HarmonyOS5 便捷生活类——如何解决Tabs组件嵌套同向可滚动组件后无法触发外层Tabs滑动的问题
来来来,加入CSDN官方班级并考证,拿鸿蒙礼盒!
加入下方官方CSDN班级,得鸿蒙礼盒
本期活动时间:2025年8月1日-12月31日
如有问题欢迎私聊我呀!
问题现象
Tabs组件存在横向滑动的控制手势,当其内部嵌套Tabs或横向的List、Scroll、Swiper、Grid等滚动与滑动组件时,会产生横向滚动手势冲突,导致外部的Tabs无法横向切换。
以Grid为例,问题效果预览:

Tabs组件嵌套Grid组件,当Grid组件滑动到右侧底部时,无法触发Tabs组件的滑动。如下图所示,使用手指左滑页面中的2行icon,预期触发tab从“首页1”切换到“首页2”,实际未触发。
背景知识
- Tabs组件是官方提供的导航与切换组件,可以通过TabsController手动控制其页面切换逻辑。
- List、Scroll、Grid组件是官方提供的滑动与滚动组件,支持滚动组件通用接口。且都可以通过自身属性控制横向或纵向滚动,当为横向滚动时,与Tabs嵌套时会发生手势冲突。
- Swiper组件是滑块视图容器,提供子组件滑动轮播显示的能力,也可以通过SwiperController手动控制其滑动逻辑。
解决方案
- 方案一:由于List、Scroll、Grid滚动组件存在nestedScroll通用属性,可以设置滚动优先级。以横向滚动的Grid为例,在开启Tabs滑动切换的前提下给Grid设置嵌套滚动nestedScroll属性。
@Entry @Component struct TabsGridDemo { tabsController: TabsController = new TabsController(); gridData: number[] = Array(10).fill(0); build() { Column() { Tabs({ controller: this.tabsController }) { TabContent() { Grid() { ForEach(this.gridData, () => { GridItem() { Image($r('app.media.startIcon')) .height('40%'); }; }); } .columnsGap(16) .rowsTemplate('1fr 1fr') // 设置Grid优先滚动 .nestedScroll({ scrollForward: NestedScrollMode.SELF_FIRST, scrollBackward: NestedScrollMode.SELF_FIRST, }); }.tabBar('首页1'); TabContent() { }.tabBar('首页2'); }; }.height('100%').width('100%'); } } - 方案二:通过自定义手势判断PanGesture实现Tabs嵌套内部组件滚动逻辑判断。
- 以Tabs嵌套Swiper组件为例:
- 当Swiper显示位置为第一个卡片时,若继续往右滑,执行Tabs切换到上一个页签。
- 当Swiper显示位置为最后一个卡片时,若继续往左滑,执行Tabs切换到下一个页签。
- 其他时候左右滑动手势执行Swiper切换功能,左滑切换上一个卡片,右滑切换下一个卡片。
@Entry @Component struct TabsSwiperDemo { tabsController: TabsController = new TabsController(); swiperController: SwiperController = new SwiperController(); data: number[] = [1, 2, 3]; build() { Column() { Tabs({ controller: this.tabsController }) { TabContent() { Text('首页的内容').fontSize(30); }.tabBar('首页'); TabContent() { // Swiper在第二个TabContent内。 Swiper(this.swiperController) { ForEach(this.data, (item: number, index: number) => { Text(item.toString()) .width('100%') .height('100%') .backgroundColor(0xAFEEEE) .textAlign(TextAlign.Center) .fontSize(30) .gesture( PanGesture() .onActionStart(() => { console.info('Pan start'); }) .onActionUpdate(() => { console.info('Pan update'); }) .onActionEnd((event: GestureEvent) => { // Swiper在Tabs第二页内采用if/else逻辑优先判定Swiper边缘滑动情况。 if (index === 0 && event.offsetX > 0) { this.tabsController.changeIndex(0); // Swiper滑动到第一页继续右滑,Tabs控制器跳转到第一页。 } else if (index === (this.data.length - 1) && event.offsetX < 0) { this.tabsController.changeIndex(2); // Swiper滑动到最后一页继续左滑,Tabs控制器跳转到第三页。 } else if (event.offsetX < 0) { this.swiperController.showNext(); // Swiper控制器。 } else if (event.offsetX > 0) { this.swiperController.showPrevious(); // Swiper控制器。 } }) ); }, (item: string) => item); }; }.tabBar('发现'); TabContent() { Text('推荐的内容').fontSize(30); }.tabBar('推荐'); TabContent() { Text('我的内容').fontSize(30); }.tabBar('我的'); }; }.width('100%').height('100%'); } }Tabs嵌套Tabs的滚动也可使用PanGesture实现,只需在内层Tabs的第一个和最后一个TabContent绑定手势处理即可。
@Entry @Component struct TabsTabsDemo { tabsController: TabsController = new TabsController(); build() { Column() { Tabs({ controller: this.tabsController }) { TabContent() { Text('首页的内容').fontSize(30); }.tabBar('首页'); TabContent() { Tabs({ barPosition: BarPosition.Start }) { TabContent() { Text('tab1').fontSize('30fp'); }.tabBar('tab1') .gesture( PanGesture({ fingers: 1, distance: 1, direction: PanDirection.Right }) .onActionStart(() => { console.info('Pan start'); }) .onActionEnd(() => { this.tabsController.changeIndex(0); }) ); // 中间的其它TabContent TabContent() { Text('tab2').fontSize('30fp'); }.tabBar('tab2') .gesture( PanGesture({ fingers: 1, distance: 1, direction: PanDirection.Left }) .onActionStart(() => { console.info('Pan start'); }) .onActionEnd(() => { this.tabsController.changeIndex(2); }) ); }.backgroundColor(Color.Pink); }.tabBar('发现'); TabContent() { Text('推荐的内容').fontSize(30); }.tabBar('推荐'); TabContent() { Text('我的内容').fontSize(30); }.tabBar('我的'); }; }.width('100%').height('100%'); } } - 方案三:使用onGestureRecognizerJudgeBegin()拦截内部组件滑动。
可以使用手势拦截增强解决Tabs多层嵌套滑动冲突问题。使用示例请参考嵌套场景下拦截内部容器手势,通过onGestureRecognizerJudgeBegin监听手势事件,内层Tabs到头/尾时拒绝手势传递,允许外层Tabs响应。
总结
|
方案 |
局限性 |
本知识适用场景 |
拓展适用场景 |
|---|---|---|---|
|
方案一 |
内部组件必须支持滚动与滑动组件的通用nestedScroll接口。 |
Tabs嵌套List、Scroll、Grid组件。 |
List、Scroll、Grid组件嵌套List、Scroll、Grid组件。 |
|
方案二 |
内部为List、Scroll、Grid等组件时实现较麻烦。 |
Tabs嵌套Swiper、Tabs组件。 |
Swiper组件嵌套Swiper、Tabs组件。 |
|
方案三 |
不支持内部为Scroll、List、Grid组件。 |
Tabs嵌套Swiper、Tabs组件。 |
Swiper组件嵌套Swiper、Tabs组件。 |
综上所述,总结如下:
- Swiper组件的nestedScroll属性与Scroll、List、Grid的nestedScroll属性不一致。且Swiper不支持滚动组件通用属性。所以当Tabs嵌套Swiper时方案一并不适用。
- Tabs组件属于导航与切换组件,也不支持nestedScroll和滚动组件通用属性,所以当Tabs嵌套Tabs时方案一也不适用。
- 当Tabs内部嵌套List、Scroll、Grid组件时,优先选用方案一,内部嵌套Tabs或Swiper时,优先选用方案二,方案三。
更多推荐




所有评论(0)