背景
移动设备
上的各种点击事件与web
上完全不一样, 在web
上,应用与用户交互是通过鼠标
,只能利用鼠标的单击
操作;而在移动设备
上,是通过手势系统
,用户是通过触摸屏幕
与应用交互的,这里的情况比web
上要复杂很多,比如 App 需要判断用户的触摸到底是在滚动页面,还是滑动一个 widget,或者只是一个单纯的点击。甚至还有多点同时触控的情况。
一般我们是通过 React Native
自带的 Gesture Responder System 来管理app中的手势操作
的整个生命周期的。
React Native Gesture Handler是什么?
React Native Gesture Handler
是适用于react-native
和 react-native-web
的处理手势操作的库,目的是替代React Native
自带的 Gesture Responder System ,后者在性能方面有一些限制。
React Native Gesture Handler
可以带来等多的手势操作和更好的性能,因为它使用了Android
和iOS
的原生 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) 触发,询问是否要成为响应者
在冒泡机制下,默认情况下,层级最深的组件最先开始响应,所以前面描述的这种情况,如果 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