
鸿蒙Next手势详解系列:捏合手势 原创
今天这篇我们来学习捏合手势(PinchGesture),首先要了解捏合手势的构造参数和回调函数的回调信息都有哪些。
捏合手势处理器配置参数:PinchGestureHandlerOptions
名称 | 说明 |
---|---|
fingers | 触发捏合的最少手指数[2, 5] |
distance | 最小识别距离 |
isFingerCountLimited | 是否检查触摸屏幕的手指数量 |
GestureEvent对象在PinchGesture中的回调属性:
名称 | 说明 |
---|---|
scale | 缩放比例 |
pinchCenterX | 捏合手势中心点距离开始时左上角的x轴距离 |
pinchCenterY | 捏合手势中心点距离开始时左上角的y轴距离 |
fingerList | FingerInfo[],包含触发事件的所有触点信息 |
接下来看一下给图片添加捏合手势在捏合回调中的详细信息,模拟器演示:
以上是在模拟器的操作录屏,模拟器的捏合中心默认是图片的中心点,因此可以总结出:
1.捏合中心点坐标pinchCenterX,pinchCenterY是相对于捏合手势开始时图片左上角的距离。由上录屏可发现,在捏合过程中,中心点的坐标是不变的。只在重新开始捏合时才会改变。红色原点的位置就是捏合中心点的位置。
2.看打印日志可发现:开始捏合时图片尺寸 宽 = 2 * pinchCenterX
3.因此我们可以根据手指坐标计算一下捏合中心的坐标:
假设手指1在左侧,则2个手指中心相对于屏幕的X坐标 = 手指1X坐标+(手指2X坐标-手指1X坐标)/2
pinchCenterX = 手指中心点相对于屏幕的X坐标-组件开始时左上角相对于X的坐标
随便截一个图,根据上面的公式计算一下:
150+(236-150)/2-(-105)=298 和打印的pinchCenterX一致。
注意:
1.官方文档中介绍的pinchCenterX是捏合手势中心点的x轴坐标,我认为是不严谨的,个人观点,仅供参考!
2.捏合手势中,如果要想图片跟随放大缩小,一定注意不能在onActionUpdate回调中让当前缩放系数x回调系数,这样会指数级放大,即不能使用A=AxB,要在开始捏合时保存捏合开始的缩放系数,用这个系数乘回调系数才是真实的缩放系数。具体看代码。
接下来看一下在真机上的捏合回调演示:
绿色圆点是捏合开始时的中心点
红色圆点是捏合过程中手指移动时动态变化的中心点
白色圆点录屏时自动显示的手指按压位置
截取最后一帧看一下打印信息:
利用上面我们总结的公式:
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%')
}
}
