GestureGroup 自学指南:一次搞懂组合手势(三种模式全解析)
在 ArkUI 里,单个手势(点击、长按、滑动、缩放…)已经够好用,但一旦你要做这种交互: 就会发现仅靠 本文定位就是一篇可以直接发社区的实战向自学笔记,按这几个问题展开: 官方一句话定义: 核心接口只有一个: 参数说明: 特点: 典型场景: 特点: 典型场景: 特点: 一旦其中一个手势识别成功: 典型场景: 含义: 常见情况: 在实际项目里, 先看一下官方示例的完整版,然后逐块拆解思路。 用户先长按卡片: 长按识别完成后,才会开始识别拖动: 必须用 Sequence 模式 想要“长按 → 再拖动”这样的链式交互,最自然就是顺序识别。 位移计算通过“起始位置 + 偏移量”完成 只在 PanGesture 的 onActionEnd 收尾 这是 用 ✅ 小结: 这里给两个思路示例,你可以按需带入自己项目。 伪代码示意: 思路: 给同一个 Item 区域同时注册: 伪代码示意: 最后快速帮你盘一遍重点: 基本语法 mode 选型建议 Tap + 双击 必须注意顺序 Sequence 模式 only 最后一个 onActionEnd 生效 onCancel 用来兜底清理状态 到这里, 用熟之后,你会发现:组合手势本身没那么难,难的是想清楚交互规则,而 GestureGroup 正好帮你把“规则”变成清晰的代码结构。TapGesture / PanGesture 这些基础手势不太好管理——这时候就轮到主角 GestureGroup 登场了。GestureMode 到底怎么选?onCancel 在真实项目中有什么用?一、GestureGroup 是什么?
GestureGroup 用来把多个基础手势组合在一起,根据指定的识别模式统一管理。
SystemCapability.ArkUI.ArkUI.FullGestureGroup(mode: GestureMode, ...gesture: GestureType[])mode: GestureMode(必填)
组合手势的“识别策略”,即三种模式:Sequence / Parallel / Exclusive...gesture: GestureType[](可选)TapGesture、LongPressGesture、PanGesture 等)⚠️ 官方特别说明:
当一个组件要同时支持 单击 + 双击 时,必须把双击放前面,单击放后面,才能正确识别。二、GestureMode 三种模式,搞清区别就成功一半
GestureMode 枚举定义了组合手势的识别方式:enum GestureMode {
Sequence, // 顺序识别
Parallel, // 并发识别
Exclusive // 互斥识别
}2.1 Sequence:顺序识别(默认值)
按照注册顺序,一个一个识别。前面的失败,后面的都不会触发。
onActionEnd 事件。2.2 Parallel:并发识别
所有手势同时识别,互不干扰。
PinchGesture(缩放)又要识别 RotateGesture(旋转);2.3 Exclusive:互斥识别
所有手势一起识别,谁先成功,就“赢”,其余都视为失败。
三、事件:onCancel 什么时候触发?
GestureGroup 自己只有一个事件:onCancel(event: () => void)onCancel 通常用来做:四、官方示例拆解:长按 + 拖动(顺序识别)
// xxx.ets
@Entry
@Component
struct GestureGroupExample {
@State count: number = 0;
@State offsetX: number = 0;
@State offsetY: number = 0;
@State positionX: number = 0;
@State positionY: number = 0;
@State borderStyles: BorderStyle = BorderStyle.Solid;
build() {
Column() {
Text('sequence gesture\n' +
'LongPress onAction:' + this.count + '\n' +
'PanGesture offset:\nX: ' + this.offsetX + '\n' +
'Y: ' + this.offsetY)
.fontSize(15)
}
.translate({ x: this.offsetX, y: this.offsetY, z: 0 })
.height(150)
.width(200)
.padding(20)
.margin(20)
.border({ width: 3, style: this.borderStyles })
.gesture(
// 顺序识别:长按成功后,才会识别拖动
GestureGroup(GestureMode.Sequence,
LongPressGesture({ repeat: true })
.onAction((event?: GestureEvent) => {
if (event && event.repeat) {
this.count++
}
console.info('LongPress onAction')
}),
PanGesture()
.onActionStart(() => {
this.borderStyles = BorderStyle.Dashed
console.info('pan start')
})
.onActionUpdate((event?: GestureEvent) => {
if (event) {
this.offsetX = this.positionX + event.offsetX
this.offsetY = this.positionY + event.offsetY
}
console.info('pan update')
})
.onActionEnd(() => {
this.positionX = this.offsetX
this.positionY = this.offsetY
this.borderStyles = BorderStyle.Solid
console.info('pan end')
})
)
.onCancel(() => {
console.info('sequence gesture canceled')
})
)
}
}4.1 交互效果总结
count 会累加;offsetX / offsetY 更新);onCancel。4.2 关键点解读
GestureGroup(GestureMode.Sequence, LongPressGesture(...), PanGesture())this.offsetX = this.positionX + event.offsetX
this.offsetY = this.positionY + event.offsetYpositionX / positionY 记录上一次拖动结束的位置;event.offsetX / offsetY 是当前手势中的增量;onActionEnd;五、经典场景:单击 + 双击共存怎么写?
GestureGroup 出现频率最高的需求之一。5.1 思路
TapGesture 写两个手势:count: 2 表示双击;count: 1 表示单击;GestureGroup(GestureMode.Sequence, 双击, 单击);5.2 示例代码
@Entry
@Component
struct TapGestureGroupDemo {
@State singleCount: number = 0;
@State doubleCount: number = 0;
build() {
Column() {
Text(`单击次数:${this.singleCount}`)
.fontSize(16)
Text(`双击次数:${this.doubleCount}`)
.fontSize(16)
.margin({ bottom: 12 })
Text('点击这个区域测试单击/双击')
.fontSize(18)
.padding(20)
.backgroundColor('#EEEEEE')
.borderRadius(12)
.gesture(
GestureGroup(
GestureMode.Sequence,
// 一定要把双击放前面!
TapGesture({ count: 2 })
.onAction(() => {
this.doubleCount++;
console.info('double tap');
}),
TapGesture({ count: 1 })
.onAction(() => {
this.singleCount++;
console.info('single tap');
})
)
)
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
}
}六、Parallel / Exclusive 模式实战思路示例
6.1 Parallel:缩放 + 旋转同时识别
Shape()
.width(200)
.height(200)
.gesture(
GestureGroup(GestureMode.Parallel,
PinchGesture()
.onActionUpdate(e => {
// 根据 e.scale 处理缩放
}),
RotationGesture()
.onActionUpdate(e => {
// 根据 e.angle 处理旋转
})
)
)6.2 Exclusive:滑动删除 vs 点击打开二选一
PanGesture(水平滑动触发删除);TapGesture(点击进入详情);GestureGroup(GestureMode.Exclusive, PanGesture, TapGesture);Row()
.width('100%')
.height(60)
.gesture(
GestureGroup(GestureMode.Exclusive,
PanGesture({ direction: PanDirection.Horizontal })
.onActionEnd(e => {
// 滑到一定距离后,触发删除
}),
TapGesture({ count: 1 })
.onAction(() => {
// 打开详情页
})
)
)七、GestureGroup 使用小结 & 常见坑
.gesture(
GestureGroup(GestureMode.Sequence | Parallel | Exclusive, 手势1, 手势2, ...)
.onCancel(() => { ... })
)onActionEnd 里做收尾逻辑;onActionEnd 不同:onCancel 是“被打断”的收尾;GestureGroup 的核心思路和常见用法基本都过了一遍。建议你:Parallel / Exclusive 把原来复杂的 if/else 手势逻辑慢慢收敛到 GestureGroup 上。