美文网首页
React Native 手势触摸事件机制详解(基础篇)

React Native 手势触摸事件机制详解(基础篇)

作者: 基因宝研发团队 | 来源:发表于2021-03-04 16:16 被阅读0次

    概述

    作为与用户交互的第一层,触摸事件直接影响着用户行为体验。在Android 和 iOS 平台设备中,对于触摸机制做了非常完善的封装,能够很方便的帮助开发者处理基本的触摸行为操作,原生平台通过注册Listener的方式可以轻松的实现单击,双击等操作。在RN中同样提供了与Native触摸事件映射一致的处理方式,方便React Native开发者处理触摸行为,定义触摸操作。

    RN系统触摸组件

    RN中实现按钮单击组件的方式很简单,系统为我们提供了四种方式:

    (1)TouchableOpacity
    (2)TouchableHightLight
    (3)TouchableNativeFeedback(仅限Android)
    (4)TouchableWithoutFeedback

    以上四种方式相信大家都不陌生,都是以 Touchable* 开始,实现单击事件只需要声明onPress属性即可。以 TouchableOpacity 为例:

    <TouchableOpacity onPress={()=>this.onPress()}>
       <Text>单击按钮</Text>
    </TouchableOpacity>
    

    除了onPress属性,系统还为我们提供了:

    (1)onLongPress
    (2)onPressIn
    (3)onPressOut

    顾名思义,onLongPress即为长按点击、onPressIn点击开始(手指按下)、onPressOut点击结束(手指离开)。

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

    四个方法也提供了触摸事件数据,所以也可以写成如下:

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

    evt 中包含了关于触摸事件相关的数据,大部分情况下我们只需要关心 nativeEvent,其大致结构如下:

    nativeEvent
    可以看到 nativeEvent 中包含了手指触摸的一些坐标及时间戳参数,关于具体的参数含义我们后面再进行具体探讨。

    自定义触摸事件处理

    从Touchable* 组件的使用可以看出,其内部实现了触摸之后的事件处理。开发者以简单的属性声明方式,即可完成对于点击事件的操作。且RN组件默认不支持触摸事件的处理(Text组件除外),如果想要处理触摸事件,首先要【申请】成为事件响应者,当成为事件响应者之后,即可处理发生在该组件之上的触摸事件,例如:按下(Start)、移动(Move)、弹起(Release),最终释放响应行为。即一次完整的触摸流程大致如下:

    申请成为触摸事件响应者 -> 成为触摸事件响应者 -> 处理触摸事件 -> 释放触摸事件 -> 触摸事件结束
    整个事件流程中,组件的事件身份分为两种:非事件响应者、事件响应者。

    非事件响应者

    授权
    在上述部分我们提到,RN中的组件除Text外,默认是不能进行触摸事件处理响应的,即非事件响应者。如果要进行触摸事件处理,首先需要申请成为触摸事件响应者。对应申请的处理方法如下:

    View.props.onStartShouldSetResponder(evt): => bool  
    View.props.onMoveShouldSetResponder(evt): => bool  
    

    从名称可以看出,onStartShouldSetResponder在手势触开始时(按下)被触发,onMoveShouldSetResponder在手势滑动时被触发。两个方法都需要返回boolean类型值:onStartShouldSetResponder 在手指按下屏幕时,RN系统会询问当前组件是否需要申请成为事件响应者,true表示成为事件响应者,false则不处理。同样,onMoveShouldSetResponder表示手指在屏幕滑动时,RN系统询问当前组件是否需要申请成为事件响应者,true表示成为事件响应者,false则不处理。

    授权结果

    当接收到上述方法返回true时,组件申请成为响应者,并处理接收后续触摸事件。在同一时间只能有一个事件处理者,此时,RN系统会协调当前所有组件到事件处理,所以不是每个组件申请都能成功,RN 通过如下两个回调来通知告诉组件它的申请结果:

    View.props.onResponderGrant: (evt) => { }
    View.props.onResponderReject: (evt) => { }
    

    onResponderGrant表示申请成功,组件已经成为事件响应者,并且接收后续的触摸事件。在该方法中,我们可以做一些手势事件初始化的操作。onResponderReject表示申请失败,失败的原因一般为其他组件正在进行触摸事件的处理,并且不放弃当前触摸事件的权限,在你申请时被拒。

    事件响应者

    通过上述步骤,当组件申请成为事件响应者后,后续当触摸事件行为都会被该组件拦截处理,并触发对应都行为函数。触摸事件行为函数如下:

    View.props.onResponderStart: (evt) => { }
    View.props.onResponderMove: (evt) => { }
    View.props.onResponderEnd: (evt) => { }
    View.props.onResponderRelease: (evt) => { }
    

    可以很明显的看到,四个事件行为方法都是以【onResponder】作为前缀,后面紧接行为方式名称。

    Start:表示手指按下,开始进行触摸行为。
    Move:手指触摸屏幕并进行移动,此回调函数触发非常频繁,尽可能不要做过多任务处理。
    End:触摸行为结束,手指弹起离开屏幕。
    Release:当手指弹起离开屏幕时,事件行为结束,一次完整的触摸事件结束,并释放当前触摸行为权限,当前组件不再是事件响应者。

    释放响应者权限

    当前组件正在进行触摸事件行为处理且没有结束时,其他组件也可能会请求系统申请成为事件响应者,此时RN会询问当前组件

    是否可以释放当前响应者身份,将权限让给其他组件。对应的函数如下:

    View.props.onResponderTerminationRequest: (evt) => bool  
    View.props.onResponderTerminate: (evt) => {}  
    

    Termination 即中止,onResponderTerminationRequest 方法需要返回boolean类型值,true 表示可以立刻释放当前响应者角色,并触发 onResponderTerminate 方法,告知当前组件触摸事件被中止。

    触摸事件被意外中断也会触发 onResponderTerminate 方法,例如:手机来电,自动关机,消息等。

    触摸事件响应参数

    从系统提供的 Touchable* 点击组件,到自定义触摸组件,可以发现触摸行为函数都有 event 参数,在文章开始介绍 Touchable* 组件时,我们说到 event 参数中包含一个触摸事件数据 nativeEvent,在该属性中包含了触摸事件中的相关参数,具体如下:

    changedTouches:[{…}] 触摸事件集合,记录从上次到本次触摸事件到所有事件,在触摸过> 程中,由于非常频繁,可能有没有及时反馈,系统用这个属性来批量上报。
    identifier: 手指触摸事件ID,多点触控场景下,用来区分手指的触摸事件。
    locationX: 触摸点在组件横向上的位置。
    locationY: 触摸点在组件纵向上的位置。
    pageX: 当前组件触摸点相对屏幕横向上的位置。
    pageY: 当前组件触摸点相对屏幕纵向上的位置。
    target: 当前组件ID。
    timestamp:当前触摸的事件的时间戳,可以用来进行滑动计算。
    touches:[{…}] 触摸事件集合,多点触摸场景下,包含当前所有触摸点的事件参数。

    在日常开发中常用的属性是 locationX|Y、pageX|Y。常用触摸事件的参数是从原生层传递到RN层,即参数数值是作为原生层到像素值,如果需要转换成RN中到逻辑单位,可以用如下方式:

    const px = evt.nativeEvent.locationX / PixelRatio.get()

    触摸事件拦截

    在第二小节中,我们花了大量篇幅来介绍自定义触摸事件的相关行为函数,触摸参数等。可以看到所有的触摸行为都是基于一个View组件之上。在日常开发中,我们肯定离不开View组件嵌套实现一个视图效果。那么嵌套组件对于触摸事件响应情况又是怎样的?

    了解 Native 开发的朋友肯定不陌生,在Native层系统处理触摸事件传递方式使用的是 冒泡机制。即事件的响应是从布局最底层的组件开始,逐层向父布局组件传递。同样,RN系统也提供了与 Native 层相同的触摸事件传递机制,用来保证在嵌套布局中的所有组件都可以得到响应处理。在某些情况下,父组件可能需要单独处理触摸事件,不需要交给子组件处理,即父组件拦截掉触摸事件的响应,消耗完成不再向下传递。同样,RN系统提供了两个函数,用来实现授权父组件事件拦截机制:

    View.props.onStartShouldSetResponderCapture: (evt)=> bool
    View.props.onMoveShouldSetResponderCapture: (evt)=> bool
    

    在触摸事件 开始,RN父布局组件会回调 onStartShouldSetResponderCapture,询问是否要拦截事件,自己接收处理, true 表示拦截。在触摸 滑动 事件时,RN父布局组件会回调 onMoveShouldSetResponderCapture,询问是否要拦截事件,自己接收处理, true 表示拦截。

    举个?,假设有A,B,C 三个组件,布局为 C是B的子组件,B是A的子组件:

    嵌套组件

    (1)A组件的 onStartShouldSetResponderCapture 返回 false,表示不拦截,事件传递到>B。
    (2)B组件的onStartShouldSetResponderCapture 返回 true,表示拦截,事件不再向下传> > 递,即事件不会传递到C。
    (3)此时B组件开始向系统申请事件响应者权限,即调用 onShouldSetResponder,如果返> 回 true,则表示授权成功,此时B组件成为事件响应者,执行Start、Move 等事件行为。
    (4)如果B组件不申请,则系统询问A组件是否申请成为事件响应者。即调用A组件等> on
    ShouldSetResponder,如果返回 true,则表示授权成功,此时A组件成为事件响应者,执行 Start、Move 等事件行为。

    关于触摸事件到处理流程,相信大家看到这里都有了自己的认知与理解。下一篇,我们会从更高级的API来了解RN中的触摸事件,并通过代码运行的结果更深的去看事件触发的行为方式。

    相关文章

      网友评论

          本文标题:React Native 手势触摸事件机制详解(基础篇)

          本文链接:https://www.haomeiwen.com/subject/ygekqltx.html