React Native入门(十五)之手势系统详解

前端之家收集整理的这篇文章主要介绍了React Native入门(十五)之手势系统详解前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

前言

对于Android原生的事件分发机制和手势处理,相信Android开发者已经很熟悉了,那么同样的在RN中也有着一套对应的手势系统和事件处理逻辑!
触摸响应系统可以使组件在不关心父组件或子组件的前提下自行处理触摸交互。具体的实现在ResponderEventPlugin.js文件中。
用户的每一个操作都应该具有下列属性

  • 反馈/高亮 —— 让用户看到他们到底按到了什么东西,以及松开手后会发生什么。
  • 取消功能 —— 当用户正在触摸操作时,应该是可以通过把手指移开来终止操作。

Touchable系列组件

我们在之前讲RN中基本组件Button的时候,提到过Touchable系列组件,因为Button组件是在后边新加的,而且使用起来体验很不好,所以在设置点击触控相关的事件时,还是推荐使用Touchable系列组件来实现!

  • TouchableHighlight
  • TouchableNativeFeedback
  • TouchableOpacity
  • TouchableWithoutFeedback

在具体到本节讲的触摸操作时,就反映到这些组件的几个按压属性上!需要注意的是前三个组件都是在第四个TouchableWithoutFeedback的基础上增强而来,所以TouchableWithoutFeedback的所有基本属性,前边3个组件都有!
(TouchableWithoutFeedback由于交互体检比较差,所以不推荐使用)

TouchableHighlight为例,我们可以从源码中看出:

const TouchableHighlight = createReactClass({
  displayName: 'TouchableHighlight',propTypes: {
    ...TouchableWithoutFeedback.propTypes,//xxxx省略其他xxxx
  }
}

TouchableHighlight等前3个组件都有TouchableWithoutFeedback属性!那么触摸按压相关的,主要就是下边4个比较常用的属性

  • onPress: PropTypes.func
  • onPressIn: PropTypes.func
  • onPressOut: PropTypes.func
  • onLongPress: PropTypes.func

这块比较简单,就不再举例子了!需要的可以去github找一下官方的sample!
不想麻烦的话,淘到一篇深度好文:
“指尖上的魔法” – 谈谈React-Native中的手势
推荐一看,下边引用一下,文章中简单例子演示之后的一个总结

  1. 快速点击,只会触发press事件
  2. 只要在点击时有一个“按”的操作,就是比快速点击要按的久一点,就会触发pressin事件
  3. 如果同时绑定了pressIn,pressOut和press事件,那么当pressIn事件触发之后,如果用户的手指在绑定的组件中释放,那么接着会连续触发pressOut和press事件,此时的顺序是pressIn -> pressOut -> press。而如果用户的手指滑到了绑定组件之外才释放,那么此时将会不触发press事件,只会触发pressOut事件,此时的顺序是pressIn -> pressOut。后一种情况就是前面所说的中途取消,如果我们将回调函数绑定给press事件,那么后一种情况中回调函数并不会被触发,相当于”被取消”。
  4. 如果绑定了longPress事件,那么在pressIn事件被触发之后,press事件不会被触发,通过打点计时,可以发现longPress事件的触发时间大概是在pressIn事件发生383ms之后,当longPress事件触发之后,无论用户的手指在哪里释放,都会接着触发pressOut事件,此时的触发顺序是 pressIn -> longPress -> pressOut

Responder的生命周期

上边touchable系列组件使用非常简单,下边我们了解一下RN中的触摸响应事件的流程!
我们之所以觉得touchable系列组件使用比较简单,好像我们自己并没有去控制相关触摸的逻辑,那是因为touchable系列组件已经在内部帮我们实现了
在React Native中,响应手势的基本单位是responder,RN 的组件默认不进行处理触摸事件。组件要处理触摸事件,首先要“申请”成为触摸事件的响应者(Responder),完成事件处理以后,会释放响应者的角色。一个触摸事件处理周期,是从用户手指按下屏幕,到用户抬起手指抬起结束,这是用户的一次完整触摸操作。

生命周期回调方法

Responder的生命周期相关的回调方法是在View组件的props中!主要有以下几个属性

  • View.props.onStartShouldSetResponder
  • View.props.onMoveShouldSetResponder
  • View.props.onResponderGrant
  • View.props.onResponderReject
  • View.props.onResponderMove
  • View.props.onResponderRelease
  • View.props.onResponderTerminationRequest
  • View.props.onResponderTerminate

上边也提到过,如果组件要进行触摸事件处理,首先要申请成为事件响应者,那么前两个方法就是用来处理申请Responder的:

  • onStartShouldSetResponder: (event) => [true | false]
    用户开始触摸的时候(手指刚刚接触屏幕的瞬间),是否愿意成为响应者,true表示要响应,false表示不要响应!
    注意:此回调方法只在touch down按下的时候调用一次!
  • onMoveShouldSetResponder: (event) => [true | false]
    如果View不是响应者,那么在每一个触摸点开始移动touch move(没有停下也没有离开屏幕)时再询问一次:是否愿意响应触摸交互。同样的true表示要响应,false表示不要响应!
    注意:此回调方法只在onStartShouldSetResponder返回false的时候调用,而且在move的过程中不断回调!

如果上边申请成为响应者return ture,那么我就要知道我是否申请成功?我是否能够开始响应用户的操作?,对应的是第3和第4个回调方法,另外如果上边申请成为响应者return false,则到此结束,不会再继续往下走下去!:

  • onResponderGrant: (evt) => {}
    表示申请成功,组件成为了事件处理响应者,组件被激活,能够开始响应手势触摸事件!
  • onResponderReject: (evt) => {}
    这个回调是跟onResponderGrant对应的,表示申请失败!因为我们需要注意的一点就是同一时间,只能有一个事件处理响应者!,如果你在申请成为响应者的时候,此时别的组件正在响应用户操作,也就是说别的组件现在是Responder,而且不放弃当前的响应权利,那么就会回调这个方法通知你申请失败!

如果申请成功,就进入了正式响应手势触摸事件的阶段,对应第5和第6个回调方法!相反的,如果申请失败,则到此结束!

  • onResponderMove: (evt) => {}
    用户开始滑动手指的时候(没有停下也没有离开屏幕),会不断回调该方法
  • onResponderRelease: (evt) => {}
    触摸操作结束时触发一次,比如touchUp(手指抬起离开屏幕)。此时本次触摸交互结束,该组件将不再是响应者,激活状态取消!

那么还有最后两个回调,我们上边提到过当有组件申请成为响应者,当前如果已经有响应者的时候,那么就是通过这两个回调去协调的!

  • onResponderTerminationRequest: (evt) => [true | false]
    有其他组件请求接替响应者,当前的View是否“放权”,如果返回ture,则当前响应者组件释放响应者权利,请求成为响应者的组件请求成功(回调onResponderGrant),相反的返回false,则表示当前组件不放权,则请求成为响应者的组件请求失败(回调onResponderReject
  • onResponderTerminate: (evt) => {}
    如果onResponderTerminationRequest回调返回ture,则下边会回调该方法,表示当前组件的响应者权力已经交出!需要注意的一点就是:也可能是由操作系统强制夺权,最简单的例子就是,突然来电话了!

下边是我总结的一整个手势触摸事件的流程图,包括父子组件请求响应,是否放权的一整个逻辑!

回调方法参数evt

evt 是一个合成触摸事件,它包含以下结构:

  • nativeEvent

    • changedTouches - 在上一次事件之后,所有发生变化的触摸事件的数组集合(即上一次事件后,所有移动过的触摸点)
    • identifier - 触摸点的ID
    • locationX - 触摸点相对于父元素的横坐标
    • locationY - 触摸点相对于父元素的纵坐标
    • pageX - 触摸点相对于根元素的横坐标
    • pageY - 触摸点相对于根元素的纵坐标
    • target - 触摸点所在的元素ID
    • timestamp - 触摸事件的时间戳,可用于移动速度的计算
    • touches - 当前屏幕上的所有触摸点的集合

捕获ShouldSet事件处理

这部分内容,找到一篇比较经典的文章,相信大部分学习RN的同学都看过,那就是race604React Native 触摸事件处理详解一文中的@H_551_301@嵌套组件事件处理小节,官方文档中说明还是太简单了,这篇文章中讲解介绍的非常清晰!
下边就是该小节的内容

上一小节介绍的都是针对单个组件来说,事件处理的流程和机制。但是前面也提到了,当组件需要作为事件处理响应者时,需要通过 onStartShouldSetResponder 或者 onMoveShouldSetResponder 回调返回值为 true 来申请。假如当多个组件嵌套的时候,这两个回调都返回了 true 的时候,但是同一个只能有一个事件处理响应者,这种情况怎么处理呢?为了便于描述,假设我们的组件布局如下:

在 RN 中,默认情况下使用冒泡机制,响应最深的组件最先开始响应,所以前面描述的这种情况,如图中,如果 A、B、C 三个组件的 on*ShouldSetResponder 都返回为 true,那么只有 C 组件会得到响应成为响应者。这种机制才能保证了界面所有的组件才能得到响应。但是有些情况下,可能父组件可能需要处理事件,而禁止子组件响应。RN 提供了一个劫持机制,也就是在触摸事件往下传递的时候,先询问父组件是否需要劫持,不给子组件传递事件,也就是如下两个回调:

  • View.props.onStartShouldSetResponderCapture:这个属性接收一个回调函数函数原型是 function(evt): bool,在触摸事件开始(touchDown)的时候,RN 容器组件会回调此函数,询问组件是否要劫持事件响应者设置,自己接收事件处理,如果返回 true,表示需要劫持;
  • View.props.onMoveShouldSetResponderCapture:此函数类似,不过是在触摸移动事件(touchMove)询问容器组件是否劫持。

可以把这种劫持机制看成是一种下沉机制,与上面的冒泡机制对应,我们可以总结 RN 事件处理流程如下图:

触摸事件开始,首先调用 A 组件的 onStartShouldSetResponderCapture,若此回调返回 false,则按照图传递到 B 组件,然后调用 B 组件 onStartShouldSetResponderCapture,若返回 true,则事件不再传递给 C 组件,直接调用本组件的 onResponderStart,则 B 组件就成为事件响应者,后续事件直接传递给它。其他的分析类似。

高级手势PanResponder

上边只是抽象出来的触摸相关的手势操作,那么一些高级的手势操作,比如拖拽,双指缩放等,就需要使用更加抽象的PanResponder来实现了!

用法

用法上与上边稍微有一些不同:

responder = PanResponder.create({
  onStartShouldSetPanResponder: (evt,gestureState) => false,//其他方法...
})

<View style={styles.container}
      //onLayout={this.onLayout}
      {...this.responder.panHandlers}
>

回调方法

回调方法基本上与上边触摸相同,就是简单的把onResponder回调中的Responder替换为PanResponder用法及其意义也一样!这里就不再赘述了!

需要注意的一点就是,PanResponder多了一个回调方法

onShouldBlockNativeResponder: (evt,gestureState) => {
  // 返回一个布尔值,决定当前组件是否应该阻止原生组件成为JS响应者
  // 默认返回true,表示禁用,全部使用 JS 中的事件处理。目前暂时只支持android。
  return true;
},

回调方法参数

PanResponder的回调方法参数除了上边的nativeEvent,还增加了一个gestureState对象,也正是由于这一对象的存在,才得以进行一些高级手势的操作!
一个gestureState对象有如下的字段:

  • stateID - 触摸状态的ID。在屏幕上有至少一个触摸点的情况下,这个ID会一直有效。
  • moveX - 最近一次移动时的屏幕横坐标
  • moveY - 最近一次移动时的屏幕纵坐标
  • x0 - 当响应器产生时的屏幕坐标
  • y0 - 当响应器产生时的屏幕坐标
  • dx - 从触摸操作开始时的累计横向路程
  • dy - 从触摸操作开始时的累计纵向路程
  • vx - 当前的横向移动速度
  • vy - 当前的纵向移动速度
  • numberActiveTouches - 当前在屏幕上的有效触摸点的数量

结语

上边基本上就是React Native关于手势处理系统全部的内容了,当然如果我们了解过原生开发中事件处理机制的话,对应的理解起来还是比较简单的!
好了,先这样,我们下篇再见!

@H_551_301@感谢:
“指尖上的魔法” – 谈谈React-Native中的手势
React Native 触摸事件处理详解

原文链接:https://www.f2er.com/react/301957.html

猜你在找的React相关文章