HarmonyOS Swiper的翻页与子组件的PanGesture事件冲突?

操作步骤:

我使用 Swiper 希望通过左右翻页 实现 图片翻页查看

而在子组件中 实现了

TapGesture({ count: 2 }) 实现放大缩小

PinchGesture({ fingers: 2, distance: 1 }) 实现双指捏合缩放图片

PanGesture({ fingers: 1 }) 实现滑动图片

当我添加 PanGesture 后 我的Swiper 却不能通过左右滑动 来翻页了

请问该怎么实现

import { matrix4, router, window } from '@kit.ArkUI'
import { CommonConstants } from '../constants/CommonConstants'
import { Logger } from '@yunkss/eftool'
import { image } from '@kit.ImageKit'
import { windowSizeManager } from 'ImageViewerView/src/main/ets/utils/Managers'
import { runWithAnimation } from 'ImageViewerView/src/main/ets/utils/FuncUtils'

export function pushImagePreview(data:ImagePreviewData) {
  router.pushUrl({
    url: 'pages/ImagePreviewPage',
    params: data
  })
}

interface ImagePreviewData {
  images: ResourceStr[]
  index:number
}
@Entry
@Component
struct ImagePreviewPage {
  data:ImagePreviewData = router.getParams() as ImagePreviewData
  private DISPLAY_COUNT: number = 1
  private MIN_SCALE: number = 0.75

  @State opacityList: number[] = []
  @State scaleList: number[] = []
  @State translateList: number[] = []
  @State zIndexList: number[] = []

  @State isEnableSwipe: boolean = true;
  @Provide bgc: Color = Color.Black;

  aboutToAppear(): void {
    for (let i = 0; i < this.data.images.length; i++) {
      this.opacityList.push(1.0)
      this.scaleList.push(1.0)
      this.translateList.push(0.0)
      this.zIndexList.push(0)
    }
  }

  build() {
    Swiper() {
      ForEach(this.data.images, (image: string, index: number) => {
        // Image(image)
        //   .objectFit(ImageFit.ScaleDown)
        //   .width(CommonConstants.THOUSANDTH_1000)
        //   .height(CommonConstants.THOUSANDTH_1000)
        // .sharedTransition(''+index,{duration:300,curve:Curve.Sharp})
        //   // 自定义动画变化透明度、缩放页面、抵消系统默认位移、渲染层级等
        // .opacity(this.opacityList[index])
        // .scale({ x: this.scaleList[index], y: this.scaleList[index] })
        // .translate({ x: this.translateList[index] })
        // .zIndex(this.zIndexList[index])
        ImageItemView({
          url:image,
          isEnableSwipe:this.isEnableSwipe
        })
          .width(CommonConstants.THOUSANDTH_1000)
          .height(CommonConstants.THOUSANDTH_1000)
          .opacity(this.opacityList[index])
          .scale({ x: this.scaleList[index], y: this.scaleList[index] })
          .translate({ x: this.translateList[index] })
          .zIndex(this.zIndexList[index])

      })
    }
    .width(CommonConstants.THOUSANDTH_1000)
    .height(CommonConstants.THOUSANDTH_1000)
    .backgroundColor(this.bgc)
    .loop(false)
    .indicator(true)
    .displayCount(this.DISPLAY_COUNT, true)
    .index(this.data.index)
    .disableSwipe(!this.isEnableSwipe)
    .onClick(() => {
      this.bgc = this.bgc === Color.White ? Color.Black : Color.White;
    })
    .customContentTransition({
      // 页面移除视窗时超时1000ms下渲染树
      timeout: 1000,
      // 对视窗内所有页面逐帧回调transition,在回调中修改opacity、scale、translate、zIndex等属性值,实现自定义动画
      transition: (proxy: SwiperContentTransitionProxy) => {
        if (proxy.position <= proxy.index % this.DISPLAY_COUNT || proxy.position >= this.DISPLAY_COUNT + proxy.index % this.DISPLAY_COUNT) {
          // 同组页面往左滑或往右完全滑出视窗外时,重置属性值
          this.opacityList[proxy.index] = 1.0
          this.scaleList[proxy.index] = 1.0
          this.translateList[proxy.index] = 0.0
          this.zIndexList[proxy.index] = 0
        } else {
          // 同组页面往右滑且未滑出视窗外时,对同组中左右两个页面,逐帧根据position修改属性值,实现两个页面往Swiper中间靠拢并透明缩放的自定义切换动画
          if (proxy.index % this.DISPLAY_COUNT === 0) {
            this.opacityList[proxy.index] = 1 - proxy.position / this.DISPLAY_COUNT
            this.scaleList[proxy.index] = this.MIN_SCALE + (1 - this.MIN_SCALE) * (1 - proxy.position / this.DISPLAY_COUNT)
            this.translateList[proxy.index] = - proxy.position * proxy.mainAxisLength + (1 - this.scaleList[proxy.index]) * proxy.mainAxisLength / 2.0
          } else {
            this.opacityList[proxy.index] = 1 - (proxy.position - 1) / this.DISPLAY_COUNT
            this.scaleList[proxy.index] = this.MIN_SCALE + (1 - this.MIN_SCALE) * (1 - (proxy.position - 1) / this.DISPLAY_COUNT)
            this.translateList[proxy.index] = - (proxy.position - 1) * proxy.mainAxisLength - (1 - this.scaleList[proxy.index]) * proxy.mainAxisLength / 2.0
          }
          this.zIndexList[proxy.index] = -1
        }
      }
    })
    // .onContentDidScroll((selectedIndex: number, index: number, position: number, mainAxisLength: number) => {
    //   // 监听Swiper页面滑动事件,在该回调中可以实现自定义导航点切换动画等
    //   //Logger.info("onContentDidScroll selectedIndex: " , selectedIndex + ", index: " + index + ", position: " + position + ", mainAxisLength: " + mainAxisLength)
    // })

  }

}



@Component
export struct ImageItemView {

  @Prop url:ResourceStr
  @Link isEnableSwipe: boolean; // TODO:需求:多图切换

  @Consume private bgc:Color
  @State isEnableOffset: boolean = false;
  @State imageScaleInfo: ScaleModel = new ScaleModel(1.0, 1.0, 1.5, 0.3);
  @State imageOffsetInfo: OffsetModel = new OffsetModel(0, 0);
  @State matrix: matrix4.Matrix4Transit = matrix4.identity().copy();
  @State fitWH: "width" | "height" | undefined = undefined; // 表示当前图片是根据宽度适配还是高度适配
  @State imageDefaultSize: image.Size = { width: 0, height: 0 }; // 图片默认大小,即,与屏幕大小最适配的显示大小
  imageWHRatio: number = 0; // 图片原始宽高比



  /**
   * 根据图片宽高比及窗口大小计算图片的默认宽高,即,图片最适配屏幕的大小
   * @param imageWHRatio:图片原始宽高比
   * @param size:窗口大小{with:number,height:number}
   * @returns image.Size
   */
  calcImageDefaultSize(imageWHRatio: number, size: window.Size): image.Size {
    let width = 0
    let height = 0;
    if (imageWHRatio > size.width / size.height) {
      // 图片宽高比大于屏幕宽高比,图片默认以屏幕宽度进行显示
      width = size.width;
      height = size.width / imageWHRatio;
    } else {
      height = size.height;
      width = size.height * imageWHRatio;
    }
    return { width: width, height: height };
  }

  /**
   * TODO:知识点:根据图片大小(宽高<=屏幕宽高)和屏幕大小计算图片放大适配屏幕进行显示的缩放倍率
   * @param imageSize:图片当前大小
   * @param windowSize:窗口大小
   * @returns:缩放倍率
   */
  calcFitScaleRatio(imageSize: image.Size, windowSize: window.Size): number {
    let ratio: number = 1.0;
    if (windowSize.width > imageSize.width) {
      ratio = windowSize.width / imageSize.width;
    } else {
      ratio = windowSize.height / imageSize.height;
    }
    return ratio;
  }

  /**
   * 设置当前图片的相关信息:uri、whRatio、pixelMap、fitWH、defaultSize、maxScaleValue
   * TODO:知识点:提前获取图片的信息,以进行Image组件的尺寸设置及后续的相关计算
   */
  initCurrentImageInfo(width:number,height:number): void {
    this.matrix = matrix4.identity().copy();

    this.imageWHRatio = width / height;
    this.imageDefaultSize = this.calcImageDefaultSize(this.imageWHRatio, windowSizeManager.get());
    if (this.imageDefaultSize.width === windowSizeManager.get().width) {
      this.fitWH = "width";
    } else {
      this.fitWH = "height";
    }
    this.imageScaleInfo.maxScaleValue += this.fitWH === "width" ?
      (windowSizeManager.get().height / this.imageDefaultSize.height) :
      (windowSizeManager.get().width / this.imageDefaultSize.width);

    this.isEnableOffset = false;
    this.imageScaleInfo.reset();
    this.imageOffsetInfo.reset();


    /*
    this.matrix = matrix4.identity().copy();
    const imageSource: image.ImageSource = image.createImageSource(this.imageUri);
    imageSource.getImageInfo(0).then((data: image.ImageInfo) => {
      this.imageWHRatio = data.size.width / data.size.height;
      this.imageDefaultSize = this.calcImageDefaultSize(this.imageWHRatio, windowSizeManager.get());
      if (this.imageDefaultSize.width === windowSizeManager.get().width) {
        this.fitWH = "width";
      } else {
        this.fitWH = "height";
      }
      this.imageScaleInfo.maxScaleValue += this.fitWH === "width" ?
        (windowSizeManager.get().height / this.imageDefaultSize.height) :
        (windowSizeManager.get().width / this.imageDefaultSize.width);
    }).catch((err: BusinessError) => {
      console.error(`[error][getImageInfo]${err.message}`);
    });
    imageSource.createPixelMap().then((data: image.PixelMap) => {
      this.imagePixelMap = data;
    }).catch((err: BusinessError) => {
      console.error(`[error][createPixelMap]${err.message}`);
    });
    this.isEnableOffset = false;
    this.imageScaleInfo.reset();
    this.imageOffsetInfo.reset();
    */
  }

  /**
   * 在图片消失时,将当前图片的信息设置为默认值
   */
  resetCurrentImageInfo(): void {
    this.imageScaleInfo.reset();
    this.imageOffsetInfo.reset();
    this.matrix = matrix4.identity().copy();
  }

  /**
   * TODO:需求:在偏移时评估是否到达边界,以便进行位移限制与图片的切换
   * @returns:长度为4的boolean数组,表示上下左右是否到达边界
   */
  evaluateBound(): boolean[] {
    return [false, false, false, false];
  }

  build() {
    Stack() {
      Image(this.url)// TODO:知识点:宽高只根据其尺寸设置一个,通过保持宽高比来设置另一个属性
        .width(this.fitWH === "width" ? '100%' : undefined)
        .height(this.fitWH === "height" ? '100%' : undefined)
        .aspectRatio(this.imageWHRatio)
        .objectFit(ImageFit.Cover)// TODO:知识点:保持宽高比进行缩放,可以超出父组件,以便实现多图切换的增强功能
        .autoResize(false)
        .transform(this.matrix)// TODO:知识点:通过matrix控制图片的缩放
        .defaultFocus(true)
        .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
        .offset({
          // TODO:知识点:通过offset控制图片的偏移
          x: this.imageOffsetInfo.currentX,
          y: this.imageOffsetInfo.currentY
        })
        .onComplete((event) => {
          Logger.error('图片加载完成',event?.width + '   ' + event?.height)
          this.initCurrentImageInfo(event?.width ?? 0,event?.height ?? 0)
        })
    }
    .onBlur(() => {
      this.resetCurrentImageInfo();
    })
    .backgroundColor(this.bgc)
    .alignContent(Alignment.Center)
    .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
    .clip(true)
    .width('100%')
    .height('100%')
    .gesture(
      GestureGroup(
        GestureMode.Exclusive,
        // TODO:知识点:双击切换图片大小
        TapGesture({ count: 2 })
          .onAction(() => {
            let fn: Function;
            // 已经是放大状态下,双击缩小
            if (this.imageScaleInfo.scaleValue > this.imageScaleInfo.defaultScaleValue) {
              fn = () => {
                this.isEnableSwipe = true;
                this.imageScaleInfo.reset();
                this.imageOffsetInfo.reset();
                this.matrix = matrix4.identity().copy();
              };
            } else {
              // 已经是缩小状态,双击放大
              fn = () => {
                this.isEnableSwipe = false;
                const ratio: number = this.calcFitScaleRatio(this.imageDefaultSize, windowSizeManager.get());
                this.imageScaleInfo.scaleValue = ratio;
                this.imageOffsetInfo.reset();
                this.matrix = matrix4.identity().scale({
                  x: ratio,
                  y: ratio,
                }).copy();
                this.imageScaleInfo.stash();
              }
            }
            runWithAnimation(fn);
          }),
        // 单击切换背景色
        TapGesture({ count: 1 })
          .onAction(() => {
            runWithAnimation(() => {
              this.bgc = this.bgc === Color.White ? Color.Black : Color.White;
            });
          }),
        // TODO:知识点:双指捏合缩放图片
        PinchGesture({ fingers: 2, distance: 1 })
          .onActionUpdate((event: GestureEvent) => {
            this.imageScaleInfo.scaleValue = this.imageScaleInfo.lastValue * event.scale;
            // TODO:知识点:缩放时不允许大于最大缩放因子+额外缩放因子,不允许小于默认大小-额外缩放因子,额外缩放因子用于提升用户体验
            if (this.imageScaleInfo.scaleValue > this.imageScaleInfo.maxScaleValue *
              (1 + this.imageScaleInfo.extraScaleValue)
            ) {
              this.imageScaleInfo.scaleValue = this.imageScaleInfo.maxScaleValue *
                (1 + this.imageScaleInfo.extraScaleValue);
            }
            if (this.imageScaleInfo.scaleValue < this.imageScaleInfo.defaultScaleValue *
              (1 - this.imageScaleInfo.extraScaleValue)) {
              this.imageScaleInfo.scaleValue = this.imageScaleInfo.defaultScaleValue *
                (1 - this.imageScaleInfo.extraScaleValue);
            }
            // TODO:知识点:matrix默认缩放中心为组件中心
            this.matrix = matrix4.identity().scale({
              x: this.imageScaleInfo.scaleValue,
              y: this.imageScaleInfo.scaleValue,
            }).copy();
            console.debug(this.imageScaleInfo.toString());
          })
          .onActionEnd((event: GestureEvent) => {
            /**
             * TODO:知识点:当小于默认大小时,恢复为默认大小
             */
            if (this.imageScaleInfo.scaleValue < this.imageScaleInfo.defaultScaleValue) {
              runWithAnimation(() => {
                this.imageScaleInfo.reset();
                this.imageOffsetInfo.reset();
                this.matrix = matrix4.identity().copy();
              })
            }
            // TODO:知识点:当大于最大缩放因子时,恢复到最大
            if (this.imageScaleInfo.scaleValue > this.imageScaleInfo.maxScaleValue) {
              runWithAnimation(() => {
                this.imageScaleInfo.scaleValue = this.imageScaleInfo.maxScaleValue;
                this.matrix = matrix4.identity()
                  .scale({
                    x: this.imageScaleInfo.maxScaleValue,
                    y: this.imageScaleInfo.maxScaleValue
                  });
              })
            }
            this.imageScaleInfo.stash();
          }),
        // TODO:知识点:滑动图片
        PanGesture({ fingers: 1 })// TODO:需求:默认大小下左右滑动应当是切换图片
          .onActionUpdate((event: GestureEvent) => {
            if (this.imageScaleInfo.scaleValue === this.imageScaleInfo.defaultScaleValue) {
              // 默认大小下不允许移动
              return;
            }
            this.imageOffsetInfo.currentX = this.imageOffsetInfo.lastX + event.offsetX;
            this.imageOffsetInfo.currentY = this.imageOffsetInfo.lastY + event.offsetY;

            Logger.error('拖动', this.imageOffsetInfo.currentX + '<==>' + this.imageOffsetInfo.currentY )
          })
          .onActionEnd((event: GestureEvent) => {
            this.imageOffsetInfo.stash();
          })
      ),
    )
  }
}


@Observed
class ScaleModel {
  /**
   * scaleValue: 本次缩放因子,用于控制图片的大小显示
   * lastValue:记录上次缩放完后的缩放因子
   * defaultMaxScaleValue:默认的最大放大值
   * defaultScaleValue:默认缩放值,1
   */
  public scaleValue: number;
  public lastValue: number;
  public maxScaleValue: number;
  public extraScaleValue: number;
  public readonly defaultScaleValue: number = 1;

  constructor(scaleValue: number = 1.0, lastValue: number = 1.0,
    maxScaleValue: number = 1.5, extraScaleValue: number = 0.2) {
    this.scaleValue = scaleValue;
    this.lastValue = lastValue;
    this.maxScaleValue = maxScaleValue;
    this.extraScaleValue = extraScaleValue;
  }

  reset(): void {
    this.scaleValue = this.defaultScaleValue;
    this.lastValue = this.scaleValue;
  }

  stash(): void {
    this.lastValue = this.scaleValue;
  }

  toString(): string {
    return `[scaleValue: ${this.scaleValue} lastValue: ${this.lastValue}]`;
  }
}

@Observed
class OffsetModel {
  public currentX: number;
  public currentY: number;
  public lastX: number = 0;
  public lastY: number = 0;

  constructor(currentX: number = 0, currentY: number = 0) {
    this.currentX = currentX;
    this.currentY = currentY;
  }

  reset(): void {
    this.currentX = 0;
    this.currentY = 0;
    this.lastX = 0;
    this.lastY = 0;
  }

  stash(): void {
    this.lastX = this.currentX;
    this.lastY = this.currentY;
  }

  toString(): string {
    return `[currentX: ${this.currentX} currentY: ${this.currentY} lastX: ${this.lastX} lastY: ${this.lastY}]`;
  }
}
阅读 403
1 个回答

可利用Watch属性监听imageScaleInfo变化从而改变directions

if(this.imageScaleInfo.scaleValue === this.imageScaleInfo.defaultScaleValue){ this.directions = PanDirection.None }
else { this.directions = PanDirection.All }

就顺利解决放大后滑动内部图片,从而只对内部图片进行拖拽,关闭Swiper的拖拽。当图片还原后就对Swiper进行拖拽,从而关闭对图片的推拽。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进