react-native的PanResponder详解研究

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来返回是否阻断原生响应。

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值