《相机焦距缩放》二、捏合手势使用指南

《相机焦距缩放》二、捏合手势使用指南 HarmonyOS PinchGesture 捏合手势使用指南本指南系统讲解 HarmonyOS ArkUI 中PinchGesture捏合手势的使用方法从基础概念到实战示例帮助开发者快速掌握双指缩放手势的实现。效果一、概述PinchGesture是 ArkUI 提供的基础手势之一用于识别双指或多指捏合手势。典型应用场景包括图片/地图的缩放相机焦距缩放控制文档/网页的放大缩小游戏界面的视角缩放核心特性特性说明最少手指2 指最多手指5 指最小识别距离5vp鼠标/键盘支持Ctrl 鼠标滚轮在支持设备上起始版本API Version 7二、基本语法2.1 构造函数PinchGesture(value?:{fingers?:number;distance?:number})参数类型默认值说明fingersnumber2触发手势所需的最少手指数量2~5distancenumber5最小识别距离单位 vp2.2 回调事件PinchGesture提供三个回调回调触发时机参数onActionStart手势识别成功时event: GestureEventonActionUpdate手势状态持续更新时手指移动event: GestureEventonActionEnd手势结束时手指抬起event: GestureEvent2.3 GestureEvent 属性interfaceGestureEvent{scale:number;// 捏合缩放比例相对于手势开始时的比例centerX:number;// 双指中心点 X 坐标centerY:number;// 双指中心点 Y 坐标offsetX:number;// X 方向偏移量offsetY:number;// Y 方向偏移量}关键属性scale说明手势开始时scale 1.0双指张开放大时scale 1.0双指捏合缩小时scale 1.0该值是相对于手势开始时的累积比例不是增量三、基础用法3.1 最简单的捏合手势ComponentV2struct SimplePinchExample{LocalscaleValue:number1;build(){Column(){Text(缩放比例:${this.scaleValue.toFixed(2)}x).fontSize(20).margin({bottom:20})Box().width(200).height(200).backgroundColor(#4FC08D).borderRadius(16).scale({x:this.scaleValue,y:this.scaleValue}).gesture(PinchGesture().onActionUpdate((event:GestureEvent){this.scaleValueevent.scale;}).onActionEnd((){// 手势结束后保留当前缩放值console.info(捏合结束, 最终比例: this.scaleValue.toFixed(2));}))}.width(100%).height(100%).justifyContent(FlexAlign.Center)}}3.2 带起始和结束回调ComponentV2struct PinchWithCallbacks{LocalscaleValue:number1;LocalstatusText:string等待手势...;build(){Column({space:20}){Text(this.statusText).fontSize(16).fontColor(#666)Text(当前缩放:${this.scaleValue.toFixed(2)}x).fontSize(24).fontWeight(FontWeight.Bold)Box().width(150).height(150).backgroundColor(#3B82F6).borderRadius(12).scale({x:this.scaleValue,y:this.scaleValue}).gesture(PinchGesture({fingers:2}).onActionStart((){this.statusText手势开始 - 正在缩放;}).onActionUpdate((event:GestureEvent){this.scaleValueevent.scale;}).onActionEnd((){this.statusText手势结束;}))}.width(100%).height(100%).justifyContent(FlexAlign.Center)}}四、进阶用法4.1 累积缩放基于初始值实际应用中通常需要在每次捏合时基于当前已缩放的比例继续缩放而非每次从 1.0 开始ComponentV2struct AccumulativeZoom{LocalcurrentScale:number1;// 当前显示的缩放比例LocalbaseScale:number1;// 手势开始时的基准比例build(){Column(){Text(缩放:${this.currentScale.toFixed(2)}x).fontSize(20).margin({bottom:20})Box().width(200).height(200).backgroundColor(#8B5CF6).borderRadius(16).scale({x:this.currentScale,y:this.currentScale}).gesture(PinchGesture().onActionUpdate((event:GestureEvent){// 当前缩放 基准值 × 手势比例this.currentScalethis.baseScale*event.scale;}).onActionEnd((){// 手势结束后将当前值保存为下次的基准值this.baseScalethis.currentScale;}))}.width(100%).height(100%).justifyContent(FlexAlign.Center)}}4.2 限制缩放范围ComponentV2struct BoundedZoom{LocalcurrentScale:number1;LocalbaseScale:number1;privateminScale:number0.5;privatemaxScale:number5.0;build(){Column(){Text(缩放:${this.currentScale.toFixed(2)}x).fontSize(20).margin({bottom:20})Box().width(200).height(200).backgroundColor(#EF4444).borderRadius(16).scale({x:this.currentScale,y:this.currentScale}).gesture(PinchGesture().onActionUpdate((event:GestureEvent){letnewScalethis.baseScale*event.scale;// 限制在 [minScale, maxScale] 范围内newScaleMath.max(this.minScale,Math.min(this.maxScale,newScale));this.currentScalenewScale;}).onActionEnd((){this.baseScalethis.currentScale;}))}.width(100%).height(100%).justifyContent(FlexAlign.Center)}}4.3 图片缩放查看器ComponentV2struct ImageZoomViewer{LocalimageScale:number1;LocalbaseScale:number1;build(){Stack(){Image($r(app.media.sample_image)).width(100%).height(100%).objectFit(ImageFit.Contain).scale({x:this.imageScale,y:this.imageScale}).gesture(PinchGesture().onActionUpdate((event:GestureEvent){letnewScalethis.baseScale*event.scale;newScaleMath.max(1.0,Math.min(5.0,newScale));this.imageScalenewScale;}).onActionEnd((){this.baseScalethis.imageScale;}))// 缩放比例提示Text(${this.imageScale.toFixed(1)}x).fontSize(14).fontColor(Color.White).backgroundColor(rgba(0,0,0,0.5)).padding({left:12,right:12,top:6,bottom:6}).borderRadius(20).position({x:50%,y:90%}).translate({x:-30})}.width(100%).height(100%)}}4.4 结合拖拽手势实现平移缩放import{gestureModifier}fromkit.ArkUI;ComponentV2struct PanAndZoom{LocalimageScale:number1;LocalbaseScale:number1;LocaloffsetX:number0;LocaloffsetY:number0;LocalstartOffsetX:number0;LocalstartOffsetY:number0;build(){Column(){Image($r(app.media.sample_image)).width(100%).height(400).objectFit(ImageFit.Contain).scale({x:this.imageScale,y:this.imageScale}).translate({x:this.offsetX,y:this.offsetY}).gesture(PinchGesture().onActionUpdate((event:GestureEvent){this.imageScaleMath.max(0.5,Math.min(5.0,this.baseScale*event.scale));}).onActionEnd((){this.baseScalethis.imageScale;})).parallelGesture(PanGesture().onActionUpdate((event:GestureEvent){this.offsetXthis.startOffsetXevent.offsetX;this.offsetYthis.startOffsetYevent.offsetY;}).onActionEnd((){this.startOffsetXthis.offsetX;this.startOffsetYthis.offsetY;}))}}}五、在相机焦距缩放中的应用以下示例展示如何将PinchGesture与相机焦距控制结合// 假设 photoSession 已初始化letzoomRatioRange:number[][1.0,10.0];// 从 getZoomRatioRange() 获取letcurrentZoom:number1.0;letbaseZoom:number1.0;XComponent({type:XComponentType.SURFACE,controller:xComponentController}).gesture(PinchGesture({fingers:2}).onActionUpdate((event:GestureEvent){// 计算目标缩放值lettargetZoombaseZoom*event.scale;// 限制在相机支持范围内if(targetZoomzoomRatioRange[1]){targetZoomzoomRatioRange[1];}elseif(targetZoomzoomRatioRange[0]){targetZoomzoomRatioRange[0];}currentZoomtargetZoom;// 设置相机焦距photoSession.setZoomRatio(targetZoom);}).onActionEnd((){// 保存当前焦距作为下次手势的基准baseZoomphotoSession.getZoomRatio();}))六、手势组合6.1 串行组合手势GestureGroup - Sequence// 先捏合再旋转.gesture(GestureGroup(GestureMode.Sequence,PinchGesture(),RotationGesture()))6.2 并行组合手势GestureGroup - Parallel// 同时支持捏合和拖拽.gesture(GestureGroup(GestureMode.Parallel,PinchGesture(),PanGesture()))6.3 互斥组合手势GestureGroup - Exclusive// 优先识别捏合其次识别点击.gesture(GestureGroup(GestureMode.Exclusive,PinchGesture(),TapGesture()))七、常见问题与注意事项7.1 scale 是累积值还是增量event.scale是相对于手势开始时的累积比例不是每次回调的增量。手势开始: scale 1.0 手指张开: scale 1.2 (表示比开始时放大了 20%) 继续张开: scale 1.5 (表示比开始时放大了 50%) 手指捏合: scale 0.8 (表示比开始时缩小了 20%)7.2 如何实现连续累积缩放需要在onActionEnd中保存当前值下次手势开始时作为基准.onActionUpdate((event){this.currentScalethis.baseScale*event.scale;}).onActionEnd((){this.baseScalethis.currentScale;// 保存为下次基准})7.3 如何限制缩放范围在onActionUpdate中使用Math.max()和Math.min()进行边界限制letclampedScaleMath.max(minScale,Math.min(maxScale,computedScale));7.4 PinchGesture 与 Scroll 冲突当组件在Scroll容器中时PinchGesture可能与滚动冲突。解决方案使用.hitTestBehavior(HitTestMode.Block)阻止事件穿透使用parallelGesture()替代gesture()使手势并行识别在缩放时临时禁用滚动7.5 多指手势的手指数量fingers参数指定的是最少手指数量不是固定手指数量。设置为 2 时2~5 指均可触发。八、API 速查表API说明PinchGesture({ fingers?, distance? })创建捏合手势.onActionStart(callback)手势识别成功回调.onActionUpdate(callback)手势持续更新回调.onActionEnd(callback)手势结束回调event.scale缩放比例累积值event.centerX / centerY双指中心点坐标GestureGroup(GestureMode.Parallel, ...)并行组合手势.parallelGesture(gesture)组件并行手势不阻止默认行为九、总结PinchGesture的使用核心流程创建 PinchGesture → 绑定到组件 .gesture() → onActionUpdate 中处理缩放逻辑注意累积缩放和范围限制 → onActionEnd 中保存基准值关键要点event.scale是累积比例需要配合基准值实现连续缩放始终对缩放范围进行限制避免超出合理值在相机场景中缩放范围由getZoomRatioRange()决定结合parallelGesture()可与其他手势共存