文章目录
- 前言
- 手势体系概览
- 点击与长按
- TapGesture——双击点赞
- LongPressGesture——长按弹出操作菜单
- 拖拽——PanGesture 实战图片拖拽
- 捏合缩放——PinchGesture 实战图片缩放
- 手势组合——并行与串行
- 并行组合——GestureGroup Parallel
- 串行组合——GestureMode.Sequence
- 写在最后
前言
你刷短视频双击点赞、看图捏合放大、长按弹出菜单——这些交互背后全是手势识别。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——长按弹出操作菜单
@StateisLongPressed:boolean=falseText('长按我试试').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 实战图片拖拽
这是最实用的手势之一,图片拖拽、列表滑动删除都用它:
@StateimageX:number=100@StateimageY:number=100Image($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.offsetXthis.imageY+=event.offsetY}).onActionEnd(()=>{console.info('拖拽结束')}))关键讲解:
direction: PanDirection.All——允许所有方向拖拽。也可以设PanDirection.Horizontal或PanDirection.Vertical限制单方向event.offsetX / offsetY——相对上一次回调的偏移量,不是绝对坐标。所以要用+=累加- 如果要限制拖拽范围,在
onActionUpdate里加边界判断就行
捏合缩放——PinchGesture 实战图片缩放
看大图时捏合缩放,几乎成了用户本能:
@StateimageScale:number=1@StatelastScale:number=1Image($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.5if(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.offsetXthis.itemY+=event.offsetY})))串行模式下,手势按顺序识别。长按成功后才识别拖拽,任何一个失败则整个组合失败。这就像 Android 的长按拖拽——得先按住不动,才能拖。
还有个GestureMode.Exclusive(互斥),多个手势谁先识别算谁的,另一个取消。日常用得不多,知道就行。
写在最后
手势识别是交互体验的基础。搞懂了 Tap、LongPress、Pan、Pinch 四个手势,90% 的交互场景都能覆盖。组合手势稍微复杂点,但理解了并行和串行的区别就不难了。
实战建议:先从单手势开始做,调通了再组合。别上来就搞三四个手势并行,出了冲突调试到崩溃。手势和动画搭配使用效果翻倍——拖拽加个animateTo弹回,捏合加个平滑过渡,体验直接上一个档次。