鸿蒙Next手势详解系列:捏合手势实现图片跟手缩放 原创

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

上篇已经了解了捏合手势的基础使用和回调函数。在上篇的捏合手势中,我们可以发现,图片在缩放过程中都是居中缩放的,虽然捏合中心点不一样,但是缩放效果都是一样的。这显然不符合我们手指捏合的预期。
实际图片展示中,我们希望实现,手指捏合放大或缩小的过程中,可以让缩放中心位置保持不变,这样我们就可以直接看到我们想要放大展示的地方。
看一下最终实现效果演示
鸿蒙Next手势详解系列:捏合手势实现图片跟手缩放-鸿蒙开发者社区
实现思路:
1.由于我们的image放在stack布局中,居中显示,因此在默认放大之后,图片依然是居中显示的,要想实现跟手缩放,我们就需要在缩放的过程中,移动我们的图片,保持开始的缩放中心点位置保持不变
2.因此我们需要记录手指开始缩放时的中心点位置,相当于开始缩放状态时宽高的百分比
3.在缩放过程中,我们根据开始缩放中心点的百分比计算出放大之后这个点相对于图片的位置和屏幕的位置
4.将计算出新的缩放中心的位置移动到开始时缩放中心点的位置,这样就实现了跟手缩放,达到了图片的缩放中心保持不变的效果。
实现过程:
1.定义图片的偏移量offsetX、offsetY,缩放开始时保存当前偏移量currentoffsetX、currentoffsetY
2.计算开始时捏合中心点相当于图片宽高的百分比startPercentX、startPercentY
3.计算开始捏合时的中心点坐标startPinchCenterX、startPinchCenterY
4.缩放过程中,根据缩放系数,计算开始的中心点在图片缩放后的位置的坐标点newPinchCenterX、newPinchCenterY
5.根据两个坐标计算图片的偏移量offsetX、offsetY

源码

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
  @Local startPercentX:number=0  // 捏合手势开始时占宽的百分比
  @Local startPercentY:number=0
  @Local newPinchCenterX:number=0 // 捏合过程中 开始时的中心点在图片缩放后的新位置
  @Local newPinchCenterY:number=0 //
  @Local offsetX:number=0;
  @Local offsetY:number=0;
  @Local currentoffsetX:number=0;
  @Local currentoffsetY: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
        })
        .offset({x:this.offsetX,y:this.offsetY})
        .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
              this.startPinchCenterY=event.pinchCenterY-(this.imageHeight*this.imageCurrentScale-getScreenHeight())/2-px2vp(WindowUtils.getStatusHeight())
              //保存开始时的偏移量
              this.currentoffsetX = this.offsetX
              this.currentoffsetY = this.offsetY
              //计算开始时 捏合中心点相当于图片宽高的百分比  用于计算缩放后当前点的位置 计算捏合偏移量
              this.startPercentX = event.pinchCenterX/this.startPinchimageWidth
              this.startPercentY = event.pinchCenterY/this.startPinchimageHeight
            })
            .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
              this.currentPinchCenterY=event.pinchCenterY-(this.imageHeight*this.imageCurrentScale-getScreenHeight())/2-px2vp(WindowUtils.getStatusHeight())
              // 计算开始的中心点在图片缩放后的位置 的坐标点
              this.newPinchCenterX=this.imageWidth*this.imageScale*this.startPercentX-(this.imageWidth*this.imageScale-getScreenWidth())/2
              this.newPinchCenterY=this.imageHeight*this.imageScale*this.startPercentY-(this.imageHeight*this.imageScale-getScreenHeight())/2-px2vp(WindowUtils.getStatusHeight())
              // 通过缩放前的缩放中心点坐标和 按照缩放比例计算的新中心点的坐标 计算偏移量
              this.offsetX =this.currentoffsetX+ this.startPinchCenterX- this.newPinchCenterX
              this.offsetY =this.currentoffsetY+ this.startPinchCenterY- this.newPinchCenterY
            })
            .onActionEnd(()=>{
              this.showPinchCenter = false
              if (this.imageScale<1) {
                this.imageScale=1
                this.offsetX=0
                this.offsetY=0
              }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)
      Circle({width:20,height:20}).fill(Color.Yellow)
        .position({
          x:this.newPinchCenterX+this.offsetX,
          y:this.newPinchCenterY+this.offsetY})
        .visibility(this.showPinchCenter?Visibility.Visible:Visibility.Hidden)
    }.height('100%')
  }
}

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