HarmonyOS 鸿蒙Next 两层Tabs嵌套滑动冲突问题

发布于 1周前 作者 sinazl 来自 鸿蒙OS

HarmonyOS 鸿蒙Next 两层Tabs嵌套滑动冲突问题

两层Tabs组件嵌套使用,内部的tabs组件可以左右滑动,但是在滑动到第一个或是最后一个时,外层Tabs无法响应滑动事件。

请教各位大佬,有什么方案可以实现内部Tabs滑动到页面边界时可以响应外层Tabs的滑动事件

9 回复

楼主看下这个方案可行,通过PanGesture和controller.changeIndex实现一二级Tabs切换,当二级Tabs 滑动到第一个或者最后一个tab的时候 ,切换到一级Tabs 。

Demo如下:

@Entry
@Component
struct TabsExample {
  @State fontColor: string = '#182431'
  @State selectedFontColor: string = '#007DFF'
  @State currentIndex: number = 0
  private controller: TabsController = new TabsController()

  @State subCurrentIndex: number = 0
  private subController: TabsController = new TabsController()

  private panOptionRight: PanGestureOptions = new PanGestureOptions({ direction: PanDirection.Left })
  private panOptionLeft: PanGestureOptions = new PanGestureOptions({ direction: PanDirection.Right })


  @Builder tabBuilder(index: number, name: string) {
    Column() {
      Text(name)
        .fontColor(this.currentIndex === index ? this.selectedFontColor : this.fontColor)
        .fontSize(16)
        .fontWeight(this.currentIndex === index ? 500 : 400)
        .lineHeight(22)
        .margin({ top: 17, bottom: 7 })
      Divider()
        .strokeWidth(2)
        .color('#007DFF')
        .opacity(this.currentIndex === index ? 1 : 0)
    }.width('100%')
  }

  @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)
        .margin({ top: 17, bottom: 7 })
      Divider()
        .strokeWidth(2)
        .color('#007DFF')
        .opacity(this.subCurrentIndex === index ? 1 : 0)
    }.width('100%')
  }

  build() {
    Column() {
      Tabs({ barPosition: BarPosition.Start, index: this.currentIndex, controller: this.controller }) {
        TabContent() {
          Column().width('100%').height('100%').backgroundColor('#ffb554d7')
        }.tabBar(this.tabBuilder(0, '首页'))

        TabContent() {
          Column(){
            Tabs({ barPosition: BarPosition.Start,controller: this.subController }) {
              TabContent() {
                Column().width('100%').height('100%').backgroundColor('#00CB87')
                  // 左右拖动触发该手势事件
                  .gesture(
                    PanGesture(this.panOptionRight)
                      .onActionStart((event?: GestureEvent) => {
                        console.info('Pan start')
                      })
                      .onActionUpdate((event?: GestureEvent) => {
                        if (event) {
                        }
                      })
                      .onActionEnd(() => {
                        this.controller.changeIndex(2)
                        console.info('Pan end')
                      })

                  )
                  // .gesture(
                  //   PanGesture(this.panOptionLeft)
                  //     .onActionStart((event?: GestureEvent) => {
                  //       console.info('Pan start')
                  //     })
                  //     .onActionUpdate((event?: GestureEvent) => {
                  //       if (event) {
                  //       }
                  //     })
                  //     .onActionEnd(() => {
                  //       this.controller.changeIndex(0)
                  //       console.info('Pan end')
                  //     })
                  // )
              }.tabBar(this.subTabBuilder(3, 'pink'))
            }
            .vertical(false)
            .barMode(BarMode.Fixed)
            .barWidth(360)
            .barHeight(56)
            .animationDuration(400)
            .onChange((index: number) => {
              this.subCurrentIndex = index
            })
            .width(360)
            .backgroundColor('#F1F3F5')
          }
          .width('100%').height('100%').backgroundColor('#00CB87')
        }.tabBar(this.tabBuilder(1, '详情'))

        TabContent() {
          Column().width('100%').height('100%').backgroundColor('#ffc19757')
        }.tabBar(this.tabBuilder(2, '我的'))

      }
      .vertical(false)
      .barMode(BarMode.Fixed)
      .barWidth(360)
      .barHeight(56)
      .animationDuration(400)
      .onChange((index: number) => {
        this.currentIndex = index
      })
      .width(360)
      .height(296)
      .margin({ top: 52 })
      .backgroundColor('#F1F3F5')
    }.width('100%')
  }
}

更多关于HarmonyOS 鸿蒙Next 两层Tabs嵌套滑动冲突问题的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


这个方案看到过,目前也采用了这种方案,虽然还有点问题。 希望官方能够把事件分发机制再优化下,如果能够像安卓那样从事件分发的角度解决这个问题要简单的多。

希望HarmonyOS能继续推出更多实用的功能,满足用户的不同需求。

这个解决方案看到过,但是外层Tabs切换时有些生硬,没有原始过度动画。不知道还有没有其他的解决方案。譬如,类似安卓中的事件分发,内部Tabs将事件抛给上层处理

最终采用了PanGesture和controller.changeIndex的实现方案,虽然滑动事件从内部Tabs向外层Tabs过度时动效上还有些问题,但已是目前较好的解决方案了。

不知道使用Tabs+Swiper 的组件嵌套方法会不会有意想不到的效果。

// xxx.ets

[@Entry](/user/Entry)

[@Component](/user/Component)

struct Index {

  [@State](/user/State) currentIndex: number = 0

  [@State](/user/State) selectedIndex: number = 0

  [@State](/user/State) fontColor: string = '#182431'

  [@State](/user/State) selectedFontColor: string = '#007DFF'

  innerSelectedIndex: number = 0 // 记录内层Tabs的索引

  controller?: TabsController = new TabsController();

  [@Builder](/user/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

      })

    }

  }

}

在HarmonyOS鸿蒙Next系统中,处理两层Tabs嵌套滑动冲突问题,通常涉及到对组件的滑动事件进行协调管理。以下是针对此问题的直接解决思路:

  1. 事件拦截

    • 在外层Tabs组件中,通过重写滑动事件处理方法,实现对滑动事件的拦截。当检测到滑动事件时,根据当前滑动状态判断是否需要将事件传递给内层Tabs组件。
  2. 滑动方向判断

    • 通过判断滑动手势的方向,如果滑动方向是横向(通常用于切换Tabs),则由外层Tabs处理;如果滑动方向是纵向(通常用于内容滚动),则允许事件传递给内层Tabs或其内容区域。
  3. 嵌套滚动视图

    • 使用鸿蒙提供的嵌套滚动视图组件(如NestedScrollView),该组件能够较好地处理嵌套滚动冲突,确保内外层Tabs的滑动行为互不干扰。
  4. 滚动区域设置

    • 明确设置内外层Tabs的滚动区域,避免滚动区域重叠导致的冲突。

通过上述方法,可以有效解决HarmonyOS鸿蒙Next系统中两层Tabs嵌套时的滑动冲突问题。如果问题依旧没法解决请联系官网客服,官网地址是:https://www.itying.com/category-93-b0.html。

回到顶部