- 移动开发
- UI组件
【免费下载链接】react-native-gesture-handler
Declarative API exposing platform native touch and gesture system to React Native.
本篇技术指南围绕react-native-gesture-handler中的Gesture对象展开,它是新版声明式手势 API 的入口:通过Gesture.Tap()、Gesture.Pan()等工厂方法创建手势实例,再交给GestureDetector绑定到视图,并通过Gesture.Race()、Gesture.Simultaneous()、Gesture.Exclusive()组合出复杂的交互逻辑。读完本文,你将掌握该 API 的全部工厂方法、链式配置方式、三种组合语义的底层原理,以及它在当前仓库中已被标注弃用、如何平滑迁移到 v3 hook 式 API 的完整路径。
Gesture 对象是什么
Gesture是允许你创建和组合手势的核心对象。它本身不是一个视图组件,而是描述"手势应该如何被识别"的配置对象:创建出的手势实例不携带任何回调,需要后续通过链式方法(如.onStart()、.enabled())逐步完善,最终作为gesture属性传给GestureDetector,由后者把手势绑定到具体的视图上。
最小用法示例(源自 gesture.md 的 Reference 部分):
import { GestureDetector, Gesture } from 'react-native-gesture-handler'; function App() { // 创建一个 Tap 手势实例(默认配置、无回调) const tap = Gesture.Tap(); return ( <GestureDetector gesture={tap}> <Animated.View /> </GestureDetector> ); }在当前仓库源码中,Gesture对象的工厂方法实现在 gestureObjects.ts:每个方法都返回对应手势类的全新实例。抽象基类Gesture与实现类BaseGesture定义在 gesture.ts,它通过toGestureArray()、initialize()、prepare()三个抽象方法,为"创建与更新 handler"提供统一接口——无论是单个手势还是组合手势,最终都会以相同的方式接入原生侧。
创建基础手势的工厂方法
Gesture提供了 10 个工厂方法,每个方法都"创建对应手势类型的新实例,使用其默认配置且不附加任何回调"。下表汇总了各方法的语义与其在源码中的对应类:
| 工厂方法 | 对应手势类 | 手势类型 | 识别语义 |
|---|---|---|---|
Gesture.Tap() | TapGesture | 离散 | 识别一次或多次点击 |
Gesture.Pan() | PanGesture | 连续 | 识别拖拽(panning)并持续跟踪位移 |
Gesture.LongPress() | LongPressGesture | 离散 | 视图被按住足够长时间后激活 |
Gesture.Fling() | FlingGesture | 离散 | 移动足够快时激活(甩动/轻扫) |
Gesture.Pinch() | PinchGesture | 连续 | 识别双指捏合,跟踪两指距离以缩放内容 |
Gesture.Rotation() | RotationGesture | 连续 | 识别旋转并持续跟踪角度变化 |
Gesture.Hover() | HoverGesture | 连续 | 识别鼠标或触控笔在视图上方的悬停 |
Gesture.ForceTouch() | ForceTouchGesture | 连续(仅 iOS) | 跟踪触摸压力(仅部分 iOS 设备) |
Gesture.Manual() | ManualGesture | 手动 | 无特定激活标准与事件数据,状态需用状态管理器手动控制 |
Gesture.Native() | NativeGesture | 桥接 | 让其他原生触摸组件参与 RNGH 手势系统 |
以上语义均可在 gestureObjects.ts 的 JSDoc 注释中找到原始出处;例如Manual的说明明确写着"没有任何特定激活标准或事件数据,必须使用状态管理器手动控制其状态,并且当所有指针离开屏幕时它不会失败"。
每个手势实例在构造时都会绑定对应的原生 handler 名称,例如TapGesture的handlerName为'TapGestureHandler'(见 tapGesture.ts),PanGesture为'PanGestureHandler'(见 panGesture.ts),这是前端手势对象与原生识别器(iOS 侧如RNTapHandler.m,Android 侧如TapGestureHandler.kt)对接的桥梁。
链式配置:以 Tap 与 Pan 为例
工厂方法返回的手势实例支持链式配置(每个方法返回this)。以TapGesture为例,在 tapGesture.ts 中可以确认以下可配置项及其默认值:
minPointers(n):激活所需的最少触点数量,默认 1;numberOfTaps(count):激活所需的点击次数,默认 1(用于实现双击、三击);maxDistance(dist):点击过程中手指允许移动的最大距离(单位:pt);maxDuration(ms):从按下到松开的最大时间,默认 500ms;maxDelay(ms):多次点击之间允许的最大间隔,默认 500ms;maxDeltaX(delta)/maxDeltaY(delta):点击过程中沿 X / Y 轴允许的最大位移。
PanGesture的配置项(见 panGesture.ts)则更加丰富,典型的如:
activeOffsetX/Y(value):手指在此范围内移动不会激活手势(传负数表示负方向阈值,也可传[start, end]数组);failOffsetX/Y(value):激活前手指移出该范围则手势失败;minDistance(distance)/minVelocity(velocity)/minVelocityX/Y:激活所需的最小位移或最小速度;minPointers/maxPointers:参与手势的触点数量范围;averageTouches(value)(仅 Android):将位移计算从"以先落下的手指位置为准"改为"以所有活动触点平均位置为准",与 iOS 默认行为对齐;enableTrackpadTwoFingerGesture(value)(仅 iOS):在支持触控板的设备(如 iPad)上启用双指手势;activateAfterLongPress(duration):Pan 允许激活前必须完成的 LongPress 时长(毫秒)。
使用示例:
const pan = Gesture.Pan() .minDistance(10) .activeOffsetX([-20, 20]) .onStart(() => { console.log('pan started'); });所有手势共有的配置与回调
无论何种手势类型,BaseGesture(定义于 gesture.ts)都提供了一套通用的链式方法:
- 状态回调:
onBegin(handler 开始接收触点、处于BEGAN状态)、onStart(手势被识别、进入ACTIVE状态)、onEnd(仅当此前处于ACTIVE状态时,手势结束进入END状态触发)、onFinalize(手势成功结束或识别失败时都会触发); - 触点级回调:
onTouchesDown/onTouchesMove/onTouchesUp/onTouchesCancelled,注册这些回调会自动置位needsPointerData,让 handler 分析触点事件流; - 通用配置:
enabled(boolean)、shouldCancelWhenOutside(boolean)、hitSlop(hitSlop)、activeCursor(cursor)(仅 Web,支持 CSS cursor 值,默认"auto"); - 关系配置:
simultaneousWith/requireToFail/blocksHandlers,用于跨组件手势交互; withRef(ref):给手势对象绑定 ref,以便与旧版 API 互通。
连续手势(Pan/Pinch/Rotation 等)还额外支持onUpdate(持续更新)与onChange(基于位移增量的变化回调)。以 Pan 为例,其onChange事件载荷中的changeX/changeY由 panGesture.ts 中的changeEventCalculator计算:首次回调取当前translationX/Y,后续回调取与上一次位移的差值。
组合手势:Race / Simultaneous / Exclusive
Gesture还提供三种静态组合方法,它们都接收一个或多个手势作为参数,返回ComposedGesture类型的组合手势,可以像普通手势一样交给GestureDetector。
Gesture.Race(gesture1, gesture2, ...): ComposedGesture
创建一个由传入手势组合而成的新手势。其中只有一个手势可以变为活动状态,且对各手势的激活没有任何额外限制——第一个激活的手势会取消其余所有手势。
典型场景:把Pan与LongPress放进Race,用户既可以快速拖动,也可以在按住后触发长按菜单,先识别出谁就赢。
Gesture.Simultaneous(gesture1, gesture2, ...): ComposedGesture
创建一个由传入手势组合而成的新手势。其中所有手势都可以同时变为活动状态,互不取消。
典型场景:双指缩放的Pinch与单指拖动的Pan同时进行,既缩放又平移。
Gesture.Exclusive(gesture1, gesture2, ...): ComposedGesture
创建一个由传入手势组合而成的新手势。其中只有一个手势可以变为活动状态,且第一个手势优先级最高,依次递减。当所有手势都处于BEGAN状态、且第二个手势的激活条件已满足时,它不会立即激活,而是等待第一个手势失败(随后自己激活)或第一个手势先激活(随后自己被取消)。
它特别适合组合激活条件相似的手势——例如在同一个组件上同时响应单击与双击:没有Exclusive时,用户每次点击都会让单击手势激活,从而取消双击手势;而Exclusive(doubleTap, singleTap)会先等待单击手势失败后再激活双击手势。
三种组合的源码级实现原理
三种组合语义的具体实现位于 gestureComposition.ts:
ComposedGesture(Race的返回类型)通过prepareSingleGesture把组合级的simultaneousWith/requireToFail关系合并到每个成员手势的 config 中,并用relationsSnapshot保存组合前的原始关系快照。这样即使组合手势在每次渲染时被重建,也不会让关系数组不断累积上一次渲染的引用(内存泄漏问题,见源码注释引用的 issue #3763),同时保留原始引用以便在react-freeze解冻后重新解析关系(issue #4238);SimultaneousGesture.prepare()的逻辑很巧妙:对数组中的每个手势,取"除它自己之外的所有手势扁平化后的列表",让每个手势与其余所有手势互为simultaneousWith。通过排除自身,避免了把手势与自己做"同时"关系——这一点对内部嵌套了ExclusiveGesture的组合尤为重要(见 gestureComposition.ts);ExclusiveGesture.prepare()先把手势数组转换为分组数组(组合手势会被toGestureArray()展开),然后让每一组都等待其前面所有组失败:requireToFail列表在循环中不断累加,从而实现"优先级按参数顺序递减"的效果(见 gestureComposition.ts)。
此外,仓库中还存在一份专门讲解组合关系的文档 gesture-composition.md,以及覆盖"同一组件用组合 hooks、跨组件用关系属性"决策树的 overview.mdx,可与本文互相印证。
性能优化:用 useMemo 包裹手势配置
原文档在 Remarks 一节特别强调:建议用useMemo包裹手势配置,因为它能减少 Gesture Handler 在更新手势时底层要做的工作。示例(沿用原文档):
import React from 'react'; function App() { const gesture = React.useMemo( () => Gesture.Tap().onStart(() => { console.log('Number of taps:', tapNumber + 1); setTapNumber((value) => value + 1); }), [tapNumber, setTapNumber] ); // ... }这一建议在源码中也有对应设计:BaseGesture构造时会分配全局递增的gestureId(见 gesture.ts)。当配置被useMemo稳定缓存时,依赖不变则配置不重建、gestureId不变;只有当依赖变化、配置重建时gestureId才会改变,从而触发 handler 的原生侧更新。换句话说,useMemo让 RNGH 可以跳过"配置未变化"时的大量重复更新工作。
当前仓库中的弃用状态与迁移路径
需要特别指出:在当前仓库中,整套Gesture对象 API 已被标注为弃用(deprecated),后续版本将移除。gestureObjects.ts顶部的 JSDoc 明确写道:
Gesturebuilder API is deprecated and will be removed in a future version of Gesture Handler. Please migrate to the new, hook-based API.
各工厂方法及组合方法均有对应的 v3 hook 替代,且均有同名实现文件可查证:
| 旧版 API | 新版 hook API(源码位置) |
|---|---|
Gesture.Tap() | useTapGesture(useTapGesture.ts) |
Gesture.Pan()/Pinch()/Rotation()/Fling()/LongPress()/Hover()/Manual()/Native()/ForceTouch() | 对应usePanGesture、usePinchGesture、useRotationGesture、useFlingGesture、useLongPressGesture、useHoverGesture、useManualGesture、useNativeGesture、useForceTouchGesture(位于 v3/hooks/gestures 目录下) |
Gesture.Race() | useCompetingGestures(useCompetingGestures.ts) |
Gesture.Simultaneous() | useSimultaneousGestures(useSimultaneousGestures.ts) |
Gesture.Exclusive() | useExclusiveGestures(useExclusiveGestures.ts) |
组合语义在三者之间一一对应:useCompetingGestures同样遵循"第一个激活的手势取消其余手势",useSimultaneousGestures允许全部同时激活,useExclusiveGestures则按参数顺序决定优先级(见 overview.mdx)。新 API 的相关文档位于docs/gestures/(如 use-tap-gesture.mdx)与docs/composition/(如 use-competing-gestures.mdx、use-simultaneous-gestures.mdx、use-exclusive-gestures.mdx);旧版各手势的完整 API 参考仍保留在 legacy-gestures 目录下(如 tap-gesture.md、pan-gesture.md)。若仓库中同时使用了 v3 hook 与旧版 API,请注意 overview.mdx 中强调的限制:手势关系无法在 hook 式 API 与旧 API 之间跨体系设置。
总结
Gesture对象是 RNGH 声明式手势体系的基石:10 个工厂方法覆盖了从点击、拖拽、长按到捏合、旋转、悬停、压力触控的完整手势类型,链式配置让每个手势的激活标准、回调与关系都可精确控制,而Race、Simultaneous、Exclusive三种组合语义则以极小的心智负担实现了复杂交互编排。对于新项目,建议直接采用 v3 的 hook 式 API(useTapGesture、useCompetingGestures等);对于维护中的旧代码,本指南给出的对应关系表可作为逐步迁移的对照依据。
- 移动开发
- UI组件
【免费下载链接】react-native-gesture-handler
Declarative API exposing platform native touch and gesture system to React Native.
相关推荐
react-native-gesture-handler 的 Gesture 对象:手势创建、组合与 v3 Hook API 迁移指南
react native gesture handler 的 Gesture 对象:手势创建、组合与 v3 Hook API 迁移指南 Gesture 是 re
移动开发UI组件电费到底花哪了?Home Assistant 家庭能源管理一篇就够
电费到底花哪了?Home Assistant 家庭能源管理一篇就够 上个月电费 487 元,空调单独贡献了多少?没人答得上来。用 Home Assistant
文档教程智能家居物联网Carthage命令速查手册:update、bootstrap、build等10个核心命令详解
Carthage命令速查手册:update、bootstrap、build等10个核心命令详解 Carthage 是一款简单、去中心化的 Cocoa 依赖管理工
移动开发UI组件
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考