前端知识 | React Native手势响应浅析

目前手机市场上,全面屏时代已经势不可挡,为了增大屏幕,一个个物理按键已渐渐消失在手机上。那么,手势将成为在移动应用开发中一个重要的组成部分,移动设备上手势识别要比 web 端复杂得多,往往用户的一个手势,我们在 APP 上要通过好几个阶段去判断用户的真实意图是什么,在 ReactNative (以下简称 RN)中针对手势处理也提供了从最基本的点击手势到复杂的滑动等一系列解决方案,让我们一起去看看。

RN基本触控组件

RN 的组件除了 Text,其他组件默认是不支持点击事件的,也不能成为一个触摸事件的响应者。RN 提供了几个比较直接的处理响应事件的组件,基本上能满足大部分的点击事件的处理需求。

TouchableHighlight

TouchableNativeFeedback (仅限 Android 平台)

TouchableOpacity

TouchableWithoutFeedback


这几个组件的功能和使用方法基本类似,只是就 Touch 的效果反馈上有所差异,他们有如下几个回调方法:

onPressIn:用户触摸开始的时候,也就是手指刚落在 Touch 点击区域内的时触发

onPressOut:用户触摸结束的时候,也就是手指从 Touch 点击区域内抬起的时触发

onPress:用户完成一次从 onPressIn 到 onPressOut 的过程,且时间很短,即一次快速点击操作时触发

onLongPress:用户触发 onPressIn 且手指一段时间内没有抬起时触发


这里以 TouchableHighlight 为例,贴一个 Touch 的基本用法:

RN 中提供的 Touch 组件的使用非常简单,可以参考官方文档,这里就不做详细的介绍了,我们主要来说下用户的触摸事件处理。


gesture responder system

在 RN 中,响应手势的基本单位是 responder,具体点说就是最常见的 View 组件。任何的 View 组件都可以成为一个手势的响应者。其实要把一个普通的 View 组件开发成为一个能响应手势操作的 responder 很简单,话不多说,我们举栗子!

乍一看,WillMount 里面的这几个方法名字又长又奇怪,但是等你了解了 RN 手势响应的流程了之后,记忆这几个方法就非常简单了。在我们探索这几个方法之前,我们首先要记住一个重要的点:

一个 RN 应用中只能存在一个 responder!

 一次正常的手势操作的流程如下所示:

是否响应 Touch 或者 move 手势->grant(被激活) ->move->release (结束事件)

与流程相对应的方法是:

onStartShouldSetResponder(event) => true:在用户开始进行触摸操作时(手指刚刚接触屏幕的瞬间),询问是否申请成为触摸事件的响应者,返回 true 为需要成为响应者。

onMoveShouldSetResponder(event) => true:如果绑定的View不是响应者,那么会在用户的触摸点开始移动的时候再次询问是否申请成为触摸时间的响应者,返回true

为需要成为响应者。

假设组件通过上面的方法返回了 true,表示发出了申请需要成为响应者,但是我们前面说过,一个 RN 应用中只能有一个 responder,那么接下来就需要协调所有组件的请求,看看这个响应者的位置给谁。

onResponderGrant:(event) => {}:View 申请成功,并成为了响应者。一般情况下,这时开始,组件进入了激活状态,并进行一些事件处理或者手势识别的初始化。

onResponderReject: (event) =>{}:View 申请失败了,这就意味着有其他的组件正在成为或者已经成为了响应者,并且他不愿意交出这个权利。所以你被拒绝了~

如果你成为了响应者,那么会收到后续的事件输入并由你来决定他的行为动作:

onResponderMove: (event) => 表示触摸手指的移动事件,这个回调在一次完成的手势动作中可能会非常频繁的调用,所以这个回调函数里面的内容需要尽量简单

onResponderRelease: (event) => 表示触摸完成,相当于前面讲的 Touch 里面的 onPressOut 方法,表示用户已经完成了本次的触摸操作,同时会释放响应者这个权利。

在你成为响应者期间,其他组件也有可能会申请成为响应者,那么此时RN会通过回调来询问当前的响应者是否放权给其他申请者。回调如下:

onResponderTerminationRequest: (event) => true:如果我们返回的是 true,那就代表当前响应者同意放权,让其他的组件来当响应者,自己回归平淡的生活,同时也会回调一个函数,通知组件事件响应处理被终止了:

onResponderTerminate: (event) => {}:这个回调也会发生在系统直接终止组件的触摸事件处理中,比如用户在进行触摸操作的时候,来电话了,或者意外闪退了。

相信大家都发现了,所有的方法都有一个 event 参数,里面包含了一个触摸事件数据 nativeEvent,nativeEvent 具体结构如下图:

chanedTouches:event 数组,从上次回调上报的触摸事件,到这次上报之间的所有事件数组。因为在用户触摸过程中会产生很多事件,有时候可能还没来得及上报,系统就用这种方式批量上报

identifier:触摸的 ID,这个 ID 存在周期为从触摸开始到释放为止,主要是用来区别在多点触控的情况下,区分是哪个手指的触摸事件。

locationX 和 locationY:触摸点相对于组件的位置

pageX 和 pageY:触摸点相对于屏幕的位置

target:接收当前触摸事件的组件 ID

timestamp:当前触摸的事件的时间戳,可以用来进行滑动的相关计算(速度,停留时长)

touches:event 数组,多点触摸的时候,包含当前所有触摸点的事件


冒泡机制和事件捕获

先前我们都是针对单一组件来说的,但是在实际开发过程中,我们往往会遇到很多嵌套之类的组件,那如果在我们多重嵌套的组件中,每层组件绑定了一个手势响应且 onStartShouldSetResponder 或者 onMoveShouldSetResponder 回调都返回了 true 来申请成为响应者的话,又会怎么样呢?我们举个栗子来看看:


在这个大栗子中,我们嵌套了两层组件,使得组件布局如图:

在RN中,默认情况下会遵循冒泡机制,也就是嵌套最深的组件最先开始响应,那么我们栗子中的三层组件的 onStartShouldSetResponder 或者 onMoveShouldSetResponder 全部都返回 true 的情况下,那么 C 组件会优先成为事件响应者。但在我们的实际开发中,可能你需要的是父组件去处理触控事件,而禁止子组件响应,那肿么办?。RN 给我们提供了一个事件捕获机制,也就是在触摸事件通过冒泡机制往下传递的时候,先询问上层有申请的组件是否捕获该事件,不给子组件传递事件,即上面的栗子中,正常情况下通过冒泡机制,我们的触控事件会 A->B->C 这样传递到 C 去响应事件,当 A 传递到 B 时,会询问 A 是否捕获这个触控事件并且不再向下传递给 B 和 C,如果 A确认捕获,那么 A 即成为这个事件的响应者。具体的回调是:

onStartShouldSetResponderCapture: () => true :在触摸事件开始的时候,RN 容器的组件就会收到这么一个回调函数,询问是否捕获事件成为响应者,如果返回true,表示确认捕获事件

onMoveShouldSetResponderCapture: () =>true :在触摸事件开始移动的时候,再次询问是否捕获事件成为响应者,如果返回 true,表示确认捕获事件



PanResponder

除了 gesture responder system 之外,RN 还抽象出了一套 PanResponder 方法,这套方法的好处在于,使用起来更方便,在不改变原有的逻辑和流程的前提下,提供了更多的参数,包含了手势进行过程中更多的信息,让我们更好的去理解和处理用户的手势意图,话不多说,直接上栗子。


在上面的栗子中,我们实现了在一个白色有边框的事件响应者开始响应事件而变成绿色,然后实现拖拽效果并且在拖拽过程中变成红色,最后在释放手指又变回白色的这么一个过程。


大体上和 gesture responder system 一样,我们要注意的就是几个方法的写法加上了 Pan,并且几个回调函数多了一个 gesture 参数,他具体长这样的:

dx 和 dy:从触摸操作开始到现在的累积横向/纵向路程

moveX 和 moveY:最近一次移动时的屏幕横/纵坐标

numberActiveTouches:当前在屏幕上的有效触摸点的数量

stated:和之前一样,用来识别手指的ID

vx 和 vy:当前横向/纵向移动的速度

x0 和 y0:当触摸操作开始时组件相对于屏幕的横/纵坐标


总结

以上是我对 RN 的一些基础学习和理解,只举了一些简单的栗子,要在项目里实现一些更为复杂的手势操作,还需要进一步的摸索研究。另外需要注意的是,上述的回调函数都是在 JS 线程中进行的,可能会有些许延迟。


【海说软件接受各种技术咨询及开发业务】

-END- 

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 224,619评论 6 522
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 96,163评论 3 402
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 171,789评论 0 366
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 60,911评论 1 300
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 69,920评论 6 399
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 53,410评论 1 314
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 41,788评论 3 428
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 40,762评论 0 279
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 47,293评论 1 324
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 39,328评论 3 345
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 41,456评论 1 354
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 37,063评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,775评论 3 337
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 33,231评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 34,363评论 1 275
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 49,995评论 3 381
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 46,519评论 2 365

推荐阅读更多精彩内容