- 移动开发
- UI组件
【免费下载链接】react-native-gesture-handler
Declarative API exposing platform native touch and gesture system to React Native.
Gesture是 react-native-gesture-handler 中用于创建与组合手势的核心对象:通过Gesture.Tap()、Gesture.Pan()等工厂方法生成具体手势实例,再通过Race、Simultaneous、Exclusive将其组合成复杂交互,最后交给GestureDetector挂载到视图上。本文以仓库中 version-2.x 版本文档的 gesture.md 为主体,结合 手势源码 与其 v3 Hook API 实现,完整梳理Gesture的 API 参考、组合语义、底层工作方式以及向新 API 迁移的路径,帮助你理解并准确使用这套声明式手势系统。
Gesture 是什么:声明式手势的入口对象
在 RNGH 2.x 的 API 中,Gesture是一个抽象对象,它封装了"创建手势"与"组合手势"两件事。正如文档开篇所述:Gestureis the object that allows you to create and compose gestures(Gesture是允许你创建和组合手势的对象)。
从源码看,gesture.ts 中定义了抽象类Gesture,它要求每个具体手势实现三个核心方法:
toGestureArray():返回手势数组,无论手势是用哪种对象创建的,都提供统一的接口供创建与更新 handler 使用;initialize():为手势实例分配handlerTag,并在设置了 ref 时同步ref.current;prepare():确保定义手势关系的属性值是数组,并做必要的预处理,用于在原生侧更新 handler 之前配置手势间的关系。
在此基础上,BaseGesture是所有具体手势的基类。构造函数中通过nextGestureId++为每个手势分配唯一的gestureId,正如源码注释所述:由于每个配置都有唯一 id,当useMemo的依赖不变时配置不会重建、id 保持不变;一旦 id 改变,就意味着手势配置已更新、需要同步到原生侧。
工厂方法全部集中在 gestureObjects.ts 的GestureObjects对象中,每个方法返回对应手势类的实例(如Tap: () => new TapGesture())。
基础手势工厂方法:一次创建一个手势
Gesture对象提供了若干无参工厂方法,每个都"以默认配置、无回调"的方式创建一个全新的手势实例:
| 工厂方法 | 返回的手势类型 | 手势类别 | 说明 |
|---|---|---|---|
Gesture.Tap() | TapGesture | 离散型 | 识别一次或多次点击,详见 tap-gesture.md |
Gesture.Pan() | PanGesture | 连续型 | 识别拖拽/平移并跟踪其位移 |
Gesture.LongPress() | LongPressGesture | 离散型 | 在视图上按压足够长时间后激活 |
Gesture.Fling() | FlingGesture | 离散型 | 在移动足够快时激活 |
Gesture.Pinch() | PinchGesture | 连续型 | 跟踪两指间距离,用于缩放 |
Gesture.Rotation() | RotationGesture | 连续型 | 识别旋转并跟踪其角度 |
Gesture.Hover() | HoverGesture | 连续型 | 识别鼠标/触控笔在视图上的悬停 |
Gesture.ForceTouch() | ForceTouchGesture | 连续型(仅 iOS) | 识别触摸压力 |
Gesture.Manual() | ManualGesture | 手动控制型 | 无特定激活条件,需用状态管理器手动控制 |
Gesture.Native() | NativeGesture | 原生组件型 | 让其他触摸处理组件参与 RNGH 手势系统 |
其中最典型的使用方式如下:
import { GestureDetector, Gesture } from 'react-native-gesture-handler'; function App() { const tap = Gesture.Tap(); return ( <GestureDetector gesture={tap}> <Animated.View /> </GestureDetector> ); }工厂方法创建的手势默认没有回调,需要继续链式调用onBegin、onStart、onUpdate等方法来配置行为。以 tapGesture.ts 为例,TapGesture的构造函数会自动调用shouldCancelWhenOutside(true),并允许通过minPointers、numberOfTaps、maxDistance、maxDuration、maxDelay、maxDeltaX、maxDeltaY等链式方法配置点击语义(例如numberOfTaps(2)即双击)。而 panGesture.ts 中的PanGesture则提供了activeOffsetX/Y、failOffsetX/Y、minDistance、minVelocity、averageTouches(Android)、enableTrackpadTwoFingerGesture(iOS)等连续型手势专属配置,并覆写了onChange以注入changeEventCalculator,从而在回调中额外提供changeX/changeY增量数据。
所有具体手势类最终都会注册对应的原生 handler 名称(例如TapGesture的handlerName为'TapGestureHandler'),这说明Gesture对象是 RNGH 原生手势系统在 JS 侧的声明式封装。
组合手势:让多个手势协同工作
除了创建单个手势,Gesture还提供了三种组合方法,用于在同一组件上表达多个手势之间的竞争或协作关系。它们都返回ComposedGesture类型,且接受可变数量的手势参数。
Gesture.Race(gesture1, gesture2, gesture3, ...)
由传入参数组成的手势。其中只有一个可以激活,且对手势的激活没有任何限制——最先激活的那个会取消其余所有手势。它等价于"挂载多个手势 handler 但彼此不定义simultaneousHandlers/waitFor关系"。
典型场景:组件既可拖拽又支持长按弹出选项,但长按激活后不希望组件再被拖动。此时用Gesture.Race(dragGesture, longPressGesture)即可让两者竞争,只有先激活的一方胜出,完整示例见 gesture-composition.md 的 Race 章节。
Gesture.Simultaneous(gesture1, gesture2, gesture3, ...)
由传入参数组成的手势。所有手势都可以同时激活,互不取消。它等价于为每个 handler 相互设置simultaneousHandlers属性。
典型场景:图片查看器中同时进行缩放、旋转与平移,示例:
const composed = Gesture.Simultaneous( dragGesture, Gesture.Simultaneous(zoomGesture, rotateGesture) );Gesture.Exclusive(gesture1, gesture2, gesture3, ...)
由传入参数组成的手势。只有一个可以激活,且第一个手势优先级最高、第二个次之、依次递减。当所有手势都处于BEGAN状态、第二个手势的激活条件已满足时,它不会立即激活,而是等待第一个手势失败后才激活(若第一个手势激活则第二个被取消)。
它特别适合组合激活条件相似的手势,例如在同一组件上同时响应单击与双击:若不使用Exclusive,单次点击时单击手势每次都会激活并取消双击手势;正确写法是Gesture.Exclusive(doubleTap, singleTap),让单击等待双击失败。
从源码看,gestureComposition.ts 中ComposedGesture的prepare()会遍历子手势并为其扩展simultaneousWith与requireToFail关系;SimultaneousGesture会为每个手势构造"除自身外其余所有子手势"的数组作为并行关系;ExclusiveGesture则让每个手势组等待它之前的所有手势组失败(requireToFail依次累积)。也就是说,三种组合在底层最终都转化为手势之间的simultaneousWith/requireToFail关系,与旧 API 的 ref 式配置在语义上等价。
跨组件手势关系:三种组合方法的补充
组合方法要求所有手势挂载在同一个GestureDetector下。若需要跨组件协调手势,BaseGesture还提供三个链式方法(定义见 gesture.ts 的addDependency相关实现):
simultaneousWithExternalGesture(...gestures):允许不同组件上的手势同时被识别;requireExternalGestureToFail(...gestures):延迟本手势的激活,直到传入的所有手势失败(或从未开始);blocksExternalGesture(...gestures):与requireExternalGestureToFail方向相反,常用于让ScrollView等待其下所有手势(如列表项内的缩放),即"许多手势阻塞一个手势"。
这三种方法分别对应config中的simultaneousWith、requireToFail、blocksHandlers三个关系字段,是跨组件场景下组合语义的延伸,具体示例同样见 gesture-composition.md。
基础配置方法:每个手势通用的链式选项
除具体手势类各自的专属配置外,BaseGesture还提供了一套所有手势通用的链式配置方法:
| 方法 | 作用 | 平台/默认值 |
|---|---|---|
enabled(boolean) | 是否分析触摸事件流 | 默认开启 |
shouldCancelWhenOutside(boolean) | 手指离开视图区域时取消或失败识别 | 默认false(Tap 默认true) |
hitSlop(HitSlop) | 控制视图区域内可用于开始识别手势的部分;传负数会按点数均匀收窄各边 | — |
activeCursor(cursor) | 手势激活时使用的 CSS cursor | 仅 Web,默认"auto" |
mouseButton(button) | 响应哪个鼠标按键,可用\|组合如MouseButton.LEFT \| MouseButton.RIGHT | 仅 Web 与 Android,默认LEFT |
runOnJS(boolean) | 为true时所有回调强制在 JS 线程执行 | 默认false |
withRef(ref) | 将手势对象写入 ref,用于与旧 API 互操作 | — |
withTestId(id) | 设置 testID,便于测试中查询 | — |
cancelsTouchesInView(boolean) | 激活时是否取消所附着原生 UI 组件(UIButton、UISwitch等)的触摸 | 仅 iOS,默认true |
回调方法方面,所有手势共享onBegin(进入BEGAN状态)、onStart(识别成功进入ACTIVE)、onEnd(结束且此前处于ACTIVE,带success参数)、onFinalize(无论识别成功或失败均会调用)、以及onTouchesDown/Move/Up/Cancelled四个底层触摸事件回调(设置它们会自动令config.needsPointerData = true)。连续型手势(ContinousBaseGesture)额外提供onUpdate与onChange(携带相对上一次事件的增量数据),并支持manualActivation(为true时手势不会自行激活,需借助状态管理器手动控制)。
useMemo 的必要性:减少底层更新开销
文档特别强调:考虑用useMemo包裹手势配置,因为它能减少 Gesture Handler 在更新手势时的底层工作量。原因在源码中非常清晰——BaseGesture构造函数为每个手势分配了唯一的gestureId,当useMemo的依赖不变时,手势配置不会重建、id 保持不变,底层无需重复创建或更新 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] ); // ... }这一建议在 GestureDetector 源码 中同样得到印证:GestureDetector通过useMemo(() => gestureConfig.toGestureArray(), [gestureConfig])缓存待挂载的手势数组,并在注释中明确"手势配置应使用 useMemo 包裹以防止不必要的重渲染"。
与 GestureDetector 的配合:挂载与更新
创建并配置好的手势必须交给GestureDetector(gesture属性)才能附着到视图上。GestureDetector的gesture属性可以接收任意基础手势或任意ComposedGesture(Race/Simultaneous/Exclusive),它负责基于手势配置创建和更新原生手势 handler——这也是它区别于旧手势 handler 组件的关键:借助手势组合,单个GestureDetector可以同时识别多个手势。
使用时有两点需要注意(详见 gesture-detector.md):
GestureDetector会使用子树中的第一个原生视图来识别手势,但若该视图仅用于分组子元素,可能被自动折叠(collapsable),导致手势失效;- 同一个手势实例不能复用在多个
GestureDetector中,否则会抛出错误。
此外,GestureDetector还提供三个 Web 专属属性:userSelect(可选"none" | "auto" | "text",默认"none")、touchAction(支持全部 CSStouch-action值,默认"none")、enableContextMenu(右键上下文菜单,默认false)。在源码中,这些属性会通过propagateDetectorConfig逐一传播到手势数组里每个基础手势的 config 上。
向前看:向 v3 Hook API 迁移
当前仓库中,Gesture构建器 API 已被标记为弃用:在 gestureObjects.ts 中,每个工厂方法与组合方法都带有@deprecated注释,指向对应的新 Hook API(如useTapGesture、usePanGesture、useCompetingGestures等)。
新的 v3 Hook API 位于 v3/hooks 目录,例如:
- useTapGesture.ts 取代
Gesture.Tap(),通过TapPropsMapping将公开属性名映射为内部配置名(如maxDistance→maxDist、maxDuration→maxDurationMs、maxDelay→maxDelayMs),并默认设置shouldCancelWhenOutside = true,与旧TapGesture构造函数行为一致; - useCompetingGestures.ts 取代
Gesture.Race(),内部调用useComposedGesture(ComposedGestureName.Race, ...gestures); - 同目录下的
useSimultaneousGestures与useExclusiveGestures分别对应Gesture.Simultaneous()与Gesture.Exclusive()。
因此,从 v2 迁移到 v3 时,核心心智模型不变:先创建单个手势,再组合成复合手势,最后挂载到检测器上;变化的只是创建方式从"对象工厂方法"变为"Hook 函数"。
小结
Gesture对象是 RNGH 2.x 声明式手势体系的中枢:Gesture.Tap()等工厂方法创建基础手势,Race/Simultaneous/Exclusive在同一组件内组合手势,simultaneousWithExternalGesture等三个方法跨组件建立关系,最终统一交由GestureDetector挂载。理解其gestureId机制与useMemo的配合、组合手势底层转化为simultaneousWith/requireToFail关系的实现方式,是写出高性能、行为可预期的手势代码的关键。面对正在进行的 v3 Hook API 迁移,这套组合与挂载的心智模型依然适用,可以平滑过渡。
- 移动开发
- UI组件
【免费下载链接】react-native-gesture-handler
Declarative API exposing platform native touch and gesture system to React Native.
相关推荐
BabelDOC 高级配置指南:4 类疑难 PDF 的完整解决方案
BabelDOC 高级配置指南:4 类疑难 PDF 的完整解决方案 你以为 PDF 翻译就是跑一条命令,结果要么输出乱版、要么扫描件发糊、要么同一个术语在几十页
移动开发UI组件Carthage命令速查手册:update、bootstrap、build等10个核心命令详解
Carthage命令速查手册:update、bootstrap、build等10个核心命令详解 Carthage 是一款简单、去中心化的 Cocoa 依赖管理工
移动开发UI组件react-native-gesture-handler 手势组合(Composed Gestures)完全指南:Race / Simultaneous / Exclusive 与跨组件手势关系
react native gesture handler 手势组合(Composed Gestures)完全指南:Race / Simultaneous / E
移动开发UI组件
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考