HarmonyOS鸿蒙Next中Scroll API20图片浏览与Tabs左右滑动冲突的解决办法

HarmonyOS鸿蒙Next中Scroll API20图片浏览与Tabs左右滑动冲突的解决办法 cke_172.png

Scroll API20这玩意图片浏览好是好用,但是Tabs跟它搭配左右滑动会和Tabs的左右滑动切换冲突啊,有没有好的解决办法?使用手势api来判断过于复杂而且tabs切换没有动画过于生硬。。

还有就是放大缩小到极限值回弹的时机似乎没有回调来搞振动提示之类的


更多关于HarmonyOS鸿蒙Next中Scroll API20图片浏览与Tabs左右滑动冲突的解决办法的实战教程也可以访问 https://www.itying.com/category-93-b0.html

11 回复

参考嵌套场景下拦截内部容器手势,通过 onGestureRecognizerJudgeBegin 进行手势拦截,判断是否滑动到左右边界,决定是否处理手势。解决滑动手势冲突。

cke_12073.gif

@Entry
@Component
struct Index {
  @State currentIndex: number = 0;
  @State selectedIndex: number = 0;
  @State fontColor: string = '#182431';
  @State selectedFontColor: string = '#007DFF';
  controller?: TabsController = new TabsController();
  @State currScale: number = 1;
  private scroller: Scroller = new Scroller();

  @Builder
  tabBuilder(index: number, name: string) {
    Column() {
      Text(name)
        .fontColor(
          this.selectedIndex === index
            ? this.selectedFontColor
            : this.fontColor
        )
        .fontSize(16)
        .fontWeight(this.selectedIndex === index ? 500 : 400)
        .lineHeight(22)
        .margin({ top: 17, bottom: 7 })

      Divider()
        .strokeWidth(2)
        .color('#007DFF')
        .opacity(this.selectedIndex === index ? 1 : 0)
    }
    .width('100%')
  }

  build() {
    Column() {
      Tabs({ barPosition: BarPosition.Start, index: this.currentIndex, controller: this.controller }) {
        TabContent() {
          Column()
            .width('100%')
            .height('100%')
            .backgroundColor(Color.Green)
        }
        .tabBar(this.tabBuilder(0, 'green'))

        TabContent() {
          this.scrollBuilder()
        }
        .tabBar(this.tabBuilder(1, 'image'))

        TabContent() {
          Column()
            .width('100%')
            .height('100%')
            .backgroundColor(Color.Brown)
        }
        .tabBar(this.tabBuilder(2, 'brown'))
      }
      .width('100%')
      .height('100%')
      .onAnimationStart((index: number, targetIndex: number, event: TabsAnimationEvent) => {
        this.selectedIndex = targetIndex;
      })
    }
    .width('100%')
    .height('100%')
  }

  @Builder
  scrollBuilder() {
    Scroll(this.scroller) {
      Image($r('app.media.startIcon'))
        .width('100%')
        .objectFit(ImageFit.Contain)
    }
    .width('100%')
    .height('100%')
    .scrollable(ScrollDirection.FREE)
    .minZoomScale(1)
    .maxZoomScale(2)
    .enableBouncesZoom(true)
    .onDidZoom((scale: number) => {
      this.currScale = scale;
    })
    .onGestureRecognizerJudgeBegin(
      (
        event: BaseGestureEvent,
        current: GestureRecognizer,
        others: Array<GestureRecognizer>
      ): GestureJudgeResult => {

        if (!current) {
          return GestureJudgeResult.CONTINUE;
        }

        if (current.isBuiltIn() && current.getType() === GestureControl.GestureType.PAN_GESTURE) {
          const pan = event as PanGestureEvent;

          // 横向滑动
          const isHorizontal = Math.abs(pan.offsetX) > Math.abs(pan.offsetY);
          if (!isHorizontal) {
            return GestureJudgeResult.CONTINUE;
          }

          // 未放大
          if (this.currScale <= 1) {
            return GestureJudgeResult.REJECT;
          }

          // 当前 scroll 偏移
          const xOffset = this.scroller.currentOffset().xOffset;

          // 左滑
          if (pan.offsetX < 0) {
            // 已经到最右
            if (this.scroller.isAtEnd()) {
              return GestureJudgeResult.REJECT;
            }
          }

          // 右滑
          if (pan.offsetX > 0) {
            // 已经到最左
            if (xOffset <= 0) {
              return GestureJudgeResult.REJECT;
            }
          }

          // 中间区域 Scroll
          return GestureJudgeResult.CONTINUE;
        }

        return GestureJudgeResult.CONTINUE;
      },
      true
    )
  }
}

更多关于HarmonyOS鸿蒙Next中Scroll API20图片浏览与Tabs左右滑动冲突的解决办法的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


这个场景本质是同向水平手势竞争,不建议只靠 nestedScroll 解决。nestedScroll 更适合父子滚动容器的联动;Tabs 页切换和图片浏览/缩放属于“本次水平 pan 到底由谁消费”的问题。

比较稳的处理方式是做一个手势状态机:

  1. 图片未放大、且处在图片内容左右边界时,才允许 Tabs 响应左右滑页。
  2. 图片已放大、正在拖拽图片,或图片还没有滑到边界时,让图片容器优先响应,Tabs 暂时不处理这次水平手势。
  3. 如果只是为了避免误触,也可以在图片预览打开期间关闭手势切 Tab,改为通过缩略图/按钮/绑定 index 控制 Tabs,退出预览后恢复。

ArkUI 侧可以参考手势冲突处理思路:简单场景用 priorityGesture / parallelGesture 控制图片容器和外层 Tabs 的优先级;复杂场景用手势拦截增强,在 shouldBuiltInRecognizerParallelWithonGestureRecognizerJudgeBegin 中根据图片是否到边界动态决定哪个识别器可用。

缩放到最大/最小值的“回弹极限”如果没有直接回调,可以在缩放回调里自己维护 scale,当本次计算值首次越过 minScale/maxScale 时触发一次振动提示,并做去抖,避免每帧都震。

可以参考一下官方说明:

https://developer.huawei.com/consumer/cn/doc/architecture-guides/convenient-life-v1_2-ts_7-0000002263474574

@Entry
@Component
struct TabsGridDemo {
  tabsController: TabsController = new TabsController();
  gridData: Resource[] = [];
  @State selectIndex: number = 0;


  @Builder
  tabContent(nam: string, targetIndex: number) {
    Text(nam)
      .fontColor(this.selectIndex === targetIndex ? '#0a59f7' : '#99000000')
  }


  aboutToAppear(): void {
    for (let i = 1; i <= 10; i++) {
      this.gridData.push($r(`app.media.${i}`));
    }
  }


  build() {
    Column() {
      Tabs({ controller: this.tabsController }) {
        TabContent() {
          Column() {
            Grid() {
              ForEach(this.gridData, (item: Resource) => {
                GridItem() {
                  Image(item)
                    .height(150)
                    .width(150)
                };
              });
            }
            .height('25%')
            .columnsGap(16)
            .rowsTemplate('1fr')
            // 设置Grid优先滚动
            .nestedScroll({
              scrollForward: NestedScrollMode.SELF_FIRST,
              scrollBackward: NestedScrollMode.SELF_FIRST,
            });
          }
          .width('100%')
          .height('100%')
          .padding({
            left: 20
          })
        }.tabBar(this.tabContent('首页1', 0));


        TabContent() {
        }.tabBar(this.tabContent('首页2', 1));
      }
      .onSelected((index: number) => {
        this.selectIndex = index;
      })


    }.height('100%').width('100%');
  }
}

试过了,没用,场景不一样

我记得有个这个属性:设置与父组件的滚动联动

你把Scroll设置一下应该这个属性值应该就没问题了

有要学HarmonyOS AI的同学吗,联系我:https://www.itying.com/goods-1206.html,

设置了,没用

在HarmonyOS Next中,解决Scroll(API 20)图片浏览与Tabs左右滑动冲突,可在Scroll组件上设置nestedScroll属性为NestedScrollMode.PARENT_FIRST,或通过onTouchIntercept回调拦截手势。同时确保Tabs组件edgeEffectEdgeEffect.NONE,并调整scrollable属性控制滑动方向。另外,检查tabsController与Scroll的scrollParent参数。

Scroll 图片浏览(如双指缩放平移)与 Tabs 左右滑动的冲突,本质是父容器 Tabs 和子组件 Scroll 横向手势竞争。
最简方案是利用 ArkUI 的 嵌套滚动机制:在 Scroll 上设置 nestedScrollNestedScrollMode.SELF_FIRSTSELF_ONLY,当图片未放大到可横向滚动时,Scroll 会把剩余滑动量传递给父级 Tabs,从而在图片边界自然触发选项卡切换,且保持原生动画。无需手动判断手势,代码侵入小。
更直接的方式是动态控制 Tabs 的 scrollable 属性:进入图片浏览模式(如双击放大)时设为 false,退出全屏或恢复原始尺寸时再恢复,彻底屏蔽切换。
关于缩放至极限无震动回调:可监听缩放手势的 onActionEnd,结合当前缩放比例判断是否已达 max/min,然后调用 vibrator 模块触发短震。虽比内置回调稍繁琐,但能保持体验一致。

回到顶部