鸿蒙操作系统 ArkUI 组合手势实战:长按拖拽、双击缩放与互斥识别
一、背景说明
HarmonyOS 7 (API 26.0.1) Developer Beta 今日向开发者定向推送,ArkUI 在新版本中继续增强交互能力(如 Navigation 页面预加载、动画组件 C API 扩展),而无论版本怎么演进,手势交互始终是 ArkUI 应用侧最常用的能力。本文聚焦一个具体技术点:使用 ArkUI 的手势系统实现组合手势——长按后拖拽组件、双击与捏合并行缩放、互斥手势的优先级控制,附完整可运行代码。
二、技术原理
ArkUI 提供 6 种基础手势(TapGesture、LongPressGesture、PanGesture、PinchGesture、RotationGesture、SwipeGesture),通过 GestureGroup 可将多个基础手势组合,三种识别模式如下:
- Sequence(顺序识别):按注册顺序依次识别,前一个成功才能识别下一个;典型场景是"长按后拖拽"——只有长按达标后拖动才生效,等价于系统级拖拽事件。
- Parallel(并行识别):所有手势同时识别、互不影响;典型场景是"双击与捏合并行",双击切换开关状态的同时捏合调整缩放比例。
- Exclusive(互斥识别):手势同时参与竞争,任一成功则其余失败;典型场景是"左滑与长按只能命中其一"。
绑定方式有三种:gesture() 普通绑定(子组件优先)、priorityGesture() 父组件优先识别、parallelGesture() 父子同时触发。关键回调:onActionStart/onActionUpdate/onActionEnd,其中 GestureEvent.offsetX/offsetY 是相对手势起点的位移(vp),scale 是捏合比例增量。
数据流:手指触摸 → 手势识别器命中 → GestureGroup 按模式裁决 → 回调更新 @State → UI 声明式重渲染(translate/scale 属性)。
三、实现步骤
工程为 Stage 模型(API 12+),纯 UI 能力无需任何权限配置,module.json5 保持默认。
1. 长按后拖拽(Sequence 顺序识别)
实现一个"先长按进入拖拽态、再拖动移动位置"的卡片:
@Entry
@Component
struct DragCard {
@State offsetX: number = 0;
@State offsetY: number = 0;
@State positionX: number = 0; // 上一次拖拽结束的位置
@State positionY: number = 0;
@State dragging: boolean = false; // 拖拽态标记
@State pressCount: number = 0; // 长按触发计数
build() {
Column() {
Column() {
Text(this.dragging ? '拖拽中…' : '长按后拖动我')
.fontSize(16)
.fontColor('#FFFFFF')
}
.width(200)
.height(100)
.borderRadius(12)
.backgroundColor(this.dragging ? '#E5484D' : '#3A7BD5')
.justifyContent(FlexAlign.Center)
// 位移直接驱动 translate 属性
.translate({ x: this.offsetX, y: this.offsetY, z: 0 })
.shadow({ radius: this.dragging ? 20 : 5, color: '#33000000' })
.gesture(
// 顺序识别:长按成功后才允许拖动
GestureGroup(GestureMode.Sequence,
LongPressGesture({ duration: 500 })
.onAction(() => {
this.dragging = true;
this.pressCount++;
})
.onActionEnd(() => {
// 长按中途抬起视为取消拖拽
this.dragging = false;
}),
PanGesture()
.onActionStart(() => {
if (!this.dragging) { return; }
})
.onActionUpdate((event?: GestureEvent) => {
if (event && this.dragging) {
this.offsetX = this.positionX + event.offsetX;
this.offsetY = this.positionY + event.offsetY;
}
})
.onActionEnd(() => {
// 记录终点位置,供下次拖拽累计
this.positionX = this.offsetX;
this.positionY = this.offsetY;
this.dragging = false;
})
).onCancel(() => {
this.dragging = false; // 手势被系统打断时复位状态
})
)
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}
2. 双击与捏合并行(Parallel 并行识别)
双击在"放大/复原"间切换,捏合手势实时调整缩放,两者同时生效:
@Entry
@Component
struct ZoomCard {
@State scaleValue: number = 1.0;
@State lastScale: number = 1.0; // 捏合结束后的累计缩放
@State expanded: boolean = false;
build() {
Column() {
Column() {
Text('双击或捏合缩放')
.fontSize(15)
.fontColor('#FFFFFF')
}
.width(180)
.height(180)
.borderRadius(16)
.linearGradient({ angle: 135, colors: [['#3A7BD5', 0.0], ['#00D2FF', 1.0]] })
.justifyContent(FlexAlign.Center)
.scale({ x: this.scaleValue, y: this.scaleValue })
.gesture(
GestureGroup(GestureMode.Parallel,
// 双击:在 1.0 与 1.5 之间切换
TapGesture({ count: 2 })
.onAction(() => {
this.expanded = !this.expanded;
this.scaleValue = this.expanded ? 1.5 : 1.0;
this.lastScale = this.scaleValue;
}),
// 捏合:在上一状态基础上连续缩放
PinchGesture({ fingers: 2 })
.onActionUpdate((event?: GestureEvent) => {
if (event) {
this.scaleValue = Math.min(3, Math.max(0.5, this.lastScale * event.scale));
}
})
.onActionEnd(() => {
this.lastScale = this.scaleValue;
})
)
)
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}
3. 互斥识别(Exclusive)
同一组件上左滑与长按互斥,先满足条件者胜出:
@Entry
@Component
struct ExclusiveDemo {
@State result: string = '等待手势';
build() {
Column() {
Text(this.result)
.fontSize(16)
.width(240)
.height(80)
.textAlign(TextAlign.Center)
.borderRadius(12)
.backgroundColor('#F2F3F5')
.gesture(
GestureGroup(GestureMode.Exclusive,
PanGesture({ direction: PanDirection.Horizontal, distance: 20 })
.onActionStart(() => {
this.result = '左滑生效(长按被排除)';
}),
LongPressGesture({ duration: 800 })
.onAction(() => {
this.result = '长按生效(左滑被排除)';
})
)
)
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}
四、应用场景示例
实际业务中最常见的组合是"长按进入编辑态 + 拖动排序 + 点击打开",把本文第 1 节与单击手势放进一个并行组即可同时支持点击与拖拽:
.gesture(
GestureGroup(GestureMode.Parallel,
// 点击:非拖拽态下打开详情
TapGesture()
.onAction(() => {
if (!this.dragging) {
router.pushUrl({ url: 'pages/Detail' });
}
}),
// 长按拖拽:沿用第 1 节的 Sequence 组合
GestureGroup(GestureMode.Sequence,
LongPressGesture({ duration: 500 })
.onAction(() => { this.dragging = true; }),
PanGesture()
.onActionUpdate((event?: GestureEvent) => {
if (event && this.dragging) {
this.offsetX = this.positionX + event.offsetX;
this.offsetY = this.positionY + event.offsetY;
}
})
.onActionEnd(() => {
this.positionX = this.offsetX;
this.positionY = this.offsetY;
this.dragging = false;
})
)
)
)
五、技术要点
1. 手势冲突与优先级
- 子组件与父组件绑定了同类型手势时,子组件默认优先;需要父组件优先时用
priorityGesture,需要父子同时触发时用parallelGesture; Text、TextInput、Image等组件自带长按(选择/复制/拖拽)行为,与自定义LongPressGesture冲突:长按时间 <500ms 时系统优先响应长按,≥500ms 时优先响应组件自带拖拽,必要时用duration调节错开。
2. 状态复位
onActionEnd 只在手势正常结束时触发;手势被来电、弹窗等系统事件打断时会走 onCancel——拖拽态标记务必在 onCancel 中复位,否则组件会卡在"拖拽中"样式。
3. 常见问题
// 问题1:双击永远不触发(单击注册在双击之前吞掉了第一次点击)
// 错误:GestureGroup(GestureMode.Exclusive, TapGesture({count:1}), TapGesture({count:2}))
// 正确:双击注册在前,或两者放进同一 Sequence 组
GestureGroup(GestureMode.Sequence,
TapGesture({ count: 2 }).onAction(() => { /* 双击 */ }),
TapGesture({ count: 1 }).onAction(() => { /* 单击(双击失败后回退到单击) */ })
)
// 问题2:拖拽位置跳变——onActionUpdate 的 offset 是相对手势起点的增量
// 正确做法:offsetX = positionX + event.offsetX,onActionEnd 时更新 positionX
4. 性能建议
onActionUpdate每帧回调一次,回调内只做 @State 赋值,不要执行布局计算或日志;- 不需要响应手势的隐藏组件应避免绑定手势,识别器数量过多会增加命中测试开销;
- 高频拖拽场景可配合
translate属性动画(而非每帧修改布局参数)避免重新测量。
六、扩展建议
- 拖拽排序:把本文长按拖拽与
List/Grid的onItemMove或dragPreview结合,实现列表项长按排序; - 多指旋转:
RotationGesture与PinchGesture并行组合,可实现图片查看器的"捏合缩放 + 双指旋转"; - 自定义手势判定:API 12+ 提供
onGestureJudgeBegin回调,可在手势参与竞争前按sourceTool(手指/鼠标/触控笔)动态放行或拦截:
.gesture(
PanGesture()
.onGestureJudgeBegin((info: GestureJudgeResultInfo, event: BaseGestureEvent) => {
// 仅手指触发时才参与识别
return info.sourceTool === SourceTool.FINGER
? GestureJudgeResult.ACCEPT : GestureJudgeResult.REJECT;
})
)
结语
本文用 ArkUI 手势系统实现了顺序(长按拖拽)、并行(双击+捏合缩放)、互斥(左滑 vs 长按)三种组合手势的完整实现,核心代码约 200 行,均可直接编译运行。掌握 GestureGroup 的三种模式与父子组件手势优先级规则后,应用内的拖拽排序、图片查看、卡片交互等复杂手势都能组合出来,而不依赖任何三方库。
更多推荐



所有评论(0)