移动设备上的手势识别要比在 web 上复杂得多。用户的一次触摸操做的真实意图是什么,App 要通过好几个阶段才能判断。好比 App 须要判断用户的触摸究竟是在滚动页面,仍是滑动一个 widget,或者只是一个单纯的点击。甚至随着持续时间的不一样,这些操做还会转化。此外,还有多点同时触控的状况。react
手势响应系统可使组件在不关心父组件或子组件的前提下自行处理触摸交互。web
做为与用户交互的第一层,触摸事件直接影响着用户行为体验。在Android 和 iOS 平台设备中,对于触摸机制作了很是完善的封装,可以很方便的帮助开发者处理基本的触摸行为操做,原平生台经过注册Listener的方式能够轻松的实现单击,双击等操做。在RN中一样提供了与Native触摸事件映射一致的处理方式,方便React Native开发者处理触摸行为,定义触摸操做。react-native
RN系统中为咱们提供了TouchableHighlight 与 Touchable 系列组件,不懂得本身找度娘就好了。数组
一个View只要实现了正确的协商方法,就能够成为触摸事件的响应者。经过如下两种方法去“询问”一个View是否愿意成为响应者:ide
View.props.onStartShouldSetResponder: (evt) => true,在用户开始触摸的时候(手指刚刚接触屏幕的瞬间),是否愿意成为响应者;函数
View.props.onMoveShouldSetResponder: (evt) => true, 若是View不是响应者,那么在每个触摸点开始移动(没有停下也没有离开屏幕)时再询问一次:是否愿意成为响应者?布局
若是 View 返回 true,并开始尝试成为响应者,那么会触发下列事件之一:this
View.props.onResponderGrant: (evt) => {} View如今要开始响应触摸事件了,这也是须要作高亮的时候,使用户知道他点了哪里。spa
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 上的控制中心或是通知中心)。
其中evt
是一个合成事件,它包含如下结构:
nativeEvent
-
changedTouches
- 在上一次事件以后,全部发生变化的触摸事件的数组集合(即上一次事件后,全部移动过的触摸点)
- identifier
- 触摸点的 ID
- locationX
- 触摸点相对于当前元素的横坐标
- locationY
- 触摸点相对于当前元素的纵坐标
- pageX
- 触摸点相对于根元素的横坐标
- pageY
- 触摸点相对于根元素的纵坐标
- target
- 触摸点所在的元素 ID
- timestamp
- 触摸事件的时间戳,可用于移动速度的计算
- touches
- 当前屏幕上的全部触摸点的集合
onStartShouldSetResponder
与onMoveShouldSetResponder
是以冒泡的形式调用的,即嵌套最深的节点最早调用。这意味着当多个 View 同时在*ShouldSetResponder
中返回 true 时,最底层的 View 将优先“夺权”。在多数状况下这并无什么问题,由于这样能够确保全部控件和按钮是可用的。
可是有些时候,某个父 View 会但愿能先成为响应者。咱们能够利用“捕获期”来解决这一需求。响应系统在从最底层的组件开始冒泡以前,会首先执行一个“捕获期”,在此期间会触发on*ShouldSetResponderCapture
系列事件。所以,若是某个父 View 想要在触摸操做开始时阻止子组件成为响应者,那就应该处理onStartShouldSetResponderCapture
事件并返回 true 值。
View.props.onStartShouldSetResponderCapture: (evt) => true, View.props.onMoveShouldSetResponderCapture: (evt) => true,
onStartShouldSetPanResponderCapture: (evt, gestureState) => { // 在触摸事件 开始,RN父布局组件会回调 onStartShouldSetResponderCapture,询问是否要拦截事件,本身接收处理, true 表示拦截。 console.log('onStartShouldSetPanResponderCapture') console.log(gestureState.dx) return false; }, onMoveShouldSetPanResponderCapture: (evt, gestureState) => { // 在触摸 滑动 事件时,RN父布局组件会回调 onMoveShouldSetResponderCapture,询问是否要拦截事件,本身接收处理, true 表示拦截。 console.log('onMoveShouldSetPanResponderCapture') console.log(gestureState) return false; }, onStartShouldSetPanResponder: (evt, gestureState) => { /** * 在手指触摸开始时申请成为响应者 */ console.log('onStartShouldSetPanResponder') console.log(gestureState) return true; }, onMoveShouldSetPanResponder: (evt, gestureState) => { /** * 在手指在屏幕移动时申请成为响应者 */ console.log('onMoveShouldSetPanResponder') console.log(gestureState) return true; }, onPanResponderGrant: (evt, gestureState) => { //开始手势操做。给用户一些视觉反馈,让他们知道发生了什么事情! /** * 申请成功,组件成为了事件处理响应者,这时组件就开始接收后序的触摸事件输入。 * 通常状况下,这时开始,组件进入了激活状态,并进行一些事件处理或者手势识别的初始化 */ console.log('onPanResponderGrant') console.log(gestureState) }, onPanResponderReject: (evt, gestureState) => { /** * 表示申请失败了,这意味者其余组件正在进行事件处理, * 而且它不想放弃事件处理,因此你的申请被拒绝了,后续输入事件不会传递给本组件进行处理。 */ console.log('onPanResponderReject') }, onPanResponderStart:(evt, gestureState) => { /** * 表示手指按下时,成功申请为事件响应者的回调 */ console.log('onPanResponderStart') console.log(gestureState) }, onPanResponderMove:(evt, gestureState) => { //最近一次的移动距离为gestureState.move{X,Y} // 从成为响应者开始时的累计手势移动距离为gestureState.d{x,y} /** * 表示触摸手指移动的事件,这个回调可能很是频繁,因此这个回调函数的内容须要尽可能简单 */ console.log('onPanResponderMove') console.log(gestureState) }, onPanResponderRelease:(evt, gestureState) => { //用户放开了全部的触摸点,且此时视图已经成为了响应者。 //通常来讲这个意味着一个手势操做已经完成了。 /** * 表示触摸完成(touchUp)的时候的回调,表示用户完成了本次的触摸交互,这里应该完成手势识别的处理, * 这之后,组件再也不是事件响应者,组件取消激活 */ console.log('onPanResponderRelease') console.log(gestureState) }, onPanResponderEnd:(evt, gestureState) => { /** * 组件结束事件响应的回调 */ console.log('onPanResponderEnd') console.log(gestureState) }, onResponderTerminationRequest: (evt) => { /** * 当其余组件申请成为响应者时,询问你是否能够释放响应者角色让给其余组件 */ console.log('onResponderTerminationRequest'); return true; }, onResponderTerminate: (evt) => { /** * 若是 onResponderTerminationRequest 回调函数返回为 true, * 则表示赞成释放响应者角色,同时会回调以下函数,通知组件事件响应处理被终止 * 这多是因为其余View经过onResponderTerminationRequest请求的,也多是由操做系统强制夺权(好比iOS上的控制中心或是通知中心)。 */ console.log('onResponderTerminate'); }
注释已经说明了,很少作阐述。案例以下:
/** * PanResponder 触摸事件 * @export * @class PanResponderView * @extends {Component} */ import React, { Component } from 'react'; import { View, Text, StyleSheet, PanResponder, } from 'react-native'; export default class HomeScreen extends Component { constructor(props) { super(props) this.panResponder={} } componentWillMount() { this.panResponder = PanResponder.create({ onStartShouldSetPanResponderCapture: (evt, gestureState) => { // 在触摸事件 开始,RN父布局组件会回调 onStartShouldSetResponderCapture,询问是否要拦截事件,本身接收处理, true 表示拦截。 console.log('onStartShouldSetPanResponderCapture') console.log(gestureState.dx) return false; }, onMoveShouldSetPanResponderCapture: (evt, gestureState) => { // 在触摸 滑动 事件时,RN父布局组件会回调 onMoveShouldSetResponderCapture,询问是否要拦截事件,本身接收处理, true 表示拦截。 console.log('onMoveShouldSetPanResponderCapture') console.log(gestureState) return false; }, onStartShouldSetPanResponder: (evt, gestureState) => { /** * 在手指触摸开始时申请成为响应者 */ console.log('onStartShouldSetPanResponder') console.log(gestureState) return true; }, onMoveShouldSetPanResponder: (evt, gestureState) => { /** * 在手指在屏幕移动时申请成为响应者 */ console.log('onMoveShouldSetPanResponder') console.log(gestureState) return true; }, onPanResponderGrant: (evt, gestureState) => { //开始手势操做。给用户一些视觉反馈,让他们知道发生了什么事情! /** * 申请成功,组件成为了事件处理响应者,这时组件就开始接收后序的触摸事件输入。 * 通常状况下,这时开始,组件进入了激活状态,并进行一些事件处理或者手势识别的初始化 */ console.log('onPanResponderGrant') console.log(gestureState) }, onPanResponderReject: (evt, gestureState) => { /** * 表示申请失败了,这意味者其余组件正在进行事件处理, * 而且它不想放弃事件处理,因此你的申请被拒绝了,后续输入事件不会传递给本组件进行处理。 */ console.log('onPanResponderReject') }, onPanResponderStart:(evt, gestureState) => { /** * 表示手指按下时,成功申请为事件响应者的回调 */ console.log('onPanResponderStart') console.log(gestureState) }, onPanResponderMove:(evt, gestureState) => { //最近一次的移动距离为gestureState.move{X,Y} // 从成为响应者开始时的累计手势移动距离为gestureState.d{x,y} /** * 表示触摸手指移动的事件,这个回调可能很是频繁,因此这个回调函数的内容须要尽可能简单 */ console.log('onPanResponderMove') console.log(gestureState) }, onPanResponderRelease:(evt, gestureState) => { //用户放开了全部的触摸点,且此时视图已经成为了响应者。 //通常来讲这个意味着一个手势操做已经完成了。 /** * 表示触摸完成(touchUp)的时候的回调,表示用户完成了本次的触摸交互,这里应该完成手势识别的处理, * 这之后,组件再也不是事件响应者,组件取消激活 */ console.log('onPanResponderRelease') console.log(gestureState) }, onPanResponderEnd:(evt, gestureState) => { /** * 组件结束事件响应的回调 */ console.log('onPanResponderEnd') console.log(gestureState) }, onResponderTerminationRequest: (evt) => { /** * 当其余组件申请成为响应者时,询问你是否能够释放响应者角色让给其余组件 */ console.log('onResponderTerminationRequest'); return true; }, onResponderTerminate: (evt) => { /** * 若是 onResponderTerminationRequest 回调函数返回为 true, * 则表示赞成释放响应者角色,同时会回调以下函数,通知组件事件响应处理被终止 * 这多是因为其余View经过onResponderTerminationRequest请求的,也多是由操做系统强制夺权(好比iOS上的控制中心或是通知中心)。 */ console.log('onResponderTerminate'); } }); } render() { return ( <View {...this.panResponder.panHandlers } style={ styles.container }> </View> ) } } const styles = StyleSheet.create({ container: { width: 100, height: 100, borderRadius: 50, backgroundColor: '#87CEFA' }, btn: { width: 100, height: 60, alignItems: 'center', justifyContent: 'center', backgroundColor: '#ff5511' }, btnText: { color: 'white' } });
运行效果: