Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
hushicai opened this issue Nov 2, 2021 · 1 comment
Open

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

hushicai opened this issue Nov 2, 2021 · 1 comment

Comments

@hushicai
Copy link
Owner

hushicai commented Nov 2, 2021

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%解决冲突问题。

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

@hushicai
Copy link
Owner Author

hushicai commented Nov 2, 2021

还有一个场景,ScrollView中的Touchable高度比较小,正常操作的情况下,是不会出现手势冲突问题的。

表现虽然没问题,只不过是因为Touchable的HitRect比较小,当手指移动时,onPress还未触发,就已经离开了Touchable的HitRect区域。

image

如图所示,当手指释放时,Touchable已经离开了HitRect,不再是当前Responder了,因此也不会再触发onPress事件了。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant