- 移动开发
- UI组件
【免费下载链接】react-native-gesture-handler
Declarative API exposing platform native touch and gesture system to React Native.
导读:本文围绕 react-native-gesture-handler 的
PanGestureHandler(1.x 版本 API),系统讲解连续型手势识别原理、自定义激活/失败判定条件、多指平移的平台差异与统一方案,以及完整的事件数据字段。你将学会用声明式组件在 iOS 与 Android 上实现可复制的拖拽交互,并理解minDist、activeOffset、failOffset等配置的底层判定逻辑,为升级到 v3 的Gesture.Pan()打下基础。
概述:连续手势的识别与跟踪
PanGestureHandler是一个连续型(continuous)手势处理器,用于识别平移(拖拽)手势并持续跟踪其移动轨迹。
- 当手指放到屏幕上并移动一定的初始距离后,处理器进入 ACTIVE 状态(参见 state 文档);
- 通过配置可以指定最小初始移动距离、仅检测垂直/水平方向平移,以及激活所需的触点数(支持多指滑动);
- 在手势进行中,
onGestureEvent回调会持续触发,提供从起点开始的 XY 位移以及当前瞬时速度等关键信息。
实现层面,该处理器在 iOS 基于UIPanGestureRecognizer,在 Android 基于 PanGestureHandler.kt。在当前仓库中,对应 iOS 的具体实现位于 RNPanHandler.m,其内部使用了一个名为RNBetterPanGestureRecognizer的UIPanGestureRecognizer子类来承载自定义激活判定逻辑。
注意:在仓库源码中(如 PanGestureHandler.ts),
PanGestureHandler组件已被标记为@deprecated,官方推荐使用新版 APIGesture.Pan()。但 1.x 文档中的这套属性与事件数据模型,在新版Gesture.Pan()中依然原样保留,理解本页内容可以直接迁移到 v3 用法。
自定义激活判定(Custom activation criteria)
PanGestureHandler暴露了一系列属性,用于定制处理器在识别手势时的激活(activate)与失败(fail)判定标准。
其核心规则是:
- 当设置了多个判定属性时,
PanGestureHandler要求全部条件同时满足才会成功激活; - 只要任意一个条件被突破(overstepped),就会判定识别失败。
规则示例
- 若同时设置
minDeltaX与minDeltaY(各 20),则手指必须在 X 与 Y 两个轴上都移动 20 点,处理器才会激活;- 若同时设置
maxDeltaX与maxDeltaY(各 20)以及minDist(23),当手指沿 X 轴移动 20 点、沿 Y 轴移动 0 点时,即便 Y 轴方向仍在允许范围内,处理器也会判定失败。
这一“全部满足才激活、任一越界即失败”的语义,在 Android 侧由 PanGestureHandler.kt 的shouldActivate()与shouldFail()两个方法直接实现;在 iOS 侧则由 RNPanHandler.m 的shouldActivateUnderCustomCriteria/shouldFailUnderCustomCriteria完成对应判定。可以看到两个平台都采用“逐条件短路判断”的方式,任一条件命中即返回YES/true。
多指平移处理(Multi touch pan handling)
如果你的应用依赖多指平移,需要了解平台默认行为的差异,以及在必要时如何统一。
差异核心在于事件中位移(translation)属性的计算方式:
- iOS默认行为:屏幕上放置多根手指时,系统会视作只有一个指针,其位置为所有指针的质心(average position / center of mass)。这一行为同样适用于许多原生组件(即使它们并非主要用于多指交互,例如
UIScrollView)。 - Android / 原生组件(如 scroll view、pager views、drawers)的默认行为不同:不再把“所有手指的质心”作为主导指针,而是取最后放置的那根手指作为主导指针。这一行为可以通过 Android 上的
avgTouches标志修改。
从 Android 实现可以看到,PanGestureHandler.kt 的源码注释明确说明了这一差异:Android 上多指平移时默认只考虑最后放置的指针,而 iOS 会将各指针位置求平均;averageTouches属性可将 Android 行为切换为 iOS 的“质心”模式。
值得注意:在 Android 和 iOS 上,当额外的手指放到屏幕上时,translation 属性不会受影响——即使被跟踪指针的位置可能发生变化。因此大多数情况下可以安全依赖 translation,因为它只反映“与屏幕上手指数量无关、且数量随时间变化也不影响”的移动量。
若你需要跟踪“质心”虚拟指针、并希望在手指数量变化时也纳入其变化,可以使用事件中提供的相对位置或绝对位置:即x/y(相对视图)或absoluteX/absoluteY(相对根视图)。
属性(Properties)
PanGestureHandler首先继承基础 handler 类的公共属性集合(如enabled、shouldCancelWhenOutside、simultaneousHandlers、waitFor、hitSlop、onGestureEvent、onHandlerStateChange等)。以下是PanGestureHandler特有的属性:
minDist
手指(或多根手指)在处理器 激活 前需要移动的最小距离,以点(points)为单位。
- Android 上默认取系统的
scaledTouchSlop值(见 PanGestureHandler.kt);minDist平方后参与距离判定(distSq >= minDist * minDist)。 - iOS 侧在 RNPanHandler.m 中同样以平方形式存储(
minDistSq = dist * dist),与矢量长度平方比较。
minPointers
处理器 激活 前需要在屏幕上放置的手指数量,应为大于或等于 0 的整数。Android 默认值为1(见 PanGestureHandler.kt)。
maxPointers
当屏幕上放置的手指达到给定数量、而处理器尚未激活时,它会判定识别失败。应为大于或等于 0 的整数。Android 默认值为10(见 PanGestureHandler.kt)。
activeOffsetX
沿 X 轴(单位:点)的激活偏移范围:手指在此范围内移动不会激活处理器,移出该范围即激活。
- 范围可以以数组或单个数字给出。
- 若以数组给出,第一个值必须≤ 0,第二个值必须≥ 0。
- 若只给出单个数字
p:- 当
p ≥ 0时,使用范围(-inf, p); - 否则(
p < 0)使用范围(-p, inf)。
- 当
activeOffsetY
沿 Y 轴(单位:点)的激活偏移范围,语义与activeOffsetX完全一致:范围内不激活,移出即激活。数组/单数字的取值规则同上。
failOffsetY
当手指沿 Y 轴移出此范围(单位:点)且处理器尚未激活时,会判定识别失败。数组/单数字的取值规则同上。
failOffsetX
当手指沿 X 轴移出此范围(单位:点)且处理器尚未激活时,会判定识别失败。数组/单数字的取值规则同上。
avgTouches(仅 Android)
是否启用“多指质心”平移计算模式。默认false(取最后放置的手指为主导指针);设为true后切换为 iOS 式的“所有手指平均位置”模式。参见上文多指平移处理。
enableTrackpadTwoFingerGesture(仅 iOS)
启用受支持设备(例如带触控板的 iPad)上的双指手势。若未启用,手势需要“点击 + 拖拽”才能触发;启用后,在触控板上用两根手指滑动同样会触发该手势。
对应 iOS 实现将配置映射到allowedScrollTypesMask(见 RNPanHandler.m),启用时设为UIScrollTypeMaskAll。
类型定义与数组转换
从源码 PanGestureHandler.ts 可以确认,PanGestureHandler还支持以下在 1.x 文档未逐一列出的扩展属性:minVelocity、minVelocityX、minVelocityY(最小速度阈值)与activateAfterLongPress(长按后激活延迟,Android 单位为毫秒、iOS 单位为秒)。
在 JS 层,数组形式的activeOffsetX/Y、failOffsetX/Y会被拆分为activeOffsetXStart/activeOffsetXEnd等内部原生属性(见 transformPanGestureHandlerProps),再传递给原生端。同时 validatePanGestureHandlerProps 会在__DEV__下校验:数组首元素必须 ≤ 0、次元素必须 ≥ 0,且minDist与 offset 系列属性不可混用(会抛出明确的开发期错误)。
事件数据(Event data)
PanGestureHandler的事件载荷首先包含基础 handler 类的公共事件属性(如state、numberOfPointers)。以下是PanGestureHandler特有的事件字段:
translationX
平移手势沿 X 轴从手势开始以来累积的位移,单位为点。Android 侧的实时计算公式为lastX - startX + offsetX(见 PanGestureHandler.kt),其中offset用于在手指数量增减时保持位移连续。
translationY
平移手势沿 Y 轴从手势开始以来累积的位移,单位为点。计算逻辑同translationX。
velocityX
当前时刻平移手势沿 X 轴的瞬时速度,单位为点/秒。Android 侧通过VelocityTracker以 1000ms 窗口计算(见 PanGestureHandler.kt)。
velocityY
当前时刻平移手势沿 Y 轴的瞬时速度,单位为点/秒。
x
指针(手指,或存在多指时的主导指针)当前位置相对处理器所挂载视图的 X 坐标,单位为点。
y
指针当前位置相对处理器所挂载视图的 Y 坐标,单位为点。
absoluteX
指针当前位置**相对根视图(root view)**的 X 坐标,单位为点。当原视图可能因手势本身发生变换(transform)时,推荐使用absoluteX而非x。
absoluteY
指针当前位置相对根视图的 Y 坐标,单位为点。同样推荐在视图被变换时使用absoluteY而非y。
在 TypeScript 层,以上字段由 PanGestureHandlerEventPayload 完整定义,额外还包括可选的手写笔数据stylusData(iPad + Apple Pencil 场景)。
示例:用PanGestureHandler实现可拖拽圆形
下面是一个经典的“可拖拽圆圈”示例(原始示例来自 draggable 示例,仓库中该示例还展示了利用onHandlerStateChange在每次手势结束后记录lastOffset并重设translateX/Y的累积位移技巧):
const circleRadius = 30; class Circle extends Component { _touchX = new Animated.Value(windowWidth / 2 - circleRadius); _onPanGestureEvent = Animated.event([{ nativeEvent: { x: this._touchX } }], { useNativeDriver: true, }); render() { return ( <PanGestureHandler onGestureEvent={this._onPanGestureEvent}> <Animated.View style={{ height: 150, justifyContent: 'center', }}> <Animated.View style={[ { backgroundColor: '#42a5f5', borderRadius: circleRadius, height: circleRadius * 2, width: circleRadius * 2, }, { transform: [ { translateX: Animated.add( this._touchX, new Animated.Value(-circleRadius) ), }, ], }, ]} /> </Animated.View> </PanGestureHandler> ); } }要点说明:
PanGestureHandler包裹目标视图,onGestureEvent通过Animated.event将事件的x字段直接接入Animated.Value,配合useNativeDriver: true实现原生驱动的流畅位移;- 由于事件基于“从手势开始累积的位移”,拖拽类场景也可直接取
translationX/translationY(如仓库 draggable 示例 的做法),并在onHandlerStateChange中于手势结束时(oldState === State.ACTIVE)累加偏移量,从而支持连续多次拖拽。
常见问题与最佳实践
minDist与 offset 系列属性不可混用:源码校验(见 PanGestureHandler.ts)会在开发期抛出错误,提示使用activeOffsetX/Y或failOffsetX/Y代替,请遵循提示调整配置。- offset 数组的边界约束:
activeOffsetX、activeOffsetY、failOffsetX、failOffsetY的数组形式要求首元素 ≤ 0、次元素 ≥ 0,否则同样会在开发期报错。 - 速度激活:若希望“快速滑动即激活、慢速拖动不激活”,可配置
minVelocity/minVelocityX/minVelocityY(单位:点/秒),Android 与 iOS 实现均以绝对值比较速度(见 PanGestureHandler.kt 与 RNPanHandler.m)。 - 多指场景的位移稳定性:translation 不会因手指数量增减而跳变(原生端通过 offset 机制补偿),若需要跟踪“质心”位置变化,请改用
x/y或absoluteX/absoluteY。 - 升级到 v3:
PanGestureHandler已被标记为废弃,新代码应使用Gesture.Pan()(新版 API 中的GestureDetector),其配置项(minDist、activeOffsetX/Y、failOffsetX/Y、minPointers、maxPointers等)与事件字段(translationX/Y、velocityX/Y、x/y、absoluteX/Y)保持一致,迁移成本很低。
延伸阅读
- 基础 handler 公共属性与事件:
enabled、hitSlop、simultaneousHandlers、waitFor、onGestureEvent、onHandlerStateChange等公共配置。 - Handler 状态文档:
BEGAN、ACTIVE、FAILED、CANCELLED等状态的含义与流转。 - 可拖拽完整实现:draggable 示例
- 原生实现:Android PanGestureHandler.kt 与 iOS RNPanHandler.m
- 类型定义:PanGestureHandler.ts 与 PanGestureHandlerEventPayload
- 移动开发
- UI组件
【免费下载链接】react-native-gesture-handler
Declarative API exposing platform native touch and gesture system to React Native.
相关推荐
react-native-gesture-handler PanGestureHandler 拖拽手势完整指南:激活准则、事件数据与多指平移
react native gesture handler PanGestureHandler 拖拽手势完整指南:激活准则、事件数据与多指平移 本篇技术指南围绕
移动开发UI组件终极免费方案:VLC for Android如何彻底解决你的移动视频播放难题?
终极免费方案:VLC for Android如何彻底解决你的移动视频播放难题? 你是否曾因手机无法播放下载的电影而烦恼?是否遇到过网络视频卡顿、字幕不同步的问题
音视频移动开发Ice:如何 5 分钟整理 Mac 菜单栏,隐藏、拖拽、美化一次搞定
Ice:如何 5 分钟整理 Mac 菜单栏,隐藏、拖拽、美化一次搞定 Ice 是一款面向 macOS 的开源菜单栏管理工具,负责隐藏、重排和美化菜单栏图标,支持
桌面应用
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考