鸿蒙Next手势详解系列:捏合手势 原创

auhgnixgnahz
发布于 2025-10-16 09:17
浏览
0收藏

今天这篇我们来学习捏合手势(PinchGesture),首先要了解捏合手势的构造参数和回调函数的回调信息都有哪些。
捏合手势处理器配置参数:PinchGestureHandlerOptions

名称 说明
fingers 触发捏合的最少手指数[2, 5]
distance 最小识别距离
isFingerCountLimited 是否检查触摸屏幕的手指数量

GestureEvent对象在PinchGesture中的回调属性:

名称 说明
scale 缩放比例
pinchCenterX 捏合手势中心点距离开始时左上角的x轴距离
pinchCenterY 捏合手势中心点距离开始时左上角的y轴距离
fingerList FingerInfo[],包含触发事件的所有触点信息

接下来看一下给图片添加捏合手势在捏合回调中的详细信息,模拟器演示
鸿蒙Next手势详解系列:捏合手势-鸿蒙开发者社区
以上是在模拟器的操作录屏,模拟器的捏合中心默认是图片的中心点,因此可以总结出:
1.捏合中心点坐标pinchCenterX,pinchCenterY是相对于捏合手势开始时图片左上角的距离。由上录屏可发现,在捏合过程中,中心点的坐标是不变的。只在重新开始捏合时才会改变。红色原点的位置就是捏合中心点的位置
2.看打印日志可发现:开始捏合时图片尺寸 宽 = 2 * pinchCenterX
3.因此我们可以根据手指坐标计算一下捏合中心的坐标:
假设手指1在左侧,则2个手指中心相对于屏幕的X坐标 = 手指1X坐标+(手指2X坐标-手指1X坐标)/2
pinchCenterX = 手指中心点相对于屏幕的X坐标-组件开始时左上角相对于X的坐标
鸿蒙Next手势详解系列:捏合手势-鸿蒙开发者社区
随便截一个图,根据上面的公式计算一下:
150+(236-150)/2-(-105)=298 和打印的pinchCenterX一致。

注意:

1.官方文档中介绍的pinchCenterX是捏合手势中心点的x轴坐标,我认为是不严谨的,个人观点,仅供参考!
2.捏合手势中,如果要想图片跟随放大缩小,一定注意不能在onActionUpdate回调中让当前缩放系数x回调系数,这样会指数级放大,即不能使用A=AxB,要在开始捏合时保存捏合开始的缩放系数,用这个系数乘回调系数才是真实的缩放系数。具体看代码。

接下来看一下在真机上的捏合回调演示
绿色圆点是捏合开始时的中心点
红色圆点是捏合过程中手指移动时动态变化的中心点
白色圆点录屏时自动显示的手指按压位置
鸿蒙Next手势详解系列:捏合手势-鸿蒙开发者社区
截取最后一帧看一下打印信息:
鸿蒙Next手势详解系列:捏合手势-鸿蒙开发者社区

利用上面我们总结的公式:
145+(257-145)/2-(-99)=300 和 pinchCenterX 一样。

总结:
了解了捏合手势回调的每个函数的意义,我们就可以根据利用他们实现我们想要的缩放效果。
上面的计算公式只是证明pinchCenterX、pinchCenterY的含义,也可以根据其他方式计算。

演示代码:

import { getScreenHeight, getScreenWidth } from '../utils/DisplayUtil';
import { WindowUtils } from '../utils/WindowUtils';

@Entry
@ComponentV2
struct PinchGestureTest{
  @Local imageWidth: number = getScreenWidth() // 图片默认原始宽度 屏幕宽度
  @Local imageHeight: number = getScreenHeight() // 图片默认原始高度
  @Local imageTrueWidth:number = 0 // 图片真实尺寸
  @Local imageTrueHeight:number = 0
  @Local showPinchCenter:boolean = false
  @Local imageCurrentScale: number = 1 // 图片当前缩放比例
  @Local imageScale: number = 1 // 图片缩放比例
  @Local gestureScale:number = 1  //手势回调 缩放比例
  @Local pinchCenterX:number = 0  //手势回调 捏合手势中心点的x轴坐标 vp
  @Local pinchCenterY:number = 0  //手势回调 捏合手势中心点的y轴坐标
  @Local fingerList: FingerInfo[] = [] // 触屏手指触发事件的所有触点信息
  @Local imageTopleftX:number = 0 //图片左上角相对于父元素的坐标
  @Local imageTopleftY:number = 0
  @Local currentPinchCenterX:number=0 // 每次缩放时的中心点
  @Local currentPinchCenterY:number=0 // 每次缩放时的中心点
  @Local startPinchCenterX:number=0 //开始缩放时的缩放中心点 坐标
  @Local startPinchCenterY:number=0
  @Local startPinchimageWidth:number=0 // 开始捏合时图片的初始尺寸
  @Local startPinchimageHeight:number=0
  @Local startPinchimageTopleftX:number = 0
  @Local startPinchimageTopleftY:number = 0
  build() {
    Stack(){
      Image($r('app.media.imagetest'))
        .width(this.imageWidth*this.imageScale)
        .height(this.imageHeight*this.imageScale)
        .draggable(false)
        .onComplete((event)=>{
          this.imageTrueWidth=event?.width??0
          this.imageTrueHeight=event?.height??0
          // 宽度铺满
          this.imageHeight = this.imageTrueHeight*this.imageWidth / this.imageTrueWidth;
        })
        .onAreaChange((oldValue: Area, newValue: Area)=>{
          this.imageTopleftX = newValue.position.x as  number
          this.imageTopleftY = newValue.position.y as  number
        })
        .gesture(
          PinchGesture({fingers:2})
            .onActionStart((event)=>{
              //保存当前缩放系数
              this.imageCurrentScale = this.imageScale
              //捏合手势开始时图片的尺寸
              this.startPinchimageWidth = this.imageWidth*this.imageScale
              this.startPinchimageHeight = this.imageHeight*this.imageScale
              //保存开始时图片左上角相当于屏幕的坐标
              this.startPinchimageTopleftX = this.imageTopleftX
              this.startPinchimageTopleftY = this.imageTopleftY
              //计算开始捏合时的中心点坐标 用于绘制演示
              this.startPinchCenterX=event.pinchCenterX-(this.imageWidth*this.imageCurrentScale-getScreenWidth())/2-10
              this.startPinchCenterY=event.pinchCenterY-(this.imageHeight*this.imageCurrentScale-getScreenHeight())/2-10-px2vp(WindowUtils.getStatusHeight())
            })
            .onActionUpdate((event)=>{
              this.showPinchCenter = true
              this.gestureScale = event.scale
              this.fingerList  = event.fingerList
              this.pinchCenterX = event.pinchCenterX
              this.pinchCenterY = event.pinchCenterY
              // 通过保存的初始缩放系数 计算 新的缩放系数
              this.imageScale = this.imageCurrentScale*event.scale
              // 缩放过程中 由于手指滑动 会改变缩放中心点的坐标 用于演示
              this.currentPinchCenterX=event.pinchCenterX-(this.imageWidth*this.imageCurrentScale-getScreenWidth())/2-10
              this.currentPinchCenterY=event.pinchCenterY-(this.imageHeight*this.imageCurrentScale-getScreenHeight())/2-10-px2vp(WindowUtils.getStatusHeight())
            })
            .onActionEnd(()=>{
              this.showPinchCenter = false
              if (this.imageScale<1) {
                this.imageScale=1
              }else {
                //可以在这里或者缩放过程中限制缩放边界
              }
            })
        )
      Column({space:10}){
        Text('图片真实尺寸px 宽:'+this.imageTrueWidth.toFixed(0)+' 高:'+this.imageTrueHeight.toFixed(0) )
        Text('图片显示尺寸vp 宽:'+(this.imageWidth*this.imageScale).toFixed(0)+' 高:'+(this.imageHeight*this.imageScale).toFixed(0) )
        Text('开始捏合时图片尺寸 宽:'+this.startPinchimageWidth.toFixed(0)+' 高:'+this.startPinchimageHeight.toFixed(0))
        Text('开始捏合时左上角坐标 X:'+this.startPinchimageTopleftX.toFixed(0)+' 高:'+this.startPinchimageTopleftY.toFixed(0))
        Text('图片左上角坐标vp X:'+this.imageTopleftX.toFixed(0)+' Y:'+this.imageTopleftY.toFixed(0) )
        Text('捏合缩放比例:'+this.gestureScale)
        Text('图片缩放比例:'+this.imageScale)
        Text('捏合中心点距离开始时左上角vp X:'+this.pinchCenterX.toFixed(0)+' Y:'+this.pinchCenterY.toFixed(0) )
      }.height('100%').width('100%').hitTestBehavior(HitTestMode.None).alignItems(HorizontalAlign.Start)

      Column(){
        List(){
          ForEach(this.fingerList,(item:FingerInfo,index:number)=>{
            ListItem(){
              Column({space:5}){
                Text('手指'+(index+1))
                Text('相对于窗口左上角的x轴'+item.globalX.toFixed(0))
                Text('相对于窗口左上角的y轴'+item.globalY.toFixed(0))
                // Text('相对于当前组件元素原始区域左上角的x轴坐标'+item.localX)
                // Text('相对于当前组件元素原始区域左上角的y轴坐标'+item.localY)
                // Text('相对于屏幕左上角的x轴坐标'+item.displayX)
                // Text('相对于屏幕左上角的y轴坐标'+item.displayY)
              }.alignItems(HorizontalAlign.Start)
            }
          })
        }.height('20%').hitTestBehavior(HitTestMode.None)
      }.height('100%').width('100%').justifyContent(FlexAlign.End).hitTestBehavior(HitTestMode.None)

      Circle({width:20,height:20}).fill(Color.Green)
        .position({
          x:this.startPinchCenterX,
          y:this.startPinchCenterY})
        .visibility(this.showPinchCenter?Visibility.Visible:Visibility.Hidden)
      Circle({width:20,height:20}).fill(Color.Red)
        .position({
          x:this.currentPinchCenterX,
          y:this.currentPinchCenterY})
        .visibility(this.showPinchCenter?Visibility.Visible:Visibility.Hidden)
    }.height('100%')
  }
}

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
标签
收藏
回复
举报
回复
    相关推荐