移动设备上的手势识别要比在 web 上复杂得多。用户的一次触摸操作的真实意图是什么,App 要经过好几个阶段才能判断。比如 App 需要判断用户的触摸到底是在滚动页面,还是滑动一个 widget,或者只是一个单纯的点击。甚至随着持续时间的不同,这些操作还会转化。此外,还有多点同时触控的情况。

  手势响应系统可以使组件在不关心父组件或子组件的前提下自行处理触摸交互。

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

  RN系统中为我们提供了TouchableHighlight 与 Touchable 系列组件,不懂得自己找度娘就行了。

一、响应者的生命周期

  一个View只要实现了正确的协商方法,就可以成为触摸事件的响应者。通过以下两种方法去“询问”一个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 上的控制中心或是通知中心)。

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

  nativeEvent

    - changedTouches - 在上一次事件之后,所有发生变化的触摸事件的数组集合(即上一次事件后,所有移动过的触摸点)

    -  identifier - 触摸点的 ID

    -  locationX - 触摸点相对于当前元素的横坐标

    -  locationY - 触摸点相对于当前元素的纵坐标

    -  pageX - 触摸点相对于根元素的横坐标

    -  pageY - 触摸点相对于根元素的纵坐标

    -  target - 触摸点所在的元素 ID

    -  timestamp - 触摸事件的时间戳,可用于移动速度的计算

    -  touches - 当前屏幕上的所有触摸点的集合

二、捕获 ShouldSet 事件处理

  onStartShouldSetResponder与onMoveShouldSetResponder是以冒泡的形式调用的,即嵌套最深的节点最先调用。这意味着当多个 View 同时在*ShouldSetResponder中返回 true 时,最底层的 View 将优先“夺权”。在多数情况下这并没有什么问题,因为这样可以确保所有控件和按钮是可用的。

  但是有些时候,某个父 View 会希望能先成为响应者。我们可以利用“捕获期”来解决这一需求。响应系统在从最底层的组件开始冒泡之前,会首先执行一个“捕获期”,在此期间会触发on*ShouldSetResponderCapture系列事件。因此,如果某个父 View 想要在触摸操作开始时阻止子组件成为响应者,那就应该处理onStartShouldSetResponderCapture事件并返回 true 值。

View.props.onStartShouldSetResponderCapture: (evt) => true,
View.props.onMoveShouldSetResponderCapture: (evt) => true,

三、高级的手势功能PanResponder

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'
}
});

  运行效果:

  参考文档:https://reactnative.cn/docs/gesture-responder-system/

【React Native】进阶指南之二(手势响应系统)的更多相关文章

  1. 【独家】React Native 版本升级指南

    前言 React Native 作为一款跨端框架,有一个最让人头疼的问题,那就是版本更新.尤其是遇到大版本更新,JavaScript.iOS 和 Android 三端的配置构建文件都有非常大的变动,有 ...

  2. react native 项目使用 expo 二维码扫描失败

    今天学习react native,需使用expo在移动端进行调试. npm start 运行项目后,使用expo扫描二维码,始终没有反应.于是决定采用这个方法: 连上手机打开usb调试后,按下‘a’, ...

  3. react native进阶

    一.前沿||潜心修心,学无止尽.生活如此,coding亦然.本人鸟窝,一只正在求职的鸟.联系我可以直接微信:jkxx123321 二.项目总结 **||**文章参考资料:1.  http://blog ...

  4. React Native入门指南

    转载自:http://www.jianshu.com/p/b88944250b25 前言 React Native 诞生于 2015 年,名副其实的富二代,主要使命是为父出征,与 Apple 和 Go ...

  5. React Native细节知识点总结<二>

    1.关于React Native导出组件的export default和export的问题: 一个文件只能有一个export default,可以有多个export export class Temp ...

  6. React Native 学习笔记--进阶(二)--动画

    React Native 进阶(二)–动画 动画 流畅.有意义的动画对于移动应用用户体验来说是非常必要的.我们可以联合使用两个互补的系统:用于全局的布局动画LayoutAnimation,和用于创建更 ...

  7. React Native指南汇集了各类react-native学习资源、开源App和组件

    来自:https://github.com/ele828/react-native-guide React Native指南汇集了各类react-native学习资源.开源App和组件 React-N ...

  8. React Native初探

    前言 很久之前就想研究React Native了,但是一直没有落地的机会,我一直认为一个技术要有落地的场景才有研究的意义,刚好最近迎来了新的APP,在可控的范围内,我们可以在上面做任何想做的事情. P ...

  9. React Native 之 Touchable 介绍与使用

    前言 学习本系列内容需要具备一定 HTML 开发基础,没有基础的朋友可以先转至 HTML快速入门(一) 学习 本人接触 React Native 时间并不是特别长,所以对其中的内容和性质了解可能会有所 ...

随机推荐

  1. 微服务分布式 spring cloud springboot 框架源码 activiti工作流 前后分离

    1.代码生成器: [正反双向](单表.主表.明细表.树形表,快速开发利器)freemaker模版技术 ,0个代码不用写,生成完整的一个模块,带页面.建表sql脚本.处理类.service等完整模块2. ...

  2. 如何在阿里云服务器上搭建wordpress个人网站

    1.购买云服务器.域名.域名解析.配置linux系统上的web环境.FTP等参照下面的链接. https://www.cnblogs.com/smyhvae/p/4965163.html?tdsour ...

  3. Gradle-任务

    任务结果标签 当 Gradle 执行一个任务时,它会在控制台和 Tooling API 根据任务结果给任务打标签. 这些标签是根据任务是否有操作,是否应该执行操作,是否执行了操作以及这些操作做了哪些改 ...

  4. iOS-关于一些取整方式

    1. 直接转化 float k = 1.6; int a = (int)k; NSLog(@"a = %d",a); 输出结果是1,(int) 是强制类型转化,直接丢弃浮点数的小数 ...

  5. Unity Ruby's Adventure 第一步

    官方游戏教程:https://learn.unity.com/project/ruby-s-2d-rpg?language=en 鲁比大冒险是一个2D游戏入门教程,涉及动画,特效,UI,瓦片地图,音效 ...

  6. 是时候扔掉 Postman 了,试试 IntelliJ IDEA 自带的高能神器!

    前言 接口调试是每个软件开发从业者必不可少的一项技能,一个项目的的完成,可能接口测试调试的时间比真正开发写代码的时间还要多,几乎是每个开发的日常工作项.所谓工欲善其事必先利其器,在没有尝到 IDEA ...

  7. VS Code Remote,在服务器上开发程序,开启全新开发模式

    一直使用Idea开发java 程序,头疼的是太太太占用内存了,笔记本电脑经常卡爆,在服务器开发的话又太麻烦,VS Code Remote的带来,解决了这一烦恼.下面来实战一下. VS Code Rem ...

  8. 基于canvas二次贝塞尔曲线绘制鲜花

    canvas中二次贝塞尔曲线参数说明: cp1x:控制点1横坐标 cp1y:控制点1纵坐标 x: 结束点1横坐标 y:结束点1纵坐标 cp2x:控制点2横坐标 cp2y:控制点2纵坐标 z:结束点2横 ...

  9. CSS(3)---块级标签、行内标签、行内块标签

    块级标签.行内标签.行内块标签 html中的标签元素三种类型:块级标签.行内标签.行内块标签. 一.概述 1.块级标签 概念 每个块元素通常都会独自占据一整行或多整行,可以对其设置宽度.高度.对齐等属 ...

  10. KVO-键值监听

    键值监听,就是可以监听对象某个属性值的变化: 首先,在工程中,新建一个Person的类 @interface Person : NSObject @property (nonatomic, copy) ...