移动设备上的手势识别要比在 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 事件处理

  onStartShouldSetResponderonMoveShouldSetResponder是以冒泡的形式调用的,即嵌套最深的节点最先调用。这意味着当多个 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. day07什么是方法、方法的定义、方法的调用

    复习 1.数组 1)什么是数组 数组就是一种能够保存 多个相同数据类型的数据的变量 2)为什么使用数组 3)数组的构成 3.1)数组名 3.2)数组元素 3.3)数组长度 3.4)数组下标 4)数组的 ...

  2. RMAN备份数据库与表空间

    脚本: 数据库备份: backup database format='/u01/app/oracle/oradata/Backup/oradb_%d_%s.bak'; 表空间备份:backup tab ...

  3. IDEA 如何自动导入(import)

    如果大家正在使用一个未曾导入(import)过的类,或者它的静态方法或者静态字段,IDEA 会给出对应的建议,只要按下 ⌥(option)和回车就可以接受建议. 但我觉得这样做仍然很麻烦,不够智能化. ...

  4. WPF 3D Cube及点击交互

    在WPF中构建一个简单的立方体比较容易实现,可参考资料也比较众多.比较麻烦的是处理点击交互. 我在WPF中用两种方式实现了3DCube,效果图如下: 方式一: 最常见的3D内容构建模式,结构如下图. ...

  5. python3读取图像并可视化的方法(PIL/Pillow、opencv/cv2)

    原图: 使用TensorFlow做图像处理的时候,会对图像进行一些可视化的操作.下面,就来列举一些我知道的图像读取并可视化的方法. 1. Pillow模块 1.1 Pillow模块的前生 Pillow ...

  6. 深度好文:PHP写时拷贝与垃圾回收机制(转)

    原文地址:http://www.php100.com/9/20/87255.html 写入拷贝(Copy-on-write,简称COW)是一种计算机程序设计领域的优化策略.其核心思想是,如果有多个调用 ...

  7. Redis缓存NoSQL

    下面是一些关于Redis比较好的文章,因为篇幅较大,我就将其折叠起来了.不太喜欢分不同的笔记去记载,除非真的很多很多.所以本文不仅要对Redis做简单的介绍,还要分别介绍Redis中的五种结构,并会贴 ...

  8. 「SAP技术」A项目关联公司间退货STO流程

    [SAP技术]A项目关联公司间退货STO流程 1)创建公司间退货STO单据. 如下图示的公司间退货STO 4500000572, 2),VL10B, 创建交货单. 如下图交货单号:80044918, ...

  9. 如何用web3部署智能合约

    合约示例 pragma solidity ^0.4.18; contract CallMeChallenge { bool public isComplete = false; function ca ...

  10. LeetCode刷题191203 --回溯算法

    虽然不是每天都刷,但还是不想改标题,(手动狗头 题目及解法来自于力扣(LeetCode),传送门. 算法(78): 给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集). 说明: ...