React Native 手势触摸事件机制详解(进阶篇)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u013718120/article/details/83213261

在基础篇,对RN中的触摸事件做了详细的介绍。相信大家对于触摸事件流程机制有了更为清晰的认识。本篇博客中,同样延续基础篇中结尾的内容,对触摸事件的执行流程从代码层进行更深的说明,并使用RN系统提供的高级API作为实战,完成高仿微信通讯录快速浏览的效果。

一、RN系统触摸组件触发机制

在基础篇中,我们对RN系统提供的触摸组件(Touchable*)作了一些介绍,并通过代码来说明如何实现点击相关的事件,如下:

 <TouchableOpacity 
    style={ styles.btn }
    onPressIn={(evt)=>this.onPressIn(evt)}
    onPressOut={(evt)=>this.onPressOut(evt)}
    onLongPress={(evt)=>this.onLongPress(evt)}
    onPress={(evt)=>this.onPress(evt)}>
    <Text style={ styles.btnText }>点击按钮</Text>
</TouchableOpacity>

事件的实现方式很简单,使用 console.log 打印当前的事件名称。

测试用例

(1)手指按下,不弹起

(2)手指按下,弹起

(3)手指按下,不弹起,并滑动出View范围

测试结果

(1)手指按下,不弹起

未绑定长按事件

onPressIn

绑定长按事件

onPressIn
onLongPress

(2)手指按下,弹起

非长按(单击

onPressIn
onPressOut
onPress

长按

onPressIn
onLongPress
onPressOut

(3)手指按下,不弹起,并滑动出View范围

非长按

onPressIn
onPressOut

长按

onPressIn
onLongPress
onPressOut

结论

从上述三种触发方式中打印的log,我们可以对 onPress,onPressIn,onPressOut,onLongPress 事件的触发条件和触发顺序有一个比较清晰的了解: 

(1)未绑定长按事件,手指按下,不弹起只会触发 onPressIn,反之,则会触发 onPressIn -> onLongPress

(2)未绑定长按事件,手指按下,弹起,即单击,会触发 onPressIn -> onPressOut -> onPress,反之,如果长按后弹起,会触发onPressIn -> onLongPress -> onPressOut

(3)未绑定长按事件,手指按下,不弹起,并滑动出View范围,组件会自动失去焦点,并会触发 onPressIn -> onPressOut,反之,则会触发 onPressIn -> onLongPress -> onPressOut

可以看到,系统为我们提供了触摸组件相对来说较为简单,基本可以完全覆盖点击场景。如果涉及较为复杂的触摸交互,还是需要自定义触摸事件上场,接下来我们继续来看自定义触摸事件的触发机制。

二、RN系统触摸组件触发机制

单组件

在RN中任何View组件都可以实现自定义触摸事件,方式很简单,只需要将触摸事件行为方式作为props属性传递给View组件即可。测试代码如下:

componentWillMount() {

        this.gestureHandlers = {
            /**
             * 在手指触摸开始时申请成为响应者
             */
            onStartShouldSetResponder: (evt) =>  {
                console.log('onStartShouldSetResponder');
                return true;
            },
            /**
             * 在手指在屏幕移动时申请成为响应者
             */
            onMoveShouldSetResponder: (evt) => {
                console.log('onMoveShouldSetResponder');
                return true;
            },
            /**
             * 申请成功,组件成为了事件处理响应者,这时组件就开始接收后序的触摸事件输入。
             * 一般情况下,这时开始,组件进入了激活状态,并进行一些事件处理或者手势识别的初始化
             */
            onResponderGrant: (evt) => {
                console.log('onResponderGrant');
            },

            /**
             * 表示申请失败了,这意味者其他组件正在进行事件处理,
             * 并且它不想放弃事件处理,所以你的申请被拒绝了,后续输入事件不会传递给本组件进行处理。
             */
            onResponderReject: (evt) => {
                console.log('onResponderReject');
            },

            /**
             * 表示手指按下时,成功申请为事件响应者的回调
             */
            onResponderStart: (evt) => {
                console.log('onResponderStart');
            },

            /**
             * 表示触摸手指移动的事件,这个回调可能非常频繁,所以这个回调函数的内容需要尽量简单
             */
            onResponderMove: (evt) => {
                console.log('onResponderMove');
            },

            /**
             * 表示触摸完成(touchUp)的时候的回调,表示用户完成了本次的触摸交互,这里应该完成手势识别的处理,
             * 这以后,组件不再是事件响应者,组件取消激活
             */
            onResponderRelease: (evt) => {
                console.log('onResponderRelease');
            },

            /**
             * 组件结束事件响应的回调
             */
            onResponderEnd: (evt) => {
                console.log('onResponderEnd');
            },

            /**
             * 当其他组件申请成为响应者时,询问你是否可以释放响应者角色让给其他组件
             */
            onResponderTerminationRequest: (evt) => {
                console.log('onResponderTerminationRequest');
                return true;
            },

            /**
             * 如果 onResponderTerminationRequest 回调函数返回为 true,
             * 则表示同意释放响应者角色,同时会回调如下函数,通知组件事件响应处理被终止
             * 这可能是由于其他View通过onResponderTerminationRequest请求的,也可能是由操作系统强制夺权(比如iOS上的控制中心或是通知中心)。
             */
            onResponderTerminate: (evt) => {
                console.log('onResponderTerminate');
            }
        }
    }

    render() {
        return (
            <View { ...this.gestureHandlers } style={ styles.container } />
        )
    }

上述代码中我们在 ComponentWillMount 生命周期函数中定义了 gestureHandlers,并定义了触摸事件行为函数,通过 log 打印的方式来看自定义触摸事件执行机制。View组件的 style 样式为一个蓝色填充的圆形,附上效果图

效果图

测试用例

(1)onStartShouldSetResponder  方法返回 falseonMoveShouldSetResponder 返回 false

(2)onStartShouldSetResponder  方法返回 trueonMoveShouldSetResponder 返回 false

(3)onStartShouldSetResponder  方法返回 falseonMoveShouldSetResponder 返回 true

(4)onStartShouldSetResponder  方法返回 trueonMoveShouldSetResponder 返回 true

测试触摸方式分两种:

(1)单击事件

(2)滑动事件

测试结果

(1)onStartShouldSetResponder  方法返回 falseonMoveShouldSetResponder 返回 false

单击

onStartShouldSetResponder

滑动

onStartShouldSetResponder

onMoveShouldSetResponder

(2)onStartShouldSetResponder  方法返回 trueonMoveShouldSetResponder 返回 false

单击

   按下
   onStartShouldSetResponder
   onResponderGrant
   onResponderStart

   弹起
   onResponderEnd
   onResponderRelease

滑动

   按下
   onStartShouldSetResponder
   onResponderGrant
   onResponderStart

   移动
   onResponderMove(多次执行)

   弹起
   onResponderEnd
   onResponderRelease

(3)onStartShouldSetResponder  方法返回 falseonMoveShouldSetResponder 返回 true

单击

onStartShouldSetResponder

滑动

   按下
   onStartShouldSetResponder

   移动
   onMoveShouldSetResponder
   onResponderGrant
   onResponderMove(多次执行)

   弹起
   onResponderEnd
   onResponderRelease

(4)onStartShouldSetResponder  方法返回 trueonMoveShouldSetResponder 返回 true

   与 onStartShouldSetResponder  方法返回 trueonMoveShouldSetResponder 返回 false 执行结果完全一致。

测试结论

(1)当 onStartShouldSetResponder 、onMoveShouldSetResponder 方法都返回 false,当手指按下或者进行移动时,RN系统询问当前组件并发现在点击和滑动时都不申请成为事件响应者,只会执行 onStartShouldSetResponder 、onMoveShouldSetResponder 方法,并立刻结束,后续触摸事件不会被触发。

(2)onStartShouldSetResponder  方法返回 trueonMoveShouldSetResponder 返回 false,当手指按下时,RN系统询问当前组件,发现 onStartShouldSetResponder 方法返回 true,此时申请成为响应者(不存在多组件触摸事件互斥场景),在申请成功后,会依次执行 onResponderGrantonResponderStart 方法。由于当前组件已经成为了响应者,此时手指如果继续移动,系统并不会触发 onMoveShouldSetResponder 方法,并立刻执行 onResponderMove 方法。当手指离开屏幕,此时会触发onResponderEndonResponderRelease 方法,一次完整的触摸事件结束。

(3)onStartShouldSetResponder  方法返回 falseonMoveShouldSetResponder 返回 true,当手指按下时,RN系统询问当前组件,发现 onStartShouldSetResponder 方法返回 false,此时不申请成为响应者,执行 onStartShouldSetResponder方法完毕后立刻结束。由于当前组件不是响应者,此时手指按下后如果继续移动,系统会触发 onMoveShouldSetResponder 方法,再次询问当前组件是否要申请成为响应者,此方法返回 true ,即申请成为响应者,在申请成功后,会依次执行 onResponderGrantonResponderMove 方法。当手指离开屏幕,此时会触发onResponderEndonResponderRelease 方法,一次完整的触摸事件结束。

⚠️与(2)测试对比可以发现:当 onStartShouldSetResponder  方法返回 true,法返手指按下申请响应者授权成功后,会依次执行 onResponderGrantonResponderStart 方法。而当 onStartShouldSetResponder  方法返回 falseonMoveShouldSetResponder 方法返回 true 时,此时在滑动申请成为响应者授权成功后,会依次执行 onResponderGrantonResponderMove 方法,而不会触发onResponderStart 方法。

(4)onStartShouldSetResponder  方法返回 trueonMoveShouldSetResponder 返回 true,结论是与onStartShouldSetResponder  方法返回 trueonMoveShouldSetResponder 返回 false,执行机制相同。其实此时结合上述的分析,我们很容易明白原因。在onStartShouldSetResponder  方法返回 true,并申请成为响应者,此时在移动过程中系统不会再去询问 onMoveShouldSetResponder,即会忽略该方法。并继续执行后续的触摸事件。最终形成一次完整的触摸事件操作。

多组件

在实际开发过程中,肯定不仅仅处理单组件触摸事件行为,可能会涉及到多个组件间的交互,或者多层次的嵌套组件交互。在RN中,我们知道在同一时刻,只会存在一个响应者。当使用一个手指激活一个组件成为响应者后,在当前手指不释放的情况下,又去触摸激活另一个组件会发生什么呢?这就会涉及到多个组件间共同申请响应者互斥的场景。还记得我们在基础篇中介绍的onResponderTerminationRequest、onResponderTerminate 方法吗?

onResponderTerminationRequest 方法需要返回一个bool类型值,来决定当有其他组件同时申请成响应者时,当前组件是否放弃响应者权限。此时会分为两种情况:

(1)onResponderTerminationRequest 方法返回 true

         此时系统发现当有新当组件申请成为响应者时,当前组件会释放掉响应者身份,并将权限交与新组件。新组件onResponderGrant 方法被调用 ,当前组件的 onResponderTerminate 方法被调用,并将响应者身份权限状态释放。

(2)onResponderTerminationRequest 方法返回 false

        此时系统发现当有新当组件申请成为响应者时,当前组件不会释放掉响应者身份。新组件申请响应者权限失败,这也意味其他组件正在进行事件处理,并且它不想放弃事件处理,所以你的申请被拒绝了,后续输入事件不会传递给本组件进行处理。新组件的 onResponderReject 方法会被调用。

由于在模拟器上不好模拟多指触控请求,当家可以尝试编写demo,并运行真机查看效果。

三、触摸手势高级API

猜你喜欢

转载自blog.csdn.net/u013718120/article/details/83213261
今日推荐