Skip to content

react-native-web中ScrollView与Touchable的手势冲突问题 #97

@hushicai

Description

@hushicai

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事件:

image

onStartShouldSetResponderCapture的具体实现如下:

image

ScrollView是否能优先成为responder,取决于isAnimating是否为true。

当isAnimating为true时,ScrollView成为当前responder,Touchable没有机会响应。

当isAnimating为false时,Touchable有机会得到响应成为responder。

isAnimating最终取决于ScrollResponder中的两个状态:

image

image

很遗憾,onMomentumScrollBegin、onMomentumScrollEnd这两个事件在web上并不支持

image

所以ScrollView基本很难在capture阶段就成为responder。

但是ScrollView并不会就此放弃,它还会在scroll阶段再次申请成为responder:

image

当scroll事件发生时,如果当前isTouching为true,则会询问Touchable是否可以放弃responder:

image

这里的rejectResponderTermination默认为undefined,即返回了true。

Touchable比较好说话,同意放弃responder,ScrollView会终止Touchable,强制成为当前responder。

整个响应过程如下图所示:

image

综上可知,当手指在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%解决冲突问题。

目前没有找到更好的办法,综合衡量一下吧。

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions