react-native的PanResponder研究
一、PanResponder包含方法
目前文档版本为:v0.49
包含13个方法:
//这个视图是否在触摸开始时想成为响应器?
onStartShouldSetPanResponder:this._onStartShouldSetPanResponder,
//所以如果一个父视图要防止子视图在触摸开始时成为响应器,它应该有一个 onStartShouldSetResponderCapture 处理程序,返回 true。
onStartShouldSetPanResponderCapture:this._onStartShouldSetPanResponderCapture,
//当视图不是响应器时,该指令被在视图上移动的触摸调用:这个视图想“声明”触摸响应吗?
onMoveShouldSetPanResponder:this._onMoveShouldSetPanResponder,
//所以如果一个父视图要防止子视图在移动开始时成为响应器,它应该有一个 onMoveShouldSetPanResponderCapture 处理程序,返回 true。
onMoveShouldSetPanResponderCapture:this._onMoveShouldSetPanResponderCapture,
//当前有其他的东西成为响应器并且没有释放它。
onPanResponderReject: this._onPanResponderReject,
//视图现在正在响应触摸事件。这个时候要高亮标明并显示给用户正在发生的事情。
onPanResponderGrant: this._onPanResponderGrant,
//手势开始
onPanResponderStart: this._onPanResponderStart,
//手势结束
onPanResponderEnd: this._onPanResponderEnd,
//用户正移动他们的手指
onPanResponderMove: this._onPanResponderMove,
//在触摸最后被引发,即“touchUp”
onPanResponderRelease: this._onPanResponderRelease,
//其他的东西想成为响应器。这种视图应该释放应答吗?返回 true 就是允许释放
onPanResponderTerminationRequest:this._onPanResponderTerminationRequest,
//响应器已经从该视图抽离了。可能在调用onResponderTerminationRequest 之后被其他视图获取,也可能是被操作系统在没有请求的情况下获取了(发生在 iOS 的 control center/notification center)。
onPanResponderTerminate: this._onPanResponderTerminate,
// Returns whether this component should block native components from becoming the JS
// responder. Returns true by default. Is currently only supported on android.
onShouldBlockNativeResponder:this._onShouldBlockNativeResponder,
需要注意的是其中有6个方法是控制函数,需要返回true/false
分别是:
onStartShouldSetPanResponder
onStartShouldSetPanResponderCapture
onMoveShouldSetPanResponder
onMoveShouldSetPanResponderCapture
onPanResponderTerminationRequest
onShouldBlockNativeResponder(only Android)
二、方法调用顺序
1、点击移动
//点下触发
_onStartShouldSetPanResponderCapture
_onPanResponderGrant
_onShouldBlockNativeResponder
_onPanResponderStart
//移动触发
_onPanResponderMove
//抬起触发
_onPanResponderEnd
_onPanResponderRelease
当onStartShouldSetPanResponderCapture返回false时,顺序为:
_onStartShouldSetPanResponderCapture
_onStartShouldSetPanResponder
_onPanResponderGrant
_onShouldBlockNativeResponder
_onPanResponderStart
_onPanResponderMove
_onPanResponderEnd
_onPanResponderRelease
当onStartShouldSetPanResponderCapture,onStartShouldSetPanResponder都为false时,顺序为:
_onStartShouldSetPanResponderCapture
_onStartShouldSetPanResponder
_onMoveShouldSetPanResponderCapture
_onPanResponderGrant
_onShouldBlockNativeResponder
_onPanResponderMove
_onPanResponderEnd
_onPanResponderRelease
PS:注意这里onPanResponderStart已经不触发了,说明如果在两个start的捕获都返回false之后,是不会触发onPanResponderStart的。
将onStartShouldSetPanResponderCapture,onStartShouldSetPanResponder,onMoveShouldSetPanResponderCapture都为false时,顺序为:
_onStartShouldSetPanResponderCapture
_onStartShouldSetPanResponder
_onMoveShouldSetPanResponderCapture
_onMoveShouldSetPanResponder
_onPanResponderGrant
_onShouldBlockNativeResponder
_onPanResponderMove
_onPanResponderEnd
_onPanResponderRelease
将四个都返回false,则
_onStartShouldSetPanResponderCapture
_onStartShouldSetPanResponder
_onMoveShouldSetPanResponderCapture
_onMoveShouldSetPanResponder
_onMoveShouldSetPanResponderCapture
_onMoveShouldSetPanResponder
_onMoveShouldSetPanResponderCapture
_onMoveShouldSetPanResponder
在移动时会一直不断地请求onMoveShouldSetPanResponderCapture,onMoveShouldSetPanResponder的返回值,在确定接下来是否会响应以他方法。
三、父子view之间的手势处理
1、父子view都进行PanResponder的相应,那么在子view上的手势会如何呢?结构如下:
<View style={{ flex: 1, }}>
<View
style={{ flex: 1, backgroundColor: 'yellow' }}
{...this.panResponder.panHandlers}>
<View
style={{ height: 150, width: 200, backgroundColor: 'red' }}
{...this.childPanResponder.panHandlers}
>
</View>
</View>
</View>
2、点击移动调用顺序
- _onStartShouldSetPanResponderCapture为true时:
_onStartShouldSetPanResponderCapture
_onPanResponderGrant
_onShouldBlockNativeResponder
_onPanResponderStart
_onPanResponderMove
_onPanResponderEnd
_onPanResponderRelease
子view的PanResponder并不会被激活,手势事件全部被父view接受处理。
- 当onStartShouldSetPanResponderCapture返回为为false时:
这里要分两种情况:
1)点下抬起,没有移动
_onStartShouldSetPanResponderCapture
_onChildStartShouldSetPanResponderCapture
_onChildPanResponderGrant
_onChildShouldBlockNativeResponder
_onChildPanResponderStart
_onChildPanResponderEnd
_onChildPanResponderRelease
2)点下-移动-抬起
//点下操作
_onStartShouldSetPanResponderCapture
_onChildStartShouldSetPanResponderCapture
_onChildPanResponderGrant
_onChildShouldBlockNativeResponder
_onChildPanResponderStart
//移动操作
_onMoveShouldSetPanResponderCapture
_onPanResponderGrant
_onShouldBlockNativeResponder
_onChildPanResponderTerminationRequest
_onChildPanResponderTerminate
_onPanResponderMove
//抬起操作
_onPanResponderEnd
_onPanResponderRelease
这里我们可以看到,当onStartShouldSetPanResponderCapture不捕获时,手势事件会交给子view处理,但是当移动时,依然会去调用父view的onMoveShouldSetPanResponderCapture再确认移动是否有父view处理。而且,我们可以看到事件在子view上时,onStartShouldSetPanResponder是不会被调用的,手势的释放捕获完全由onStartShouldSetPanResponderCapture控制。
- onStartShouldSetPanResponderCapture,onMoveShouldSetPanResponderCapture均为false时:
//点下
_onStartShouldSetPanResponderCapture
_onChildStartShouldSetPanResponderCapture
_onChildPanResponderGrant
_onChildShouldBlockNativeResponder
_onChildPanResponderStart
//移动
_onMoveShouldSetPanResponderCapture
_onMoveShouldSetPanResponder
_onPanResponderGrant
_onShouldBlockNativeResponder
_onChildPanResponderTerminationRequest
_onChildPanResponderTerminate
_onPanResponderMove
//抬起
_onPanResponderEnd
_onPanResponderRelease
可以看到和Start不一样,虽然onMoveShouldSetPanResponderCapture为false,但是接下来会继续去判断onMoveShouldSetPanResponder,如果返回true,则会继续生成父view的手势,子view此时会请求onChildPanResponderTerminationRequest,返回true则会释放,之后会变成父view的移动事件处理。
- 而如果子view不释放,即onChildPanResponderTerminationRequest返回false,则:
//点下
_onStartShouldSetPanResponderCapture
_onChildStartShouldSetPanResponderCapture
_onChildPanResponderGrant
_onChildShouldBlockNativeResponder
_onChildPanResponderStart
//移动
_onMoveShouldSetPanResponderCapture
_onMoveShouldSetPanResponder
_onPanResponderGrant
_onShouldBlockNativeResponder
_onChildPanResponderTerminationRequest
_onPanResponderReject
_onChildPanResponderMove
_onMoveShouldSetPanResponderCapture
_onMoveShouldSetPanResponder
_onPanResponderGrant
_onShouldBlockNativeResponder
_onChildPanResponderTerminationRequest
_onPanResponderReject
_onChildPanResponderMove
_onMoveShouldSetPanResponderCapture
_onMoveShouldSetPanResponder
_onPanResponderGrant
_onShouldBlockNativeResponder
_onChildPanResponderTerminationRequest
_onPanResponderReject
_onChildPanResponderMove
//抬起
_onChildPanResponderEnd
_onChildPanResponderRelease
我们可以看到子view拒绝释放后,父view会收到onPanResponderReject通知,而移动事件会继续交由子view处理,每次移动都是如此循环而最后做onChildPanResponderMove操作。
- onStartShouldSetPanResponderCapture,onMoveShouldSetPanResponderCapture,onMoveShouldSetPanResponder均为false时:
_onStartShouldSetPanResponderCapture
_onChildStartShouldSetPanResponderCapture
_onChildPanResponderGrant
_onChildShouldBlockNativeResponder
_onChildPanResponderStart
_onMoveShouldSetPanResponderCapture
_onMoveShouldSetPanResponder
_onChildPanResponderMove
_onMoveShouldSetPanResponderCapture
_onMoveShouldSetPanResponder
_onChildPanResponderMove
_onMoveShouldSetPanResponderCapture
_onMoveShouldSetPanResponder
_onChildPanResponderMove
_onChildPanResponderEnd
_onChildPanResponderRelease
没什么好说的,父view不处理交由子view处理,但是每次移动都要处理是否捕获。
总结:
1)单view处理情况比较简单,各方法顺序触发。
2)父子view嵌套,onStartShouldSetPanResponderCapture,onMoveShouldSetPanResponderCapture来确定手势是否捕获并处理,捕获之后相应的事件不会再交给子view,但是移动时,就算onMoveShouldSetPanResponderCapture确定不捕获,依然会再通过onMoveShouldSetPanResponder判断一次是否处理,若不处理则交给子view,若处理,则此时子view通过onChildPanResponderTerminationRequest可以确认是否移交,不移交则子view继续处理,移交则交给父view处理移动事件。
四、子view是scrollView
- 基本上与view相同,因为scrollView没有处理拒绝的方法,所以只有当四个捕获方法都返回false时,会交于scrollView处理,会触发onScroll方法。
PS:特别注意的是,子view是scrollView时,安卓虽然表面上可以控制住,但是就算move的两个捕获函数都返回true进行捕获,上下多次滑动,也会偶现调用onScroll方法的情况,我想这个在之后的版本可能会修复,目前来说这是个安卓的坑,可以通过设置scrollable来处理。
PS2:如果事件已经交给scrollView去处理,在onScroll过程中,父view想要捕获事件,会被reject,也就是说必须等scrollView处理结束,父view才能重新走程序接管事件处理。
五、父view是scrollView
这里使用如下结构:
<ScrollView style={{ flex: 1, backgroundColor: 'blue' }}>
<View
style={{ height: 200, width: 200, backgroundColor: 'yellow' }}
{...this.panResponder.panHandlers}>
</View>
</ScrollView>
其实这里对view的事件捕获没什么特别的,只需要注意一个地方,子view在处理move事件中,如果scrollView在当前move下是会移动的,则马上会被父scrollView要求事件接管,此时会调一次onPanResponderTerminationRequest方法,子view的这个方法如果返回为true,则子view不再处理事件,返回false则继续处理。但是不管返回什么,父scrollView该滑滑,并不会受到子view的影响,可以理解为scrollView的事件处理级别高于panResponder,并不会被拒绝就停止响应。
//onPanResponderTerminationRequest返回true的情况
_onStartShouldSetPanResponderCapture
_onPanResponderGrant
_onShouldBlockNativeResponder
_onPanResponderStart
_onPanResponderMove //调用次数不等,基本上5次左右就会被调onPanResponderTerminationRequest确认是否被接管
_onPanResponderTerminationRequest
_onPanResponderRelease
//onPanResponderTerminationRequest返回false的情况
_onStartShouldSetPanResponderCapture
_onPanResponderGrant
_onShouldBlockNativeResponder
_onPanResponderStart
_onPanResponderMove
_onPanResponderTerminationRequest
_onPanResponderMove
_onPanResponderEnd
_onPanResponderRelease
这里子view不能拒绝scrollView的响应,确实在一些应用场景会比较难操作,比如手势滑动翻页,往往最外层是scrollView,我们在上下滑动希望处理子view手势,左右滑动处理翻页,但是不希望上下滑动处理子view手势时,左右滑动还会触发scrollView的滑动,但目前来看无法实现。
2017-11-13更新:iOS通过scrollView的canCancelContentTouches属性来设置scrollView的事件争抢,安卓可以通过onShouldBlockNativeResponder来返回是否阻断原生响应。