React Native使用PanResponder API滑动多选时获取选中组件方案咨询
解决方案
现有问题梳理
- 你当前将
PanResponder绑定到了每个子选项的容器上,正确做法是绑定到外层的大容器上,才能监听整个选择区域的滑动事件 - 未存储每个子选项的布局位置,仅通过固定宽高计算坐标误差极大,没有排除外层容器的屏幕偏移量、子元素margin/padding的影响
- 坐标计算未做容器相对坐标转换,
gesture.moveX/moveY是屏幕绝对坐标,不是相对于选择容器的坐标
改造步骤
1. 新增变量存储布局信息
在constructor中新增两个ref和布局存储数组:
constructor(props) { super(props); // 新增:外层容器ref,存储子元素布局的数组 this.containerRef = React.createRef(); this.hourItemLayouts = []; // 新增:记录上一次选中的下标,避免重复触发 this.lastSelectedIndex = -1; const panResponder = PanResponder.create({ onStartShouldSetPanResponder: (evt, gestureState) => true, onMoveShouldSetPanResponder: (evt, gestureState) => true, onPanResponderMove: async (event, gesture) => { // 先获取外层容器的位置信息 const containerMeasure = await new Promise((resolve) => { this.containerRef.current.measure((x, y, width, height, pageX, pageY) => { resolve({ pageX, pageY, width, height }); }); }); // 转换为相对于容器的坐标 const relativeX = gesture.moveX - containerMeasure.pageX; const relativeY = gesture.moveY - containerMeasure.pageY; // 遍历所有子元素布局,判断当前坐标落在哪个元素范围内 this.hourItemLayouts.forEach(item => { const {x, y, width, height, index} = item; if ( relativeX >= x && relativeX <= x + width && relativeY >= y && relativeY <= y + height && index !== this.lastSelectedIndex ) { // 命中元素,触发选中逻辑 this.hourSelect(this.state.hours[index], index); this.lastSelectedIndex = index; } }) }, // 新增:滑动结束后重置上一次选中下标 onPanResponderRelease: () => { this.lastSelectedIndex = -1; } }); this.state = { hours: [ {isSelected: false, hourText: '10:00'}, {isSelected: false, hourText: '10:20'}, {isSelected: false, hourText: '10:40'}, {isSelected: false, hourText: '11:00'}, {isSelected: false, hourText: '11:20'}, {isSelected: false, hourText: '11:40'}, {isSelected: false, hourText: '12:00'}, {isSelected: false, hourText: '12:20'}, {isSelected: false, hourText: '12:40'}, ], panResponder, }; }
2. 修改render部分的绑定逻辑
把PanResponder绑定到外层Box,给每个子项加onLayout收集布局信息,同时修正props拼写错误:
<Box ref={this.containerRef} flex={1} mt={20} alignItems={"flex-start"} justifyContent={"center"} flexDirection={"row"} flexWrap={"wrap"} // 把PanResponder绑到外层容器 {...this.state.panResponder.panHandlers} > {this.state.hours.map((hours, index) => { return ( <View style={{ backgroundColor: "red", }} // 新增:收集每个子元素的布局信息 onLayout={(e) => { const {x, y, width, height} = e.nativeEvent.layout; this.hourItemLayouts[index] = {x, y, width, height, index}; }} > <TouchableOpacity style={{ backgroundColor: "blue" }} onPress={() => { this.hourSelect(hours, index); }} > <HoursComponent // 修正prop拼写错误,原组件接收的是hourText不是hoursText hourText={hours.hourText} isSelected={hours.isSelected} hours={hours} /> </TouchableOpacity> </View> ); })} </Box>
3. 补充hourSelect方法(如果你还没写的话)
hourSelect = (hourItem, index) => { const newHours = [...this.state.hours]; newHours[index].isSelected = !newHours[index].isSelected; this.setState({hours: newHours}); }
注意事项
- 如果需要滑动连续选中而不是点击反选,可以修改
hourSelect里的逻辑,滑动时直接设为true即可 - 如果容器布局会动态变化,可以在布局更新时清空
this.hourItemLayouts重新收集 - 测试时如果出现坐标偏移,检查外层容器是否有状态栏、导航栏的高度影响,可对应调整相对坐标的偏移量
内容的提问来源于stack exchange,提问作者masterAvatarr
相关产品推荐
相关产品推荐

