HarmonyOS鸿蒙Next中ArkUI的PinchGesture中获取的event.scale值会产生突变

HarmonyOS鸿蒙Next中ArkUI的PinchGesture中获取的event.scale值会产生突变 使用PinchGesture与PanGesture实现双指缩放和双指平移,发现边平移边缩放时,获取到的event.scale会产生突变,体现出来就是图像在缩放平移过程中的抖动。(复现:双指距离不变,但是同时上下移动,此时图像会抖动,并且随着移动速度增加抖动越大)

我删掉双指平移后,双指缩放是正常的;删掉双指缩放,双指平移也是正常的。说明组合起来就容易使得event.scale产生突变。

下面的我手指间距离不变,同时上下滑动时,打印出来的log信息:

cke_4992.png

可见,在一个平滑的变化过程中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

8 回复

尊敬的开发者,您好!您的问题已受理,请您耐心等待,感谢您的理解与支持!

更多关于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)来手动计算缩放与平移。这是目前实现平滑双指缩放平移最稳定可靠的方法。

实现思路

  1. 使用PanGesture({ fingers: 2 })监听双指触摸。
  2. onActionStart中记录初始双指距离和中心点。
  3. 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来获得更稳定的双指操控体验。

回到顶部