# 多级Tabs嵌套滑动

#### 问题现象

Tabs组件嵌套Tabs，在正常情况下，当子组件滑动到边缘时不会继续滑动到下一个Tab，如何实现父组件继续滑动到下一个页签？

效果预览：

#### 背景知识

*   Tabs是一种通过页签进行内容视图切换的容器组件，每个页签对应一个内容视图，该组件一方面可以提升查找信息的效率，另一方面也能精简用户单次获取到的信息量。
*   PanGesture是HarmonyOS提供的滑动手势事件，当滑动的最小距离达到设定的最小值时触发该事件。

#### 解决方案

*   方案一：使用PanGesture方法切换到外部Tabs。
    
    1.  创建currentIndex属性，该属性在Tabs滑动时，与当前页面中Tabs的index索引值作比较，达到突出显示当前页签的效果。
    2.  为处于边缘的二级Tabs中的TabContent添加gesture方法，处于左边缘的TabContent向左滑动时，触发changeIndex方法，回到相邻的一级Tabs页面，处于右边缘的TabContent向右滑动时同理。
    
    示例代码如下：
    
    ```
    @Entry
    @Component
    struct MultiTabs {
      @State currentIndex: number = 1;
      @State subCurrentIndex: number = 0;
      private fontColor: string = '#ff4e5f60';
      private selectedFontColor: string = '#0A59F7';
      private controller: TabsController = new TabsController();
      private subController: TabsController = new TabsController();
      private panOptionLeft: PanGestureOptions = new PanGestureOptions({ direction: PanDirection.Left });
      private panOptionRight: PanGestureOptions = new PanGestureOptions({ direction: PanDirection.Right });
    
      @Builder
      tabBuilder(index: number, name: string) {
        Column() {
          Text(name)
            .width('100%')
            .height('36vp')
            .textAlign(TextAlign.Center)
            .fontColor(this.currentIndex === index ? Color.Black : this.fontColor)
            .fontSize(16)
            .fontWeight(this.currentIndex === index ? 500 : 400)
            .lineHeight(22)
            .backgroundColor(this.currentIndex === index ? Color.White : '#E5E5EA')
            .borderRadius('50vp');
        }
        .backgroundColor('#E5E5EA')
        .borderRadius({
          topLeft: index == 0 ? 50 : 0,
          bottomLeft: index == 0 ? 50 : 0,
          topRight: index == 2 ? 50 : 0,
          bottomRight: index == 2 ? 50 : 0
        })
        .margin({ top: 5 })
        .padding({
          left: 2,
          right: 2,
          top: 2,
          bottom: 2
        });
      }
    
      @Builder
      subTabBuilder(index: number, name: string) {
        Column() {
          Text(name)
            .fontColor(this.subCurrentIndex === index ? this.selectedFontColor : this.fontColor)
            .fontSize(16)
            .fontWeight(this.subCurrentIndex === index ? 500 : 400)
            .lineHeight(22);
          Divider()
            .width('50vp')
            .strokeWidth(2)
            .color(this.selectedFontColor)
            .opacity(this.subCurrentIndex === index ? 1 : 0);
        }
        .width('80%');
      }
    
      build() {
        Column() {
          // 一级Tab
          Tabs({ barPosition: BarPosition.Start, index: this.currentIndex, controller: this.controller }) {
            TabContent() {
              Column() {
                Text('首页内容');
              }
              .width('100%')
              .height('100%')
              .borderRadius(12)
              .backgroundColor('#F1F3F5')
              .justifyContent(FlexAlign.Center)
              .alignItems(HorizontalAlign.Center);
            }
            .tabBar(this.tabBuilder(0, '首页'))
            .margin({ left: 16, right: 16, top: 5 });
    
            TabContent() {
              Column() {
                Tabs({ barPosition: BarPosition.Start, controller: this.subController }) {
                  TabContent() {
                    Column().width('100%').height('100%').backgroundColor('#F1F3F5').borderRadius(12);
                  }
                  .tabBar(this.subTabBuilder(0, '组件1'))
                  .margin({ left: 16, right: 16 })
                  // 关键代码
                  .gesture(
                    PanGesture(this.panOptionRight)
                      .onActionEnd(() => {
                        this.controller.changeIndex(0);
                      })
                  );
    
                  TabContent() {
                    Column().width('100%').height('100%').backgroundColor('#F1F3F5').borderRadius(12);
                  }
                  .tabBar(this.subTabBuilder(1, '组件2'))
                  .margin({ left: 16, right: 16, top: 5 });
    
                  TabContent() {
                    Column().width('100%').height('100%').backgroundColor('#F1F3F5').borderRadius(12);
                  }
                  .tabBar(this.subTabBuilder(2, '组件3'))
                  .margin({ left: 16, right: 16, top: 5 });
    
                  TabContent() {
                    Column().width('100%').height('100%').backgroundColor('#F1F3F5').borderRadius(12);
                  }
                  .tabBar(this.subTabBuilder(3, '组件4'))
                  .margin({ left: 16, right: 16, top: 5 })
                  // 关键代码
                  .gesture(
                    PanGesture(this.panOptionLeft)
                      .onActionEnd(() => {
                        this.controller.changeIndex(2);
                      })
                  );
                }
                .animationDuration(400)
                .onChange((index: number) => {
                  this.subCurrentIndex = index;
                })
                .width('100%');
              }
              .width('100%').height('100%');
            }.tabBar(this.tabBuilder(1, '详情'));
    
            TabContent() {
              Column() {
                Text('我的内容');
              }
              .width('100%')
              .height('100%')
              .borderRadius(12)
              .backgroundColor('#F1F3F5')
              .justifyContent(FlexAlign.Center)
              .alignItems(HorizontalAlign.Center);
            }
            .tabBar(this.tabBuilder(2, '我的'))
            .margin({ left: 16, right: 16, top: 5 });
          }
          .barWidth('90%')
          .onChange((index: number) => {
            this.currentIndex = index;
          })
          .height(296);
        };
      }
    }
    ```
    
*   方案二：使用onGestureRecognizerJudgeBegin()拦截内部Tabs滑动。
    
    可以使用手势拦截增强解决Tabs多层嵌套滑动冲突问题。使用示例请参考嵌套场景下拦截内部容器手势，通过onGestureRecognizerJudgeBegin监听手势事件，内层Tabs到头/尾时拒绝手势传递，允许外层Tabs响应。
    

#### 总结

方案一和方案二都解决了内外层Tabs滑动冲突的问题，实现了多级Tabs嵌套滑动功能。两个解决方案的对比如下表：

| 对比维度 | 方案一 | 方案二 |
| --- | --- | --- |
| 核心思想 | 显式监听手势动作，主动调用控制器强制切换Tab | 通过手势监听动态拦截冲突手势，由系统自动处理滑动切换 |
| 手势判断逻辑 | 在内部Tabs中，为特定的TabContent添加手势识别 | 当onGestureRecognizerJudgeBegin判定为滑动手势时，同步检查滑动方向及内层Tabs的当前索引状态。 |
| 代码触发点 | 在滑动动作完成后响应 | 在手势识别阶段实时判断 |
| 视觉反馈 | 可能出现"滑动-停顿-跳转" | 无缝衔接，滑动距离累积到外层切换 |