HarmonyOS鸿蒙Next中ArkUI的PinchGesture中获取的event.scale值会产生突变
HarmonyOS鸿蒙Next中ArkUI的PinchGesture中获取的event.scale值会产生突变 使用PinchGesture与PanGesture实现双指缩放和双指平移,发现边平移边缩放时,获取到的event.scale会产生突变,体现出来就是图像在缩放平移过程中的抖动。(复现:双指距离不变,但是同时上下移动,此时图像会抖动,并且随着移动速度增加抖动越大)
我删掉双指平移后,双指缩放是正常的;删掉双指缩放,双指平移也是正常的。说明组合起来就容易使得event.scale产生突变。
下面的我手指间距离不变,同时上下滑动时,打印出来的log信息:

可见,在一个平滑的变化过程中event.scale会产生突变,而后又恢复原状,这就是图像抖动的原因。
具体实现代码如下:
.gesture(
GestureGroup(GestureMode.Parallel,
PinchGesture({ fingers: 2, distance: 1 })
.onActionStart((event: GestureEvent | undefined) => {
if (event) {
this.pinchStartZoom = this.zoom;
this.lastPinchCenterX = (event.fingerList[0].displayX + event.fingerList[1].displayX) / 2.0;
this.lastPinchCenterY = (event.fingerList[0].displayY + event.fingerList[1].displayY) / 2.0;
}
})
.onActionUpdate((event: GestureEvent | undefined) => {
if (event) {
const zoomNew = Math.max(0.001, Math.min(1000, this.pinchStartZoom * event.scale));
this.offsetX = (this.offsetX - vp2px(this.lastPinchCenterX)) * (zoomNew / this.zoom) + vp2px(this.lastPinchCenterX);
this.offsetY = (this.offsetY - vp2px(this.lastPinchCenterY)) * (zoomNew / this.zoom) + vp2px(this.lastPinchCenterY);
this.zoom = zoomNew;
this.lastPinchCenterX = (event.fingerList[0].displayX + event.fingerList[1].displayX) / 2.0;
this.lastPinchCenterY = (event.fingerList[0].displayY + event.fingerList[1].displayY) / 2.0;
draw(this.drawIndex, this.zoom, this.offsetX, this.offsetY);
}
})
.onActionEnd((event: GestureEvent | undefined) => {
}),
PanGesture({ fingers: 1, distance: 1 })
.onActionStart((event: GestureEvent | undefined) => {
if (event) {
this.lastPanX = event.offsetX;
this.lastPanY = event.offsetY;
}
})
.onActionUpdate((event: GestureEvent | undefined) => {
if (event) {
this.offsetX += vp2px(event.offsetX - this.lastPanX);
this.offsetY += vp2px(event.offsetY - this.lastPanY);
this.lastPanX = event.offsetX;
this.lastPanY = event.offsetY;
draw(this.drawIndex, this.zoom, this.offsetX, this.offsetY);
}
})
.onActionEnd((event: GestureEvent | undefined) => {
})
)
)
这是系统问题还是我实现的问题?能否给出一个双指平移、双指缩放、单指缩放同时实现的demo?(即可以同时双指缩放和双指平移,抬起一根手指后能接着单指平移)现有给出的示例好像都没有实现这一功能。
更多关于HarmonyOS鸿蒙Next中ArkUI的PinchGesture中获取的event.scale值会产生突变的实战教程也可以访问 https://www.itying.com/category-93-b0.html
尊敬的开发者,您好!您的问题已受理,请您耐心等待,感谢您的理解与支持!
更多关于HarmonyOS鸿蒙Next中ArkUI的PinchGesture中获取的event.scale值会产生突变的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
使用互斥手势组(GestureMode.Exclusive),优先处理缩放手势;通过插值算法缓解event.scale突变。示例支持双指缩放+双指平移及单指平移的切换:
@Entry
@Component
struct GestureDemo {
@State zoom: number = 1.0;
@State offsetX: number = 0;
@State offsetY: number = 0;
private lastPanX: number = 0;
private lastPanY: number = 0;
private pinchStartZoom: number = 1.0;
private lastPinchCenterX: number = 0;
private lastPinchCenterY: number = 0;
build() {
Stack() {
// 被操作的内容(例如图片或画布)
Canvas(this.ctx)
.width('100%')
.height('100%')
.scale({ x: this.zoom, y: this.zoom })
.translate({ x: this.offsetX, y: this.offsetY })
}
.gesture(
GestureGroup(GestureMode.Exclusive,
// 双指缩放逻辑
PinchGesture({ fingers: 2 })
.onActionStart((event: GestureEvent | undefined) => {
if (event) {
this.pinchStartZoom = this.zoom;
this.lastPinchCenterX = (event.fingerList.displayX + event.fingerList.displayX) / 2;
this.lastPinchCenterY = (event.fingerList.displayY + event.fingerList.displayY) / 2;
}
})
.onActionUpdate((event: GestureEvent | undefined) => {
if (event) {
// 平滑处理缩放值
const smoothFactor = 0.2;
const targetZoom = this.pinchStartZoom * event.scale;
this.zoom += (targetZoom - this.zoom) * smoothFactor;
// 根据缩放中心点调整偏移量
const newCenterX = (event.fingerList.displayX + event.fingerList.displayX) / 2;
const newCenterY = (event.fingerList.displayY + event.fingerList.displayY) / 2;
this.offsetX += (newCenterX - this.lastPinchCenterX) * this.zoom;
this.offsetY += (newCenterY - this.lastPinchCenterY) * this.zoom;
this.lastPinchCenterX = newCenterX;
this.lastPinchCenterY = newCenterY;
}
}),
// 单指/双指平移逻辑
PanGesture({ fingers: 1 })
.onActionStart((event: GestureEvent | undefined) => {
if (event) {
this.lastPanX = event.offsetX;
this.lastPanY = event.offsetY;
}
})
.onActionUpdate((event: GestureEvent | undefined) => {
if (event) {
// 直接更新偏移量
this.offsetX += (event.offsetX - this.lastPanX) * this.zoom;
this.offsetY += (event.offsetY - this.lastPanY) * this.zoom;
this.lastPanX = event.offsetX;
this.lastPanY = event.offsetY;
}
})
)
)
}
}
感谢解答,我使用Parallel模式而不是Exclusive模式的原因是希望可以在双指缩放的时候双指平移(或者说双指平移的时候双指缩放)。
这样就要求PinchGesture和PanGesture要同时触发Update回调。我试过将平移逻辑放到PinchGesture中,还是会出现图像抖动。根本原因就是平移和缩放同时进行时,event.scale会抖动。
通过插值算法缓解event.scale突变确实是目前可用的思路,不过终究是治标不治本。
我这边有个思路 你可以看看满足你的需求不: 1.双指头平移的时候指定滑动方向水平和竖直方向出发,双指缩放只能在45度角出发
2.你指定区域,手指先到达的区域是上半部分为平移、下半部分为缩放
这样感觉限制太大了,用户体验也不好,还不如使 缩放和平移 操作互斥。
是这样的 或者增加用户自定义设置,搞个开关来定义,或者是根据场景来判断处理这种情况和需求很相关,产品对这个边界都不清楚的话 是没法处理的,
在HarmonyOS鸿蒙Next的ArkUI中,PinchGesture的event.scale值突变通常由触摸事件采样率或手势识别算法导致。scale值基于连续触摸点距离计算,若触点坐标变化不连续,scale会出现跳变。可通过设置gestureMode为Parallel或Sequence调整识别模式,或使用onActionUpdate回调结合历史scale值进行平滑处理来缓解。
这是一个已知的、在并行处理PinchGesture和PanGesture时可能出现的系统级手势识别冲突问题。当双指在平移过程中,系统底层的手势识别器可能会在“双指平移”和“缩放”两种手势间产生瞬时误判,导致event.scale值发生突变,从而引起视觉抖动。
核心原因:GestureMode.Parallel模式下,PinchGesture和PanGesture会独立、并行地接收触摸事件流。当双指保持距离不变进行平移时,系统算法可能在某些帧(尤其是高速移动或触点坐标更新时)将纯平移动作瞬时识别为微小的缩放动作,导致scale值出现尖峰。
解决方案:放弃使用两个独立手势并行监听的方式,改为使用单一手势(如PanGesture)来手动计算缩放与平移。这是目前实现平滑双指缩放平移最稳定可靠的方法。
实现思路:
- 使用
PanGesture({ fingers: 2 })监听双指触摸。 - 在
onActionStart中记录初始双指距离和中心点。 - 在
onActionUpdate中:- 计算缩放:根据当前双指距离与初始距离的比值,计算出
scale。 - 计算平移:根据双指中心点的变化量,计算出平移偏移。
- 基于计算出的
scale和偏移量,统一更新视图的变换矩阵。
- 计算缩放:根据当前双指距离与初始距离的比值,计算出
这种方式完全由开发者控制缩放和平移的逻辑,避免了系统底层手势识别器的干扰,从而消除了抖动。
代码结构示例:
// 使用双指PanGesture统一处理
PanGesture({ fingers: 2 })
.onActionStart((event: GestureEvent) => {
if (event.fingerList.length === 2) {
// 1. 记录初始状态:双指距离、中心点、当前zoom和offset
this.startDistance = calcFingerDistance(event.fingerList[0], event.fingerList[1]);
this.startCenterX = (event.fingerList[0].x + event.fingerList[1].x) / 2;
this.startCenterY = (event.fingerList[0].y + event.fingerList[1].y) / 2;
this.lastZoom = this.zoom;
this.lastOffsetX = this.offsetX;
this.lastOffsetY = this.offsetY;
}
})
.onActionUpdate((event: GestureEvent) => {
if (event.fingerList.length === 2) {
// 2. 计算当前双指距离和中心点
const currentDistance = calcFingerDistance(event.fingerList[0], event.fingerList[1]);
const currentCenterX = (event.fingerList[0].x + event.fingerList[1].x) / 2;
const currentCenterY = (event.fingerList[0].y + event.fingerList[1].y) / 2;
// 3. 手动计算缩放比例
const scale = currentDistance / this.startDistance;
const newZoom = this.lastZoom * scale;
// 4. 手动计算平移偏移(需考虑缩放中心)
const deltaCenterX = currentCenterX - this.startCenterX;
const deltaCenterY = currentCenterY - this.startCenterY;
// 平移量需要叠加因缩放中心变化产生的偏移
const newOffsetX = this.lastOffsetX + deltaCenterX - (scale - 1) * (currentCenterX - this.lastOffsetX);
const newOffsetY = this.lastOffsetY + deltaCenterY - (scale - 1) * (currentCenterY - this.lastOffsetY);
// 5. 更新状态并重绘
this.zoom = newZoom;
this.offsetX = newOffsetX;
this.offsetY = newOffsetY;
this.draw();
}
})
对于“抬起一根手指后变为单指平移”的需求,可以再额外并行一个单指PanGesture({ fingers: 1 })。由于单指和双指PanGesture的触点数量不同,它们通常能较好地共存,不会产生类似双指并行时的冲突。
总结:当前系统并行的PinchGesture和PanGesture在特定场景下确实可能产生scale突变。推荐采用手动计算的方式替代独立的PinchGesture来获得更稳定的双指操控体验。

