-
Notifications
You must be signed in to change notification settings - Fork 1
react-native-web中ScrollView与Touchable的手势冲突问题 #97
Description
react-native-web:0.11.5
react native web中基本实现了一套与react native一致的手势响应系统。
手势响应系统一个最基本的特征就是:任何时刻有且仅有一个responder。
所以,当ScrollView与Touchable共存的时候,就会涉及到竞争。
如果竞争结果错误的话,则会导致一些意外的行为。
手势冲突
示例:
<ScrollView>
<Touchable onPress={() => {}} />
<Touchable onPress={() => {}} />
<Touchable onPress={() => {}} />
...
</ScrollView>
其中Touchable是一个比较大的卡片,水平铺满整个ScrollView,高度越大越容易出现手势冲突问题。
按照手势响应系统的竞争机制,当手指在ScrollView中滑动时,ScrollView和Touchable都可能成为潜在的responder。
ScrollView的优先级更高一些,它可以在capture阶段响应touch事件:
onStartShouldSetResponderCapture的具体实现如下:
ScrollView是否能优先成为responder,取决于isAnimating是否为true。
当isAnimating为true时,ScrollView成为当前responder,Touchable没有机会响应。
当isAnimating为false时,Touchable有机会得到响应成为responder。
isAnimating最终取决于ScrollResponder中的两个状态:
很遗憾,onMomentumScrollBegin、onMomentumScrollEnd这两个事件在web上并不支持:
所以ScrollView基本很难在capture阶段就成为responder。
但是ScrollView并不会就此放弃,它还会在scroll阶段再次申请成为responder:
当scroll事件发生时,如果当前isTouching为true,则会询问Touchable是否可以放弃responder:
这里的rejectResponderTermination默认为undefined,即返回了true。
Touchable比较好说话,同意放弃responder,ScrollView会终止Touchable,强制成为当前responder。
整个响应过程如下图所示:
综上可知,当手指在ScrollView中滑动时,ScrollView能否优先成为Responder的关键就在于onscroll事件能否及时响应。
分两种情况:
1、 手指慢慢滑动
这种场景下,手指在滑动过程中,ScrollView会跟着一起滚动,也就是说手指停留的时间会比较长,一般会在onscroll事件发生后,才释放手指。
ScrollView的onscrol事件及时终止了Touchable成为responder,ScrollView成为当前Responder,所以页面可以正常滚动,Touchable不会跳转。
2、 手指快速滑动
这种场景下,手指基本上是一触即松,onscroll不一定能够及时触发,如果在touchend之后才触发onscroll,那么Touchable基本就会成为当前Responder,Touchable随后就直接跳转了,造成手势冲突问题。
解决方案
这里有一个临时解决方案。
基本原理其实就是基于React的ResponderTouchHistoryStore计算出一个向量,如果大于一个阈值,则认为用户是想滚动,否则认为用户是想点击。
这个方案虽然可以解决了手势冲突问题,但引入了一个新问题:在Touchable上不能移动,一旦超过以下阈值,onPress就不会再触发了。
当然也可以考虑设置delayPressIn属性,延迟Touchable onPress的触发时间,但这个没法保证100%解决冲突问题。
目前没有找到更好的办法,综合衡量一下吧。







