• 博客园logo
  • 会员
  • 周边
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • YouClaw
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录

wkin

  • 博客园
  • 联系
  • 订阅
  • 管理

公告

View Post

鸿蒙app开发·滑动手势事件

①非滚动容器滚动事件(gesture)

  // 手势动作参数
  private panOption: PanGestureOptions = new PanGestureOptions({ direction: PanDirection.Up | PanDirection.Down})
//  private panOption: PanGestureOptions = new PanGestureOptions({ direction: PanDirection.Left | PanDirection.Right})
//
。。。。
//添加手势事件(非滚动容器使用)
.gesture(
  PanGesture(this.panOption)
    .onActionEnd((event: GestureEvent) => {
      if(event.offsetY<0) {
        this.nowIndex++
      }else if(event.offsetY>0) {
        this.nowIndex--
      }

      console.log('this is nowIndex:',this.nowIndex)
      this.scroller.scrollToIndex(this.nowIndex,true)
    }),
)

②滚动容器手势事件(parallelGesture):Scroll+ Swiper、List、Grid、WaterFlow、Tabs + Scroll

  // 手势动作参数
  private panOption: PanGestureOptions = new PanGestureOptions({ direction: PanDirection.Up | PanDirection.Down})
//  private panOption: PanGestureOptions = new PanGestureOptions({ direction: PanDirection.Left | PanDirection.Right})
。。。
    .parallelGesture(
      PanGesture(this.panOption)
        // .onActionStart((event?: GestureEvent) => {
        //   console.info('wy Pan start',event);
        // })
        // .onActionUpdate((event?: GestureEvent) => {
        //   if (event) {
        //     console.info('wy Pan event',event);
        //   }
        // })
        .onActionEnd((event: GestureEvent) => {
          if (event.offsetY < 0) {
            this.nowIndex++
          } else if (event.offsetY > 0) {
            this.nowIndex--
          }

          console.log('this is nowIndex:', this.nowIndex)
          this.scroller.scrollToIndex(this.nowIndex, true)
        })
    )

③平移手势和缩放手势


@Entry
@Component
struct Index {

  build() {
    RelativeContainer() {
      GestureDemo()
        .alignRules({
          center: { anchor: '__container__', align: VerticalAlign.Center },
          middle: { anchor: '__container__', align: HorizontalAlign.Center }
        })
        .width('100%')
        .height(300)
    }
    .height('100%')
    .width('100%')
  }
}

@Component
struct GestureDemo {
  @State isScaling: boolean = false;
  @State positionX: number = 0;
  @State positionY: number = 0;
  @State offsetX: number = 0;
  @State offsetY: number = 0;
  @State scaleValue: number = 1;
  @State pinchValue: number = 1;
  @State pinchX: number = 0;
  @State pinchY: number = 0;

  build() {
    Column() {
      Image($r('app.media.startIcon'))
        .width('100%')
        .height('100%')
    }
    .justifyContent(FlexAlign.Center)
    .width('100%')
    .height('100%')
    .scale({
      x: this.scaleValue,
      y: this.scaleValue,
      z: 0
    })
    .translate({
      x: this.offsetX,
      y: this.offsetY,
      z: 0
    })
    .gesture(
      // 并行识别手势(同时响应拖拽和缩放)
      GestureGroup(GestureMode.Parallel,
        /* 缩放手势处理 */
        PinchGesture({fingers: 2})
        // 手势更新回调
          .onActionUpdate((event: GestureEvent) => {
            if(event){
              // 计算临时缩放值:当前基准值 × 手势缩放比例
              let tmpScale = this.pinchValue * event.scale;
              // 缩放范围限制:0.5倍最小,4倍最大(临时可缩小到0.5倍)
              this.scaleValue = Math.min(Math.max(tmpScale, 0.5), 4)
            }
          })
          .onActionEnd(() => {
            // 手势结束后处理回弹逻辑
            if (this.scaleValue < 1) {
              // 当缩放小于1倍时,执行弹性动画回到原始尺寸
              animateTo({
                duration: 200,       // 动画时长200ms
                curve: Curve.EaseOut // 使用缓出曲线
              }, () => {
                this.scaleValue = 1; // 重置为1倍缩放
                this.pinchValue = 1; // 更新基准值
              })
            } else {
              // 当缩放大于等于1倍时,保持当前值作为新基准
              this.pinchValue = this.scaleValue
            }
            this.isScaling = false;      // 更新缩放状态标识
          }),

        /* 拖动手势处理 */
        PanGesture()
          .onActionStart((event: GestureEvent) => {
            console.log('拖动手势开始')  // 手势开始日志
          })
          // 拖拽更新时的边界控制
          .onActionUpdate((event: GestureEvent) => {
            if(event){
              // 计算缩放后的实际图片尺寸(容器尺寸300×300)
              const scaledWidth = 300 * this.scaleValue;
              const scaledHeight = 300 * this.scaleValue;

              // 计算最大允许偏移量(确保图片边缘不超过容器50%)
              const maxOffsetX = (scaledWidth - 300) / 2;  // 水平最大偏移
              const maxOffsetY = (scaledHeight - 300) / 2; // 垂直最大偏移

              // 计算临时偏移量(考虑缩放对拖动距离的影响)
              let newOffsetX = this.positionX + event.offsetX * this.scaleValue;
              let newOffsetY = this.positionY + event.offsetY * this.scaleValue;

              // 应用边界限制(使用Math函数确保偏移在允许范围内)
              this.offsetX = Math.max(-maxOffsetX, Math.min(newOffsetX, maxOffsetX));
              this.offsetY = Math.max(-maxOffsetY, Math.min(newOffsetY, maxOffsetY));
            }
          })
          .onActionEnd(() => {
            // 手势结束后边界回弹检查
            const scaledWidth = 300 * this.scaleValue;
            const scaledHeight = 300 * this.scaleValue;
            const maxOffsetX = (scaledWidth - 300) / 2;
            const maxOffsetY = (scaledHeight - 300) / 2;

            // 当偏移超过限制时执行回弹动画
            if (Math.abs(this.offsetX) > maxOffsetX || Math.abs(this.offsetY) > maxOffsetY) {
              animateTo({
                duration: 200,
                curve: Curve.EaseOut
              }, () => {
                // 水平方向回弹处理
                this.offsetX = this.offsetX > maxOffsetX ? maxOffsetX :
                  this.offsetX < -maxOffsetX ? -maxOffsetX : this.offsetX;
                // 垂直方向回弹处理
                this.offsetY = this.offsetY > maxOffsetY ? maxOffsetY :
                  this.offsetY < -maxOffsetY ? -maxOffsetY : this.offsetY;
                // 更新基准位置
                this.positionX = this.offsetX;
                this.positionY = this.offsetY;
              })
            } else {
              // 未超限时直接更新基准位置
              this.positionX = this.offsetX;
              this.positionY = this.offsetY;
            }
          })
      )
    )
  }
}


综合使用(缩放、跟手)


import { CommonConstants, LogUtils } from '@ohos/base';
import { ImageKnifeComponentV2, ImageKnifeOptionV2 } from '@ohos/imageknife';
import { AlbumTab } from '../models/TabItem';
import { fileUri } from '@kit.CoreFileKit';
import { matrix4, window } from '@kit.ArkUI';
import { AppUtil } from '@pura/harmony-utils';

const TAG = 'ScalableImageComponent';

@ComponentV2
export struct ScalableImageComponent {
  @Local imageKnifeOption: ImageKnifeOptionV2 | undefined;
  @Param thumbnailUrl: string = '';
  @Param albumTabType: AlbumTab = AlbumTab.CAMERA_ALBUM;
  @Param remoteUrl: string = '';
  @Local isDisableSwipe: boolean = false;
  @Local imageWidth: number = 0;
  @Local imageHeight: number = 0;
  @Local imageWHRatio: number = 0;
  @Local componentWidth: number = 0;
  @Local componentHeight: number = 0;
  @Local isOverlay: boolean = false;
  @Local curScale: number = 1;
  @Local maxScale: number = 3;
  @Local defaultScaleValue: number = 1;
  @Local curOffsetY: number = 0;
  @Local curOffsetX: number = 0;
  @Local lastOffsetY: number = 0;
  @Local lastOffsetX: number = 0;
  @Local centerY: number = 0;
  @Local centerX: number = 0;
  @Local lastScale: number = 1;
  @Local isArriveBoundary: boolean = false;
  @Local center: [number, number] = [0, 0];
  @Local matrix: matrix4.Matrix4Transit | undefined;
  @Local maxOffsetX: number = 0;
  @Local maxOffsetY: number = 0;
  @Local minOffsetX: number = 0;
  @Local minOffsetY: number = 0;
  @Local displayImageWidth: number = 0;
  @Local displayImageHeight: number = 0;

  aboutToAppear(): void {
    this.imageKnifeOption = ({
      placeholderSrc: this.thumbnailUrl,
      loadSrc: this.albumTabType === AlbumTab.CAMERA_ALBUM ? this.remoteUrl : fileUri.getUriFromPath(this.remoteUrl),
      objectFit: ImageFit.Contain,
      errorholderSrc: $r('app.media.ic_new_media_placeholder_dark_16_9'),
      onLoadListener: {
        onLoadStart: () => {
          LogUtils.debug(TAG, "Load start: " + this.remoteUrl);
        },
        onLoadFailed: (err) => {
          LogUtils.debug(TAG, "Load Failed Reason: " + err + ",fileBean:" + this.remoteUrl);
        },
        onLoadSuccess: (data, imageData) => {
          this.imageWidth = imageData.imageWidth;
          this.imageHeight = imageData.imageHeight;
          this.imageWHRatio = this.imageWidth / this.imageHeight;
          this.calculateDisplaySize();
          LogUtils.debug(TAG, `Load Successful: ${this.imageWidth}x${this.imageHeight}, display: ${this.displayImageWidth}x${this.displayImageHeight}, fileBean: ${this.remoteUrl}`);
          return data;
        }
      }
    });

    this.windowSize();
  }

  private calculateDisplaySize(): void {
    if (!this.componentWidth || !this.componentHeight || !this.imageWidth || !this.imageHeight) {
      return;
    }

    const imageRatio = this.imageWidth / this.imageHeight;
    const containerRatio = this.componentWidth / this.componentHeight;

    if (imageRatio > containerRatio) {
      this.displayImageWidth = this.componentWidth;
      this.displayImageHeight = this.componentWidth / imageRatio;
    } else {
      this.displayImageHeight = this.componentHeight;
      this.displayImageWidth = this.componentHeight * imageRatio;
    }
  }

  build() {
    ImageKnifeComponentV2({
      imageKnifeOption: this.imageKnifeOption
    })
      .width(CommonConstants.FULL_WIDTH)
      .transform(this.matrix ? this.matrix.copy() : matrix4.identity())
      .translate({ x: this.curOffsetX, y: this.curOffsetY })
      .gesture(
        GestureGroup(GestureMode.Parallel,
          TapGesture({ count: 2 })
            .onAction((event: GestureEvent) => {
              this.resetToDefaultScale();
            }),

          PanGesture({ fingers: 1, distance: this.isDisableSwipe ? 3 : 50 })
            .onActionUpdate((event: GestureEvent) => {
              this.isDisableSwipe = this.panGestureUpdate(event);
            })
            .onActionEnd(() => {
              this.gestureEnd();
            }),

          PinchGesture({ fingers: 2, distance: 1 })
            .onActionStart((event: GestureEvent) => {
              this.pinchGestureStart(event);
              if (this.isOverlay) {
                this.isOverlay = false;
              }
            })
            .onActionUpdate((event: GestureEvent) => {
              this.isDisableSwipe = this.pinchGestureUpdate(event);
            })
            .onActionEnd(() => {
              this.pinchGestureEnd();
            })
        )
      )
  }

  private resetToDefaultScale(): void {
    animateTo({
      duration: 300,
      curve: Curve.EaseOut
    }, () => {
      this.curScale = this.defaultScaleValue;
      this.curOffsetX = 0;
      this.curOffsetY = 0;
      this.lastOffsetX = 0;
      this.lastOffsetY = 0;
      this.centerX = 0.5;
      this.centerY = 0.5;

      this.pictureScaling(this.curScale);
      this.evaluateOffsetRange();
    });
  }

  panGestureUpdate(event: GestureEvent): boolean {
    this.onScale(this.lastScale, event.offsetX, event.offsetY);
    this.pictureBoundaryRestriction();
    return this.isDisableSwipe;
  }

  onScale(scale: number, offX: number, offY: number): void {
    if (this.curScale > this.defaultScaleValue) {
      this.curOffsetY = this.lastOffsetY + offY;
      this.curOffsetX = this.lastOffsetX + offX;
    }

    this.isArriveBoundary = false;

    if (!this.isArriveBoundary && this.curScale <= this.defaultScaleValue) {
      this.isDisableSwipe = false;
    } else {
      this.isDisableSwipe = true;
    }
  }

  evaluateCenter(centerX: number, centerY: number): [number, number] {
    let imgDisplayWidth = this.displayImageWidth * this.lastScale;
    let imgDisplayHeight = this.displayImageHeight * this.lastScale;
    let imgX = (this.componentWidth - imgDisplayWidth) / 2 + this.lastOffsetX;
    let imgY = (this.componentHeight - imgDisplayHeight) / 2 + this.lastOffsetY;
    let cX = Math.max((centerX - imgX) / imgDisplayWidth, 0);
    let cY = Math.max((centerY - imgY) / imgDisplayHeight, 0);
    return [cX, cY];
  }

  pinchGestureStart(event: GestureEvent): void {
    this.center = this.evaluateCenter(event.pinchCenterX, event.pinchCenterY);
    this.centerX = 1 - this.center[0];
    this.centerY = 1 - this.center[1];
    this.lastScale = this.curScale;
    this.lastOffsetX = this.curOffsetX;
    this.lastOffsetY = this.curOffsetY;
  }

  pictureScaling(scale: number): void {
    this.matrix = matrix4.identity().scale({
      x: scale,
      y: scale
    }).copy();
  }

  pinchGestureUpdate(event: GestureEvent): boolean {
    let newScale = this.lastScale * event.scale;
    newScale = Math.min(Math.max(newScale, this.defaultScaleValue), this.maxScale);

    this.curScale = newScale;

    if (newScale > this.defaultScaleValue) {
      this.curOffsetY = this.lastOffsetY + (event.offsetY || 0);
      this.curOffsetX = this.lastOffsetX + (event.offsetX || 0);
    }

    this.pictureScaling(this.curScale);

    if (this.curScale > this.defaultScaleValue) {
      this.isDisableSwipe = true;
    }

    this.evaluateOffsetRange();
    this.pictureBoundaryRestriction();

    return this.isDisableSwipe;
  }

  pinchGestureEnd(): void {
    this.lastScale = this.curScale;
    this.lastOffsetX = this.curOffsetX;
    this.lastOffsetY = this.curOffsetY;

    if (this.curScale < this.defaultScaleValue) {
      animateTo({
        duration: 200,
        curve: Curve.EaseOut
      }, () => {
        this.curScale = this.defaultScaleValue;
        this.curOffsetX = 0;
        this.curOffsetY = 0;
        this.lastOffsetX = 0;
        this.lastOffsetY = 0;
        this.pictureScaling(this.curScale);
      });
    } else {
      this.evaluateOffsetRange();
      this.pictureBoundaryRestriction();
    }
  }

  gestureEnd(): void {
    this.lastOffsetX = this.curOffsetX;
    this.lastOffsetY = this.curOffsetY;

    this.evaluateOffsetRange();
    this.pictureBoundaryRestriction();
  }

  windowSize(): void {
    window.getLastWindow(AppUtil.getContext()).then((window: window.Window) => {
      try {
        this.componentWidth = this.getUIContext().px2vp(window.getWindowProperties().windowRect.width);
        this.componentHeight = this.getUIContext().px2vp(window.getWindowProperties().windowRect.height);
        LogUtils.debug(TAG, `Window size: ${this.componentWidth}x${this.componentHeight}`);
        this.calculateDisplaySize();
      } catch (err) {
        LogUtils.error(TAG, `GetWindowProperties failed. Cause code: ${err.code}, message: ${err.message}`);
      }
    }).catch((err: BusinessError) => {
      LogUtils.error(TAG, `GetLastWindow failed. Cause code: ${err.code}, message: ${err.message}`);
    });
  }

  evaluateOffsetRange(): void {
    let scaledImageWidth = this.displayImageWidth * this.curScale;
    let scaledImageHeight = this.displayImageHeight * this.curScale;

    if (scaledImageWidth > this.componentWidth) {
      let maxXOffset = (scaledImageWidth - this.componentWidth) / 2;
      this.maxOffsetX = maxXOffset;
      this.minOffsetX = -maxXOffset;
    } else {
      this.maxOffsetX = 0;
      this.minOffsetX = 0;
    }

    if (scaledImageHeight > this.componentHeight) {
      let maxYOffset = (scaledImageHeight - this.componentHeight) / 2;
      this.maxOffsetY = maxYOffset;
      this.minOffsetY = -maxYOffset;
    } else {
      this.maxOffsetY = 0;
      this.minOffsetY = 0;
    }

    LogUtils.debug(TAG, `Scaled: ${scaledImageWidth}x${scaledImageHeight}, Offset range X:[${this.minOffsetX}, ${this.maxOffsetX}], Y:[${this.minOffsetY}, ${this.maxOffsetY}]`);
  }

  pictureBoundaryRestriction(): void {
    if (this.curOffsetX > this.maxOffsetX) {
      this.curOffsetX = this.maxOffsetX;
      this.isDisableSwipe = false;
      this.isArriveBoundary = true;
    } else if (this.curOffsetX < this.minOffsetX) {
      this.curOffsetX = this.minOffsetX;
      this.isDisableSwipe = false;
      this.isArriveBoundary = true;
    }

    if (this.curOffsetY > this.maxOffsetY) {
      this.curOffsetY = this.maxOffsetY;
    } else if (this.curOffsetY < this.minOffsetY) {
      this.curOffsetY = this.minOffsetY;
    }

    let scaledImageWidth = this.displayImageWidth * this.curScale;
    let scaledImageHeight = this.displayImageHeight * this.curScale;

    if (scaledImageWidth <= this.componentWidth) {
      this.curOffsetX = 0;
    }
    if (scaledImageHeight <= this.componentHeight) {
      this.curOffsetY = 0;
    }
  }
}


posted on 2025-04-24 09:53  带头大哥d小弟  阅读(1)  评论(0)    收藏  举报

刷新页面返回顶部
 
博客园  ©  2004-2026
浙公网安备 33010602011771号 浙ICP备2021040463号-3