鸿蒙原生 ArkTS 布局方式之 Gesture 基础:TapGesture / LongPressGesture / PanGesture 实战
一、引言
手势交互是移动端应用最核心的用户输入方式。HarmonyOS NEXT 的 ArkUI 框架提供了完整的手势系统(Gesture),支持以声明式方式为组件绑定各种手势识别与响应逻辑。
三种基础手势:
| 手势 | 类名 | 触发条件 | 典型场景 |
|---|---|---|---|
| 点击 | TapGesture | 轻触后抬起 | 按钮、双击点赞 |
| 长按 | LongPressGesture | 按住超过指定时长 | 弹出菜单 |
| 拖拽 | PanGesture | 按住并平移 | 滑动列表、拖拽排序 |
二、核心原理
2.1 手势绑定方式
| 方式 | 方法 | 说明 |
|---|---|---|
| 普通手势 | .gesture() | 绑定一个手势,后绑定的覆盖先绑定的 |
| 多手势 | 多次.gesture() | 绑定多个,并行识别 |
| 优先手势 | .priorityGesture() | 优先识别,可阻断父组件手势 |
| 并行手势 | .parallelGesture() | 并行识别,不阻断父组件 |
2.2 手势生命周期
onActionStart(手势开始识别) ↓ onActionUpdate(连续手势持续触发,如 PanGesture) ↓ onActionEnd(正常结束)/ onActionCancel(被打断取消)2.3 GestureEvent 关键属性
event.fingerList[0].localX/Y// 触摸点相对组件的坐标event.offsetX/Y// 偏移增量(PanGesture)event.timestamp// 时间戳三、环境
MyApplication/ └── entry/src/main/ ├── ets/pages/GestureDemo.ets └── resources/base/profile/main_pages.json四、6 个实战场景
4.1 TapGesture 点击手势
count参数控制点击次数:1=单击,2=双击。
@Componentstruct TapGestureDemo{@StatetapCount:number=0;@StatelastTapX:number=0;@StatelastTapY:number=0;build(){Column(){// 单击区域Column(){Text('👆').fontSize(48)Text('点击此处').fontSize(18)Text('次数: '+this.tapCount)Text('坐标: ('+this.lastTapX+', '+this.lastTapY+')')}.width('100%').padding(24).backgroundColor('rgba(255,255,255,0.08)').borderRadius(16).alignItems(HorizontalAlign.Center).gesture(TapGesture({count:1}).onAction((event:GestureEvent)=>{this.tapCount++;this.lastTapX=Math.round(event.fingerList[0].localX);this.lastTapY=Math.round(event.fingerList[0].localY);}))// 双击区域Column(){Text('👆👆 双击此处').fontSize(15)}.padding(16).borderRadius(12).backgroundColor('rgba(255,215,0,0.1)').alignItems(HorizontalAlign.Center).gesture(TapGesture({count:2}).onAction(()=>{this.tapCount+=2;}))}}}要点:
count:1单击,手指抬起立即触发onActioncount:2双击,系统等待第二次点击,超时未发生则不触发event.fingerList[0].localX/Y获取相对组件的点击坐标(vp)
4.2 LongPressGesture 长按手势
duration控制触发长按的最小时长(ms)。
@Componentstruct LongPressGestureDemo{@StateisLongPressed:boolean=false;@StatepressDuration:number=0;privatereadonlyminPress:number=500;build(){Column(){Column(){Text(this.isLongPressed?'🟢 已触发!':'🔴 按住我 500ms').fontSize(16).fontColor(this.isLongPressed?'#4CAF50':Color.White)Text('时长: '+this.pressDuration+'ms')}.padding(24).borderRadius(16).backgroundColor(this.isLongPressed?'rgba(76,175,80,0.15)':'rgba(255,255,255,0.08)').alignItems(HorizontalAlign.Center).gesture(LongPressGesture({fingers:1,repeat:false,duration:this.minPress}).onAction((event:GestureEvent)=>{this.isLongPressed=true;this.pressDuration=event.timestamp;}).onActionEnd(()=>{this.isLongPressed=false;}))}}}要点:
onAction在按住达到duration时触发onActionEnd在手指抬起时触发,适合重置状态repeat: true可让长按每隔duration重复触发
4.3 PanGesture 拖拽手势
direction控制方向,distance控制最小触发距离(防误触)。
@Componentstruct PanGestureDemo{@StateoffsetX:number=0;@StateoffsetY:number=0;@StateboxColor:string='#5C8AFF';@StatedragStatus:string='等待拖拽';build(){Column(){Stack(){Column(){Text('⬡').fontSize(28)}.width(64).height(64).backgroundColor(this.boxColor).borderRadius(14).shadow({radius:8,color:'rgba(0,0,0,0.3)'}).gesture(PanGesture({fingers:1,direction:PanDirection.All,distance:5}).onActionStart(()=>{this.dragStatus='🔄 拖拽中';this.boxColor=randomColor();}).onActionUpdate((event:GestureEvent)=>{// event.offsetX/Y 是本次增量,需累加this.offsetX+=event.offsetX;this.offsetY+=event.offsetY;}).onActionEnd(()=>{this.dragStatus='✅ 结束';}).onActionCancel(()=>{this.dragStatus='❌ 取消';})).translate({x:this.offsetX,y:this.offsetY})}.width('100%').height(200).borderRadius(16).clip(true)Text(this.dragStatus).fontSize(13).margin({top:4})Button('重置').onClick(()=>{this.offsetX=0;this.offsetY=0;})}}}functionrandomColor():string{return`hsl(${Math.floor(Math.random()*360)}, 75%, 55%)`;}| 回调 | 时机 | 用途 |
|---|---|---|
onActionStart | 手指移动达到 distance | 初始化拖拽状态 |
onActionUpdate | 手指持续移动 | 更新位置(event.offsetX/Y 为增量) |
onActionEnd | 手指抬起 | 保存结果 |
onActionCancel | 手势被更高优先级打断 | 清理状态 |
要点:event.offsetX是相对于上一次回调的增量,需用this.offsetX += event.offsetX累加,再配合.translate()应用偏移。
4.4 多手势组合
同一组件可绑定多个手势——多次调用.gesture()。
.gesture(TapGesture({count:1}).onAction(()=>{/* 点击响应 */})).gesture(LongPressGesture({fingers:1,repeat:false,duration:400}).onAction(()=>{/* 长按响应 */}))多手势识别规则:
- TapGesture + LongPressGesture → 并行,点击触发 Tap,长按触发 LongPress
- TapGesture + PanGesture → 并行,轻触触发 Tap,滑动触发 Pan
- LongPressGesture + PanGesture → 互斥,达到 duration 后不再响应 Pan
4.5 手势参数对比
三个并排卡片展示单击/双击/长按的参数差异。
Row({space:10}){// 单击 count:1Column(){Text('👆 单击我')}.backgroundColor('rgba(21,101,192,0.3)').borderRadius(12).gesture(TapGesture({count:1}).onAction(()=>{/* 单击 */}))// 双击 count:2Column(){Text('👆👆 双击我')}.backgroundColor('rgba(233,30,99,0.3)').borderRadius(12).gesture(TapGesture({count:2}).onAction(()=>{/* 双击 */}))// 长按 800msColumn(){Text('⏱️ 长按我')}.backgroundColor('rgba(255,152,0,0.3)').borderRadius(12).gesture(LongPressGesture({duration:800}).onAction(()=>{/* 长按 */}))}| 手势 | 关键参数 | 示例 |
|---|---|---|
| TapGesture | count | 1(单击)/ 2(双击) |
| LongPressGesture | duration | 500ms / 800ms |
| PanGesture | direction,distance | All / 5vp |
4.6 边界限制拖拽
通过Math.max/Math.min约束偏移量,结合animateTo实现松手回弹。
@Componentstruct BoundedPanGestureDemo{@StatepanX:number=0;@StatepanY:number=0;privatereadonlymaxOffset:number=(200-50)/2;// (容器 - 方块) / 2build(){Stack(){Stack(){Column(){Text('⬡').fontSize(28)}.width(50).height(50).backgroundColor('#FFD700').borderRadius(12).translate({x:this.panX,y:this.panY}).gesture(PanGesture({fingers:1,direction:PanDirection.All,distance:3}).onActionUpdate((event:GestureEvent)=>{letnewX=this.panX+event.offsetX;letnewY=this.panY+event.offsetY;// 限制在 [-maxOffset, maxOffset] 范围内newX=Math.max(-this.maxOffset,Math.min(this.maxOffset,newX));newY=Math.max(-this.maxOffset,Math.min(this.maxOffset,newY));this.panX=newX;this.panY=newY;}).onActionEnd(()=>{// 弹性回中animateTo({duration:200,curve:Curve.Friction},()=>{this.panX=0;this.panY=0;});}))}.width(200).height(200).borderRadius(16).backgroundColor('rgba(156,39,176,0.2)').clip(true)}}}边界限制公式:Math.max(-max, Math.min(max, newValue))将值限制在[-max, max]内。
回弹动画:animateTo({ duration: 200, curve: Curve.Friction }, () => { this.panX = 0; }),Curve.Friction摩擦力曲线模拟真实减速回弹。
五、主页面整合
@Entry@Componentstruct GestureDemo{build(){Column(){Row(){Text('👆 Gesture 基础手势').fontSize(20)}.width('100%').height(56).backgroundColor('rgba(0,0,0,0.3)')Scroll(){Column(){TapGestureDemo()LongPressGestureDemo()PanGestureDemo()GestureGroupDemo()GestureParamsDemo()BoundedPanGestureDemo()Column(){Text('📖 要点总结').fontSize(16).fontColor('#FFD700')Text('1. TapGesture:count 控制次数(1=单击/2=双击),onAction 获取坐标。')Text('2. LongPressGesture:duration 控制时长,onAction 触发长按,onActionEnd 处理抬起。')Text('3. PanGesture:direction/ distance 控制方向与灵敏度。onActionUpdate 获取偏移增量。')Text('4. 多手势:多次 .gesture() 并行绑定。')Text('5. 回调:onActionStart / onActionUpdate / onActionEnd / onActionCancel。')Text('6. 边界:onActionUpdate 中用 Math.max/min 约束偏移,animateTo 实现回弹。')}.width('100%').padding(20).backgroundColor('rgba(0,0,0,0.25)').borderRadius(16)}.width('100%').padding(16)}.layoutWeight(1)}.width('100%').height('100%').linearGradient({direction:GradientDirection.Bottom,colors:[['#1a1a2e',0],['#16213e',0.5],['#0f3460',1]]})}}六、进阶技巧
6.1 手势冲突
父子组件都绑定了手势时,子组件手势优先响应。可通过以下方式调整:
// 父组件抢优先权.parentGesture(PanGesture().onActionUpdate(()=>{}))// 子组件声明并行.parallelGesture(TapGesture().onAction(()=>{}))6.2 手势与动画组合
.onActionEnd(()=>{if(Math.abs(this.offsetX)>100){animateTo({duration:300},()=>{this.offsetX=300;});}else{animateTo({duration:200,curve:Curve.Friction},()=>{this.offsetX=0;});}})6.3 视觉反馈建议
| 状态 | 反馈 |
|---|---|
| 点击按下 | 缩放 0.95 + 背景加深 |
| 长按触发 | 背景变色 |
| 拖拽中 | 放大 + 阴影增强 |
七、常见问题
Q1:双击时单击的 onAction 会触发吗?
A:不会。系统等待约 300ms 确认是否二次点击,双击只触发 count:2 回调。
Q2:如何同时支持点击和长按?
A:多次.gesture()分别绑定 TapGesture 和 LongPressGesture,两者并行识别。
Q3:PanGesture 的 distance 设多大?
A:推荐 5~10vp。太小易误触,太大响应迟钝。
Q4:event.offsetX 和 translate 的关系?
A:offsetX 是增量,需累加后用translate({ x: 累计值 })应用。
Q5:怎么松手回弹?
A:onActionEnd 中调用animateTo({ duration:200, curve:Curve.Friction }, () => { 归零 })。
八、总结
| 场景 | 技术 | 交互 |
|---|---|---|
| 1 | TapGesture 单击/双击 + 坐标获取 | ✅ |
| 2 | LongPressGesture 时长控制 | ✅ |
| 3 | PanGesture 四回调完整演示 | ✅ |
| 4 | 多手势组合(点击+长按) | ✅ |
| 5 | 单击/双击/长按参数对比 | ✅ |
| 6 | 边界限制 + 松手回弹 | ✅ |
核心要点:
TapGesture({count}) → onAction → 点击坐标 LongPressGesture({duration}) → onAction / onActionEnd → 长按 PanGesture({direction,dist}) → 四回调 → 拖拽 多次 .gesture() → 多手势共存 Math.max/min → 边界限制 animateTo → 松手回弹掌握这三种基础手势的绑定与回调,是构建流畅、自然交互体验的第一步。