HarmonyOS7 手势一多就冲突?GestureGroup 组合手势处理套路在这
文章目录前言基础手势快速过一遍GestureGroup三种组合模式手势冲突priorityGesture 来救场实战图片查看器手势开发的几点心得前言鸿蒙的手势 API 看着简单但真到项目里组合起来用坑比想象的多。缩放和平移打架、双击和单击互相吞事件、列表里的滑动手势被外层容器吃掉——这些问题我全踩过。这篇把手势系统从基础到组合到冲突处理全讲清楚最后做一个图片查看器把几种手势组合到一起。基础手势快速过一遍ArkUI 内置了十几种手势最常用的几个// 单击.gesture(TapGesture().onAction((){/* ... */}))// 双击.gesture(TapGesture({count:2}).onAction((){/* ... */}))// 长按.gesture(LongPressGesture().onAction((){/* ... */}))// 拖拽.gesture(PanGesture().onActionUpdate((event:GestureEvent){// event.offsetX, event.offsetY 是拖拽偏移量}))// 双指缩放.gesture(PinchGesture().onActionUpdate((event:GestureEvent){// event.scale 是缩放比例}))// 旋转.gesture(RotationGesture().onActionUpdate((event:GestureEvent){// event.angle 是旋转角度}))单独用都没啥问题坑出在组合上。GestureGroup三种组合模式GestureGroup是用来把多个手势打包的。它有三种模式理解这三种模式是搞定手势冲突的关键。并行模式ParallelGesture所有手势同时识别互不干扰。缩放和平移同时生效就用这个。.gesture(GestureGroup(GestureMode.Parallel,PinchGesture().onActionUpdate((event:GestureEvent){this.scalethis.baseScale*event.scale}),PanGesture().onActionUpdate((event:GestureEvent){this.offsetXthis.baseOffsetXevent.offsetXthis.offsetYthis.baseOffsetYevent.offsetY})))互斥模式ExclusiveGesture只有一个手势能赢。比如单击和双击放在一起双击优先识别。.gesture(GestureGroup(GestureMode.Exclusive,TapGesture({count:2}).onAction((){// 双击放大this.toggleZoom()}),TapGesture({count:1}).onAction((){// 单击显示/隐藏工具栏this.toggleToolbar()})))顺序模式SequenceGesture手势按顺序触发第一个成功后才识别第二个。比如先长按再拖拽。.gesture(GestureGroup(GestureMode.Sequence,LongPressGesture({duration:500}).onAction((){this.isDraggingtrue}),PanGesture().onActionUpdate((event:GestureEvent){if(this.isDragging){this.moveItem(event.offsetX,event.offsetY)}})))这三种模式搞清楚80% 的手势需求都能满足。手势冲突priorityGesture 来救场实际开发中最常遇到的问题是父子组件手势冲突。比如列表里的每个 item 有滑动手势列表本身也有滚动手势两个会打架。ArkUI 提供了priorityGesture这个属性设置了它的手势优先级高于普通的gesture// 父组件列表容器List(){ForEach(this.items,(item:ListItem){ListItem(){ItemCard({item:item})}})}// 父组件的翻页滑动用 priorityGesture确保优先识别.priorityGesture(PanGesture({direction:PanDirection.Horizontal}).onActionUpdate((event:GestureEvent){if(event.offsetX-50){this.goToNextPage()}}))还有个方法是用手势的onActionBegin里调stopPropagation阻止事件向父组件传递.gesture(PanGesture().onActionBegin((event:GestureEvent){// 阻止父容器的滑动手势event.stopPropagation()}).onActionUpdate((event:GestureEvent){this.handleSwipe(event)}))我的经验是能用priorityGesture解决的就别用stopPropagation。后者容易搞出手势突然不响应了的灵异 bug调试起来很痛苦。实战图片查看器做一个常见的图片查看器——支持双指缩放、拖拽平移、双击放大/还原、左右滑动切换图片。这是手势组合最典型的场景。Componentstruct ImageViewer{Stateimages:string[][img1.jpg,img2.jpg,img3.jpg]StatecurrentIndex:number0Statescale:number1StatebaseScale:number1StateoffsetX:number0StateoffsetY:number0StatebaseOffsetX:number0StatebaseOffsetY:number0build(){Stack(){Image(this.images[this.currentIndex]).width(100%).height(100%).objectFit(ImageFit.Contain).scale({x:this.scale,y:this.scale}).translate({x:this.offsetX,y:this.offsetY})// 双指缩放 拖拽平移并行.gesture(GestureGroup(GestureMode.Parallel,// 双指缩放PinchGesture({fingers:2}).onActionStart((){this.baseScalethis.scale}).onActionUpdate((event:GestureEvent){this.scaleMath.max(0.5,Math.min(5,this.baseScale*event.scale))}).onActionEnd((){this.baseScalethis.scale// 缩放到原始大小时位置归零if(Math.abs(this.scale-1)0.05){animateTo({duration:200},(){this.scale1this.baseScale1this.offsetX0this.offsetY0this.baseOffsetX0this.baseOffsetY0})}}),// 拖拽平移仅缩放状态下生效PanGesture({fingers:1}).onActionStart((){this.baseOffsetXthis.offsetXthis.baseOffsetYthis.offsetY}).onActionUpdate((event:GestureEvent){if(this.scale1){// 放大时自由平移this.offsetXthis.baseOffsetXevent.offsetXthis.offsetYthis.baseOffsetYevent.offsetY}})))// 双击 单击互斥.gesture(GestureGroup(GestureMode.Exclusive,// 双击放大/还原TapGesture({count:2}).onAction((){animateTo({duration:300,curve:Curve.EaseInOut},(){if(this.scale1){this.scale1this.baseScale1this.offsetX0this.offsetY0}else{this.scale2.5this.baseScale2.5}})}),// 单击显示/隐藏工具栏TapGesture({count:1}).onAction((){this.toggleToolbar()})))// 左右滑动切换图片用 priorityGesture 保证优先识别this.SwipeLayer()}.backgroundColor(#000000)}BuilderSwipeLayer(){Column().width(100%).height(100%).hitTestBehavior(this.scale1?HitTestMode.None:HitTestMode.Transparent).priorityGesture(PanGesture({direction:PanDirection.Horizontal}).onActionEnd((event:GestureEvent){if(this.scale1){if(event.offsetX-80this.currentIndexthis.images.length-1){this.currentIndexthis.resetTransform()}elseif(event.offsetX80this.currentIndex0){this.currentIndex--this.resetTransform()}}}))}privateresetTransform(){this.scale1this.baseScale1this.offsetX0this.offsetY0}privatetoggleToolbar(){// 显示/隐藏顶部导航和底部操作栏}}这个例子里有几个关键设计缩放和拖拽用并行模式两个手势可以同时触发用户体验自然。双击和单击用互斥模式双击优先匹配匹配不到才走单击。左右滑动切换用priorityGesture但同时加了hitTestBehavior的判断——当图片处于放大状态时滑动用来平移图片而不是切换所以把 SwipeLayer 的点击穿透关掉。手势开发的几点心得手势回调里别做耗时操作。缩放、拖拽的onActionUpdate触发频率很高一秒钟可能几十次里面放重计算会直接卡顿。数值要做边界限制。缩放限制在 0.5-5 倍平移限制在图片范围内不然用户一甩手指图片就飞没影了。善用animateTo做手势结束后的回弹。手势onActionEnd后用动画把元素归位体验会好很多。调试手势问题时Logger打日志比断点好用。手势触发频率高断点一卡就全乱了。