HarmonyOS 鸿蒙Next中如何正确使用Tabs嵌套

HarmonyOS 鸿蒙Next中如何正确使用Tabs嵌套

鸿蒙中如何正确使用Tabs嵌套

问题描述:

开发中经常会用到tabs嵌套tabs的情况,如果我们直接使用tabs嵌套tabs,会发现在内部tabs滑动到第一个和最后一个时,如果再次滑动,外部tabs不会响应事件,不会跟随滑动。

问题解决方法

使用最新api13提供的手势拦截增强功能,

onGestureRecognizerJudgeBegin

注意:一定是接受两个参数,第二个参数设置为true,

如何使用api13新增方法

如果你按照上面官网把代码复制到自己的编译器大概率是会报错,因为自己本地编译器不支持api13,此时编译器版本为构建版本:5.0.3.806,构建 2024年9月11日,

onGestureRecognizerJudgeBegin 方法只接受一个参数,

如果不支持大家可以打开自己的编译器选择DevEco Studio菜单,选择Preferences(首选项),点击OpenHarmony SDK,查看自己的编译器是否可以下载API Version13

如果你的编译器也不支持,请执行以下步骤

第一步

请下载最新版本编译器, 点击访问最新下载地址

下载完最新版编译器之后你再执行上面操作发现有了API Version13的版本

选中点击确定会自动取下载,下载完之后代码就不会再报错,

第二步

更新项目"compatibleSdkVersion": “5.0.1(13)”,位置位于根目录下build-profile.json5文件

注意:如果你是模拟器这个时候有可能模拟器版本过低导致高版本app无法再低版本模拟器上运行

如何使用 onGestureRecognizerJudgeBegin

接受两个参数第一个是一个回调函数,第二个是boolean类型标示是否暴漏内部手势,这里传递true,

GestureRecognizerJudgeBeginCallback 可以获取手势事件和手势事件可传递的控件,这个方法是有返回值的,简单理解

return GestureJudgeResult.CONTINUE; 标示事件又自己处理,

return GestureJudgeResult.REJECT; 事件交给上层去处理

下面我们看一下官方代码如何实现吧:

// xxx.ets
@Entry
@Component
struct Index {
  @State currentIndex: number = 0
  @State selectedIndex: number = 0
  @State fontColor: string = '#182431'
  @State selectedFontColor: string = '#007DFF'
  innerSelectedIndex: number = 0 // 记录内层Tabs的索引
  controller?: TabsController = new TabsController();
  @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() {
          Tabs() {
            TabContent() {
              Column().width('100%').height('100%').backgroundColor(Color.Blue)
            }.tabBar(new SubTabBarStyle('blue'))
            TabContent() {
              Column().width('100%').height('100%').backgroundColor(Color.Pink)
            }.tabBar(new SubTabBarStyle('pink'))
          }
          .onAnimationStart((index: number, targetIndex: number) => {
            console.info('ets onGestureRecognizerJudgeBegin child:' + targetIndex)
            this.innerSelectedIndex = targetIndex
          })
          .onGestureRecognizerJudgeBegin((event: BaseGestureEvent, current: GestureRecognizer,
            others: Array<GestureRecognizer>): GestureJudgeResult => { // 在识别器即将要成功时,根据当前组件状态,设置识别器使能状态
            console.info('ets onGestureRecognizerJudgeBegin child')
            if (current) {
              let target = current.getEventTargetInfo();
              if (target && current.isBuiltIn() && current.getType() == GestureControl.GestureType.PAN_GESTURE) {
                console.info('ets onGestureRecognizerJudgeBegin child PAN_GESTURE')
                let swiperTaget = target as ScrollableTargetInfo
                if (swiperTaget instanceof ScrollableTargetInfo) {
                  console.info('ets onGestureRecognizerJudgeBegin child PAN_GESTURE isEnd: ' + swiperTaget.isEnd() + ' isBegin: ' + swiperTaget.isBegin())
                }
                if (swiperTaget instanceof ScrollableTargetInfo &&
                    ((swiperTaget.isEnd() || this.innerSelectedIndex === 1) || // 此处判断swiperTaget.isEnd()或innerSelectedIndex === 内层Tabs的总数 - 1,表明内层Tabs滑动到尽头
                      (swiperTaget.isBegin() || this.innerSelectedIndex === 0))) { // 此处判断swiperTaget.isBegin()或innerSelectedIndex === 0,表明内层Tabs滑动到开头
                  let panEvent = event as PanGestureEvent;
                  console.log('pan direction:' + panEvent.offsetX + ' begin:' + swiperTaget.isBegin() + ' end:' +
                              swiperTaget.isEnd() + ' index:' + this.innerSelectedIndex)
                  if (panEvent && panEvent.offsetX < 0 && (swiperTaget.isEnd() || this.innerSelectedIndex === 1)) {
                    console.info('ets onGestureRecognizerJudgeBegin child reject end')
                    return GestureJudgeResult.REJECT;
                  }
                  if (panEvent && panEvent.offsetX > 0 && (swiperTaget.isBegin() || this.innerSelectedIndex === 0)) {
                    console.info('ets onGestureRecognizerJudgeBegin child reject begin')
                    return GestureJudgeResult.REJECT;
                  }
                }
              }
            }
            return GestureJudgeResult.CONTINUE;
          }, true)
        }.tabBar(this.tabBuilder(1, 'blue and pink'))
        TabContent() {
          Column().width('100%').height('100%').backgroundColor(Color.Brown)
        }.tabBar(this.tabBuilder(2, 'brown'))
      }
      .onAnimationStart((index: number, targetIndex: number, event: TabsAnimationEvent) => {
        // 切换动画开始时触发该回调。目标页签显示下划线。
        this.selectedIndex = targetIndex
      })
    }
  }
}

以上是最简单的双tabs嵌套实现。

如果内部 tabs 显示的是一个 web,此时 web 也需要使用 onGestureRecognizerJudgeBegin 来做手势判断。


更多关于HarmonyOS 鸿蒙Next中如何正确使用Tabs嵌套的实战教程也可以访问 https://www.itying.com/category-93-b0.html

2 回复

在HarmonyOS Next中使用Tabs嵌套,需遵循以下步骤:

  1. 外层Tabs使用TabContent组件布局
  2. 内层Tabs作为外层TabContent的子组件
  3. 每个TabContent需设置对应高度
  4. 嵌套Tabs需分别设置controller控制切换

示例代码结构:

Tabs({ controller: outerController }) {
  TabContent() {
    Tabs({ controller: innerController }) {
      TabContent() { /* 内容 */ }
    }
  }
}

注意避免多层嵌套导致的性能问题,推荐不超过2层嵌套。

更多关于HarmonyOS 鸿蒙Next中如何正确使用Tabs嵌套的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


在HarmonyOS Next中实现Tabs嵌套的正确方式是通过API13提供的手势拦截增强功能。关键点如下:

  1. 核心解决方案是使用onGestureRecognizerJudgeBegin方法,该方法需要:

    • 接受两个参数(回调函数和boolean值)
    • 第二个参数必须设为true以暴露内部手势
  2. 实现逻辑:

    • 当内部Tabs滑动到边界时(通过isBegin()/isEnd()判断)
    • 根据滑动方向(offsetX正负值)决定是否将事件传递给外层Tabs
    • 返回GestureJudgeResult.REJECT让外层处理手势
  3. 开发环境要求:

    • 必须使用支持API13的DevEco Studio版本
    • build-profile.json5中设置"compatibleSdkVersion": "5.0.1(13)"
  4. 注意事项:

    • 模拟器也需要支持API13
    • Web组件嵌套时同样需要实现手势判断
    • 通过innerSelectedIndex记录内部Tabs位置状态

这种实现方式确保了内外层Tabs的手势滑动能正确联动,解决了边界滑动不响应的问题。

回到顶部