HarmonyOS7 底部导航栏:Tabs 组件 5 分钟搞定多页签切换

前言
做 App 首页,十有八九要用到底部导航栏。我之前用自己写的方式实现,代码一堆,切换动画还卡。后来发现 HarmonyOS7 的 Tabs 组件,5 分钟就能搞定,而且自带丝滑切换。
这篇聊聊 Tabs 组件怎么用,从基础布局到自定义 TabBar,再到懒加载和 Navigation 联动,一次讲清楚。
说实话,我一开始没把 Tabs 当回事。觉得不就是个底部导航嘛,自己画几个图标不就完了?结果自己写了一版,切换的时候白屏闪烁,状态还各种丢。痛定思痛,老老实实用 Tabs,才发现真香——系统组件帮你想到了很多东西。
Tabs 组件能干什么
简单说,Tabs 就是一个页签容器,每个页签(TabContent)对应一个内容区,点击页签切换内容。

它能做的事:
- 底部导航栏:微信那种底部 4 个 Tab,点击切换页面
- 顶部标签页:今日头条那种频道切换
- 侧边导航:设置页面的分类导航
- 自带滑动切换:手指左右滑动就能切页
- 预加载支持:可以提前加载相邻页签,切过去不卡
划重点:Tabs 的子组件只能是 TabContent,而 TabContent 的直接子组件只能有一个。需要多个组件就套一个 Column 或 Row。
基础 Tab 布局
最简单的 Tabs,3 行代码搞定:
Tabs() {
TabContent() {
Text('首页内容')
}.tabBar('首页')
TabContent() {
Text('消息内容')
}.tabBar('消息')
TabContent() {
Text('我的内容')
}.tabBar('我的')
}
.barPosition(BarPosition.End)
逐行讲解:
Tabs()是容器,里面放TabContent子组件- 每个
TabContent()包裹当前页签的内容

.tabBar('首页')设置页签栏显示的文字.barPosition(BarPosition.End)把导航栏放底部,默认是在顶部的
就这样,一个能滑动能点击的底部导航就出来了。不过默认样式比较素,实际开发肯定要自定义。
自定义 TabBar 样式
默认的纯文字 TabBar 太丑了,加上图标和选中态才像样。这就需要自定义 TabBar:
@Entry
@Component
struct MainPage {
@State currentIndex: number = 0
private tabsController: TabsController = new TabsController()
@Builder
tabItemBuilder(icon: Resource, text: string, index: number) {
Column() {
Image(this.currentIndex === index ? icon : $r('app.media.ic_default'))
.width(24)
.height(24)
.fillColor(this.currentIndex === index ? '#007DFF' : '#999999')
Text(text)
.fontSize(10)
.fontColor(this.currentIndex === index ? '#007DFF' : '#999999')
.margin({ top: 2 })
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
build() {
Column() {
Tabs({ controller: this.tabsController, index: this.currentIndex }) {
TabContent() {
Text('首页内容区')
}
.tabBar(this.tabItemBuilder($r('app.media.ic_home_selected'), '首页', 0))
TabContent() {
Text('消息内容区')
}
.tabBar(this.tabItemBuilder($r('app.media.ic_msg_selected'), '消息', 1))
TabContent() {
Text('我的内容区')
}
.tabBar(this.tabItemBuilder($r('app.media.ic_mine_selected'), '我的', 2))
}
.barPosition(BarPosition.End)
.onChange((index: number) => {
this.currentIndex = index
})
}
.width('100%')
.height('100%')
}
}
关键代码讲解:
@State currentIndex跟踪当前选中页签,必须用 @State,否则切换不刷新 UITabsController控制器,可以代码跳转页签,比如this.tabsController.changeIndex(2)@Builder tabItemBuilder是自定义 TabBar 的核心——根据currentIndex判断选中态,切换图标和颜色.onChange()监听页签切换,更新currentIndexicon: Resource参数类型用Resource,这样能用$r()引用资源
小技巧:选中图标和未选中图标用同一个资源,通过
.fillColor()改颜色就行,省得切两套图。
Tab 内容懒加载
Tabs 默认会把所有 TabContent 一口气加载。3 个 Tab 还好,要是 5 个 6 个,每个里面还请求数据,启动就卡成狗。
HarmonyOS7 提供了 preloadItems 方法,可以按需预加载:
.onAppear(() => {
try {
this.tabsController.preloadItems([1])
} catch (e) {
console.error('预加载失败: ' + e)
}
})
.onChange((index: number) => {
this.currentIndex = index
let preloadList: number[] = []
if (index > 0) {
preloadList.push(index - 1)
}
if (index < 4) {
preloadList.push(index + 1)
}
try {
this.tabsController.preloadItems(preloadList)
} catch (e) {
console.error('预加载失败: ' + e)
}
})
逻辑说明:
onAppear里预加载第 2 个页签,用户大概率会切过去onChange里预加载当前页签的前后各一页preloadItems必须在 Tabs 和 Controller 绑定之后调用,放aboutToAppear里会不生效- 务必
try-catch,Controller 没绑定时会抛异常
说白了就是:不提前全加载,切到哪个附近就预加载哪个。用户体验不差,性能还好。
与 Navigation 联动
实际项目中,Tabs 通常嵌在 Navigation 的首页里,子页面用 NavDestination 跳转。这是最经典的组合:
@Entry
@Component
struct IndexPage {
@Provide navPathStack: NavPathStack = new NavPathStack()
@State currentIndex: number = 0
build() {
Navigation(this.navPathStack) {
Tabs({ index: this.currentIndex }) {
TabContent() {
HomePage()
}.tabBar('首页')
TabContent() {
MessagePage()
}.tabBar('消息')
TabContent() {
MinePage()
}.tabBar('我的')
}
.barPosition(BarPosition.End)
.onChange((index: number) => {
this.currentIndex = index
})
}
.navDestination(this.pageMap)
.hideTitleBar(true)
}
@Builder
pageMap(name: string) {
if (name === 'DetailPage') {
DetailPage()
} else if (name === 'SettingsPage') {
SettingsPage()
}
}
}
要点:
Navigation包在最外层,Tabs 作为首页内容@Provide navPathStack通过@Consume在子页面里就能拿到,不用层层传参- 子页面跳转用
this.navPathStack.pushPathByName('DetailPage', null) navDestination做路由映射,名字对应组件
这种结构的好处是:Tab 页签内的页面跳转和 Tab 切换互不干扰,各管各的。
写在最后
Tabs 组件说简单也简单,3 行代码能跑;说复杂也复杂,自定义样式、懒加载、Navigation 联动都要考虑。我的建议是:先用最简版跑起来,再逐步加自定义和优化。别一上来就搞复杂的自定义 TabBar,容易把自己绕进去。
更多推荐

所有评论(0)