HarmonyOS7 手势识别:捏合、长按、拖拽,交互全靠它
文章目录
前言

你刷短视频双击点赞、看图捏合放大、长按弹出菜单——这些交互背后全是手势识别。HarmonyOS7 的手势系统比我想象的要完善,单手势、组合手势、优先级控制,该有的都有。但刚上手时确实容易晕:TapGesture 和 onClick 啥区别?PanGesture 方向怎么设?手势冲突怎么办?这篇把这些问题一次讲透。
手势体系概览
HarmonyOS7 提供的手势类型,一张表看全:
| 手势 | 类型 | 核心回调 | 典型场景 |
|---|---|---|---|
TapGesture | 点击 | onAction | 双击点赞、多次点击 |
LongPressGesture | 长按 | onAction / onActionEnd | 长按菜单、拖拽预备 |
PanGesture | 拖拽/平移 | onActionStart/Update/End | 图片拖拽、滑动删除 |
PinchGesture | 捏合 | onActionStart/Update/End | 图片缩放 |
RotationGesture | 旋转 | onActionStart/Update/End | 图片旋转 |
SwipeGesture | 快速滑动 | onAction | 翻页、切歌 |
绑定方式有三种:
gesture():普通绑定,子组件优先priorityGesture():优先绑定,父组件抢优先parallelGesture():并行绑定,父子同时响应
划重点:大部分情况用
gesture()就够了。只有父子组件手势冲突时才需要priorityGesture和parallelGesture。
点击与长按
TapGesture——双击点赞

你可能问了:不是有 onClick 吗?区别在于 onClick 只能响应单击,TapGesture 能识别多次点击:
Image($r('app.media.heart'))
.width(40)
.height(40)
.gesture(
TapGesture({ count: 2 })
.onAction(() => {
this.toggleLike()
})
)
count: 2 表示双击触发。还有个细节——绑定 TapGesture({ count: 2 }) 后,单击不会触发任何回调。如果你需要单击和双击共存,得用组合手势(后面讲)。
LongPressGesture——长按弹出操作菜单
@State isLongPressed: boolean = false
Text('长按我试试')
.fontSize(20)
.gesture(
LongPressGesture({ repeat: true })
.onAction((event: GestureEvent) => {
if (event.repeat) {
// 持续长按中,可以震动反馈
} else {
this.isLongPressed = true
}
})
.onActionEnd(() => {
this.isLongPressed = false
})
)

参数 repeat: true 很关键——设了之后,长按过程中会持续触发 onAction,event.repeat 为 true 表示是重复触发。适合做"长按加速"效果,比如长按数字递增。
不设 repeat 的话,只在长按达到阈值时触发一次,手指抬起触发 onActionEnd。
拖拽——PanGesture 实战图片拖拽
这是最实用的手势之一,图片拖拽、列表滑动删除都用它:
@State imageX: number = 100
@State imageY: number = 100
Image($r('app.media.photo'))
.width(80)
.height(80)
.position({ x: this.imageX, y: this.imageY })
.gesture(
PanGesture({ direction: PanDirection.All })
.onActionStart(() => {
console.info('开始拖拽')
})
.onActionUpdate((event: GestureEvent) => {
this.imageX += event.offsetX
this.imageY += event.offsetY
})
.onActionEnd(() => {
console.info('拖拽结束')
})
)
关键讲解:
direction: PanDirection.All——允许所有方向拖拽。也可以设PanDirection.Horizontal或PanDirection.Vertical限制单方向event.offsetX / offsetY——相对上一次回调的偏移量,不是绝对坐标。所以要用+=累加- 如果要限制拖拽范围,在
onActionUpdate里加边界判断就行
捏合缩放——PinchGesture 实战图片缩放
看大图时捏合缩放,几乎成了用户本能:
@State imageScale: number = 1
@State lastScale: number = 1
Image($r('app.media.photo'))
.width(300)
.height(200)
.scale({ x: this.imageScale, y: this.imageScale })
.gesture(
PinchGesture()
.onActionStart(() => {
this.lastScale = this.imageScale
})
.onActionUpdate((event: GestureEvent) => {
this.imageScale = this.lastScale * event.scale
// 限制缩放范围
if (this.imageScale < 0.5) this.imageScale = 0.5
if (this.imageScale > 3) this.imageScale = 3
})
.onActionEnd(() => {
this.lastScale = this.imageScale
})
)
为什么需要 lastScale?
event.scale 是相对手势开始时的缩放比例,不是相对上一次的。比如当前 scale 是 2,用户捏合让 event.scale 变成 1.5,那实际应该是 2 * 1.5 = 3,不是直接用 1.5。所以每次手势开始时记录当前 scale,update 时用 lastScale * event.scale 算真实值。
血泪教训:一定要限制缩放范围,不然用户一使劲缩到 0,图片就"没了";放大到 10 倍,直接卡死。
手势组合——并行与串行
单手势好理解,但实际需求经常要组合。比如"双击缩放 + 捏合缩放"、“长按后拖拽”。
并行组合——GestureGroup Parallel
双击缩放 + 捏合缩放同时生效:
.gesture(
GestureGroup(GestureMode.Parallel,
TapGesture({ count: 2 })
.onAction(() => {
animateTo({ duration: 200 }, () => {
this.imageScale = this.imageScale > 1 ? 1 : 2
})
}),
PinchGesture()
.onActionUpdate((event: GestureEvent) => {
this.imageScale = this.lastScale * event.scale
})
)
)
GestureMode.Parallel 表示两个手势互不干扰,各响各的。双击触发缩放切换,捏合触发连续缩放,两不误。
串行组合——GestureMode.Sequence
长按后拖拽——必须先长按触发,再拖拽才有效:
.gesture(
GestureGroup(GestureMode.Sequence,
LongPressGesture({ duration: 500 })
.onAction(() => {
// 长按触发,给出视觉反馈
this.itemSelected = true
}),
PanGesture()
.onActionUpdate((event: GestureEvent) => {
this.itemX += event.offsetX
this.itemY += event.offsetY
})
)
)
串行模式下,手势按顺序识别。长按成功后才识别拖拽,任何一个失败则整个组合失败。这就像 Android 的长按拖拽——得先按住不动,才能拖。
还有个 GestureMode.Exclusive(互斥),多个手势谁先识别算谁的,另一个取消。日常用得不多,知道就行。
写在最后
手势识别是交互体验的基础。搞懂了 Tap、LongPress、Pan、Pinch 四个手势,90% 的交互场景都能覆盖。组合手势稍微复杂点,但理解了并行和串行的区别就不难了。
实战建议:先从单手势开始做,调通了再组合。别上来就搞三四个手势并行,出了冲突调试到崩溃。手势和动画搭配使用效果翻倍——拖拽加个 animateTo 弹回,捏合加个平滑过渡,体验直接上一个档次。
更多推荐


所有评论(0)