2019-02-18 关于React Native Gesture Handler

背景

移动设备上的各种点击事件与web上完全不一样, 在web上,应用与用户交互是通过鼠标,只能利用鼠标的单击操作;而在移动设备上,是通过手势系统,用户是通过触摸屏幕与应用交互的,这里的情况比web上要复杂很多,比如 App 需要判断用户的触摸到底是在滚动页面,还是滑动一个 widget,或者只是一个单纯的点击。甚至还有多点同时触控的情况。

一般我们是通过 React Native 自带的 Gesture Responder System 来管理app中的手势操作的整个生命周期的。

React Native Gesture Handler是什么?

React Native Gesture Handler是适用于react-nativereact-native-web 的处理手势操作的库,目的是替代React Native自带的 Gesture Responder System ,后者在性能方面有一些限制。

React Native Gesture Handler可以带来等多的手势操作和更好的性能,因为它使用了AndroidiOS原生 touch handling system 来处理手势。

RN 自带的 gesture responder system

View 组件是无法直接相应手势操作的,不能直接响应onPress事件。 利用gesture responder system,一个View只需要要实现了一些定义好的方法,就可以响应触摸事件了:

  • View.props.onStartShouldSetResponder: (evt) => true, - 在用户开始触摸的时候(手指刚刚接触屏幕的瞬间),是否愿意成为响应者?
  • View.props.onMoveShouldSetResponder: (evt) => true, - 如果 View 不是响应者,那么在每一个触摸点开始移动(没有停下也没有离开屏幕)时再询问一次:是否愿意响应触摸交互呢?

如果 View 返回 true,并开始尝试成为响应者,那么会触发下列事件之一:

  • View.props.onResponderGrant: (evt) => {} - View 现在要开始响应触摸事件了。这也是需要做高亮的时候,使用户知道他到底点到了哪里。
  • View.props.onResponderReject: (evt) => {} - 响应者现在“另有其人”而且暂时不会“放权”,请另作安排。

如果 View 已经开始响应触摸事件了,那么下列这些处理函数会被一一调用:

  • View.props.onResponderMove: (evt) => {} - 用户正在屏幕上移动手指时(没有停下也没有离开屏幕)。
  • View.props.onResponderRelease: (evt) => {} - 触摸操作结束时触发,比如"touchUp"(手指抬起离开屏幕)。
  • View.props.onResponderTerminationRequest: (evt) => true - 有其他组件请求接替响应者,当前的 View 是否“放权”?返回 true 的话则释放响应者权力。
  • View.props.onResponderTerminate: (evt) => {} - 响应者权力已经交出。这可能是由于其他 View 通过onResponderTerminationRequest请求的,也可能是由操作系统强制夺权(比如 iOS 上的控制中心或是通知中心)。

RN的手势响应系统是事件冒泡机制,

  • onStartShouldSetResponderCapture 是事件捕获阶段(capture phase)触发,询问是否要成为响应者
    onStartShouldSetResponder是事件冒泡阶段(bubbling phase) 触发,询问是否要成为响应者
    image.png

    image.png

在冒泡机制下,默认情况下,层级最深的组件最先开始响应,所以前面描述的这种情况,如果 A、B、C 嵌套的三个组件的 on*ShouldSetResponder 都返回为 true,那么只有 最深层的 C 组件会得到响应成为响应者。这种机制才能保证了界面所有的组件都能得到响应。

但是有些情况下,可能父组件可能需要处理事件,而禁止子组件响应。RN 提供了一个劫持机制,也就是在触摸事件往下传递的时候,先询问父组件是否需要劫持,不给子组件传递事件,也就是上面提到的两个回调。

所以在手势响应系统中,只能存在一个responder。因为,当有嵌套的View都申请成为responder时,子元素会消费掉这个事件,所以父元素就无法响应这个事件了。

Web

上面这点与web的事件传递机制不同,web中,A,B,C 能同时响应事件。
阻止传递
在不使用任何框架的情况下,我们在js中通过stopPropagation方法阻止事件继续传递。
使用框架时可使用对应的框架提供的方法。接下来我将了Vue框架的stop修饰符来阻止事件传递。

react 基于 vitrual dom 实现了 syntheticEvent (合成事件),react 事件处理器接收到一个 syntheticEvent 对象,syntheticEvent 和原生浏览器事件一样拥有同样的接口,也支持事件冒泡机制。可以通过 stopPropgation 和 preventDefault 中断。如果需要访问原生事件对象,可以使用 nativeEvent 属性。
由于原生的捕获机制并不常用,且具有 ie 的不兼容问题, react 仅实现了冒泡机制。既没有兼容问题,而且使用委托机制只有 document 节点上才有 DOM 事件也节约了内存。


基于手势响应系统的封装

Touchable

手势响应系统用起来可能比较复杂。所以RN利用了手势响应系统封装了一个抽象的Touchable实现(TouchableOpacity、TouchableHighlight等),用来做可触控的组件,使得你可以简单地以声明的方式来配置触控处理。他们可以绑定4种不同的响应方法

  • onPress
  • onPressIn
  • onPressOut
  • onLonePress
PanResponder

而对于手指滑动(拖拽)、多点触控等操作,利用上面的Touchable方法无法实现,于是RN 又在手势响应系统的基础上封装了一个 PanResponder 来处理更复杂的手势操作。封装后的PanResponder 方法的抽象程度更高,使用起来也更加方便:

PanResponder在手势响应系统的原生事件之外提供了一个新的gestureState对象,提供了更多实用的字段(具体可以看文档);

Pressable

react-native v0.63 带来的替代 Touchable*的组件,也是基于手势响应系统的


react-native-gesture-handler

更复杂的缩放,旋转等手势操作,使用手势响应系统来实现手势与UI的复杂的交互就比较复杂与繁琐了,性能也会不及原生。这时候react-native-gesture-handler的优势就展现出来了。

react-native-gesture-handler 只有的事件默认走的是事件捕获,父元素比子元素有更高的优先级响应事件,并且只能有一个handler 成为事件响应者,当任一个handler响应了事件,其他的handler的状态都会变为 cancel状态。

但是上面的行为也可以改变,使用 simultaneousHandlers 属性,可以指定其他handler 同时响应事件

参考

https://blog.csdn.net/qq_30053399/article/details/77776049
https://www.cnblogs.com/soyxiaobi/p/9498357.html
https://zhuanlan.zhihu.com/p/100831300
https://www.jianshu.com/p/ea3a2f13f4c5

你可能感兴趣的:(2019-02-18 关于React Native Gesture Handler)