概述
在鸿蒙应用开发中,导航是构建用户界面的重要组成部分。不同的页签导航会因产品形态的不同而呈现出不同的UI效果。本文基于HarmonyOS官方最佳实践,系统性地介绍了多种常见导航样式的实现方案,包括底部导航、顶部导航和侧边导航三大类共10种导航样式。
导航样式总览
图1:底部导航效果示意图
图2:顶部导航效果示意图
图3:侧边导航效果示意图
一、底部导航
1.1 基础底部导航
效果展示
基础底部导航是移动应用中最常见的导航方式,通常采用图标加文字的形式展示在页面底部。
核心实现思路:
- 使用
Tabs组件,设置barPosition为BarPosition.End实现底部展示 - 使用
TabContent组件实现内容区,通过tabBar属性配置导航条 - 使用
Badge组件实现消息提醒功能
关键代码示例:
build() {
Tabs({
barPosition: BarPosition.End,
controller: this.tabsController
}) {
this.tabContentBuilder($r('app.string.message'),
Constants.TAB_INDEX_ZERO, $r('app.media.activeMessage'), $r('app.media.message'))
this.tabContentBuilder($r('app.string.people'),
Constants.TAB_INDEX_ONE, $r('app.media.activePeople'), $r('app.media.people'))
this.tabContentBuilder($r('app.string.activity'),
Constants.TAB_INDEX_TWO, $r('app.media.activeStar'), $r('app.media.star'))
}
.width('100%')
.backgroundColor('#F3F4F5')
.barHeight(52)
.barMode(BarMode.Fixed)
.onAnimationStart((index: number, targetIndex: number) => {
this.currentIndex = targetIndex;
})
}
导航Tab构建器:
@Builder
tabBuilder(title: Resource, index: number, selectedImg: Resource, normalImg: Resource) {
Column() {
if (index === 0) {
Badge({
count: this.msgNum,
style: { badgeSize: 14 },
maxCount: 999,
position: BadgePosition.RightTop
}) {
Image(this.currentIndex === index ? selectedImg : normalImg)
.width(24)
.height(24)
.objectFit(ImageFit.Contain)
}
.width(30)
} else {
Image(this.currentIndex === index ? selectedImg : normalImg)
.width(24)
.height(24)
.objectFit(ImageFit.Contain)
}
Text(title)
.margin({ top: 4 })
.fontSize(10)
.fontColor(this.currentIndex === index ? '#3388ff' : '#E6000000')
}
.justifyContent(FlexAlign.Center)
.height(52)
.width('100%')
.onClick(() => {
this.currentIndex = index;
this.tabsController.changeIndex(this.currentIndex);
})
}
1.2 舵式底部导航
效果展示
舵式导航是基础底部导航的扩展形式,中间按钮通常为核心功能,在视觉设计上中心图标可以超出导航条的高度,形成视觉焦点。
实现要点:
- 中心按钮使用
offset属性实现向上偏移效果 - 通过自定义布局替代
tabBar属性 - 使用
Flex布局均匀分布导航按钮
关键代码:
@Builder
Tab(selectImage: Resource, defaultImage: Resource, title: string | Resource, middleMode: boolean, index: number) {
Column() {
if (index === Constants.TAB_INDEX_TWO) {
Image(defaultImage)
.size({ width: 56, height: 56 })
.offset({ y: -15 }) // 中心按钮向上偏移
} else {
Image(this.currentIndex === index ? selectImage : defaultImage)
.size({ width: 22, height: 22 })
.offset({
y: (this.currentIndex === index && this.currentIndex !== Constants.TAB_INDEX_TWO)
? this.iconOffset : this.initNumber
})
.objectFit(ImageFit.Contain)
.animation({
duration: Constants.ANIMATION_DURATION,
curve: Curve.Ease,
playMode: PlayMode.Normal
})
}
if (!middleMode) {
Text(title)
.fontSize(10)
.margin({ top: 6 })
.fontColor(this.currentIndex === index ? '#3388ff' : '#E6000000')
}
}
.padding({ top: 11 })
.width('100%')
.backgroundColor('#F3F4F5')
.height(90)
.translate({ y: 40 })
.onClick(() => {
if (index !== Constants.TAB_INDEX_TWO) {
this.currentIndex = index;
this.controller.changeIndex(index);
this.iconOffset = Constants.ICON_Offset;
}
})
}
二、顶部导航
2.1 居左对齐样式
效果展示
由于 Tabs 组件的导航默认居中展示,无法直接通过 tabBar 属性实现居左对齐。需要使用自定义布局来实现这一效果。
实现方案:
- 使用
Stack组件叠加文字和下划线两层 - 通过
onAreaChange监听计算下划线位置 - 在页签切换时动态更新下划线的位置和宽度
核心代码:
Stack({ alignContent: Alignment.TopStart }) {
// 文字导航条
Row() {
ForEach(this.tabArray, (item: number, index: number) => {
this.tab(this.tabStr + item, item, index);
}, (item: number, _index: number) => item.toString())
Blank()
Text('+')
.width(24)
.height(24)
.fontSize(24)
.textAlign(TextAlign.Center)
.margin({ right: 24 })
}
.justifyContent(FlexAlign.Start)
.width('100%')
// 下划线指示器
Column()
.width(this.indicatorWidth)
.height(1.5)
.backgroundColor('#0A59F7')
.borderRadius(1)
.margin({ left: this.indicatorLeftMargin, top: 35 })
}
.height(56)
.margin({ left: this.tabLeftOffset })
下划线位置计算:
@Builder
tab(tabName: string, _tabItem: number, tabIndex: number) {
Row() {
Text(tabName)
.fontSize(16)
.lineHeight(22)
.fontColor(tabIndex === this.currentIndex ? '#0A59F7' : '#E6000000')
.id(tabIndex.toString())
.onAreaChange((_, newValue: Area) => {
if (this.currentIndex === tabIndex && (this.indicatorLeftMargin === 0 || this.indicatorWidth === 0)) {
let positionX: number;
let width: number = Number.parseFloat(newValue.width.toString());
if (newValue.position.x !== undefined) {
positionX = Number.parseFloat(newValue.position.x?.toString())
this.indicatorLeftMargin = Number.isNaN(positionX) ? 0 : positionX;
}
this.indicatorWidth = width;
}
})
}
.justifyContent(FlexAlign.Center)
.constraintSize({ minWidth: 35 })
.width(64)
.height(35)
.onClick(() => {
this.controller.changeIndex(tabIndex);
this.currentIndex = tabIndex;
})
}
2.2 可滑动居左对齐样式
效果展示
在居左对齐的基础上,使用 List 组件实现横向滑动功能,适合页签数量较多的场景。
实现要点:
Row() {
List({ initialIndex: 0, scroller: this.listScroller }) {
ForEach(this.tabArray, (item: TabItem, index: number) => {
this.Tab(item.name, index)
}, (item: TabItem, index: number) => JSON.stringify(item) + index)
}
.listDirection(Axis.Horizontal) // 横向滚动
.height(30)
.scrollBar(BarState.Off)
.width('85%')
.friction(0.6)
.onWillScroll((xOffset: number) => {
this.indicatorLeftMargin -= xOffset; // 滚动时同步下划线位置
})
Image($r('app.media.more'))
.width(20)
.height(15)
.margin({ left: 16 })
}
.height(52)
.width('100%')
2.3 下划线样式
效果展示
下划线样式是最常见的顶部导航形式,采用文字加下划线的简洁设计。这是一个动态GIF效果图,展示了页签切换时下划线的平滑过渡动画。
实现方法:
Tabs({ barPosition: BarPosition.Start }) {
ForEach(this.tabArray.slice(0, 4), (item: TabItem) => {
TabContent() {
Row() {
Text(item.name)
.height(300)
.fontSize(30)
}
.width('100%')
.justifyContent(FlexAlign.Center)
.height('100%')
}.tabBar(this.tabBuilder(item.id, item.name))
}, (item: TabItem, index: number) => JSON.stringify(item) + index)
}
@Builder
tabBuilder(index: number, name: string | Resource) {
Column() {
Text(name)
.fontColor(this.currentIndex === index ? '#0A59F7' : '#E6000000')
.fontSize(16)
.fontWeight(this.currentIndex === index ? FontWeight.Normal : FontWeight.Medium)
.lineHeight(22)
.margin({ top: 17, bottom: 7 })
Divider()
.width(48)
.strokeWidth(Constants.STROKE_WIDTH)
.color('#0A59F7')
.opacity(this.currentIndex === index ? 1 : 0) // 控制下划线显示
}
}
2.4 背景高亮式
效果展示
通过改变选中标签的背景色来突出当前激活的页签,视觉冲击力强。
核心实现:
@Builder
tabBuilder(name: string | Resource, index: number) {
Row() {
Text(name)
.fontSize(14)
.fontColor(this.currentIndex === index ? '#0A59F7' : '#E6000000')
}
.justifyContent(FlexAlign.Center)
.constraintSize({ minWidth: 35 })
.padding({ left: 16, right: 16 })
.height(28)
.borderRadius(16)
.backgroundColor(this.currentIndex === index ? '#1A0A59F7' : Color.Transparent)
.onClick(() => {
this.controller.changeIndex(index);
this.currentIndex = index;
})
}
2.5 文字缩放式
效果展示
通过字体大小和粗细变化来强调选中状态,交互反馈直观。
关键代码:
@Builder
tabBuilder(index: number, name: string | Resource) {
Text(name)
.fontColor(Color.Black)
.fontSize(this.currentIndex === index ? 20 : 16) // 选中时字体放大
.fontWeight(this.currentIndex === index ? 600 : FontWeight.Normal) // 字体加粗
.lineHeight(22)
.id(index.toString())
}
2.6 双层嵌套式
效果展示
双层嵌套样式拥有外层和内层两层导航,可以容纳更多的页签内容,适合复杂的信息架构。
实现架构:
TabContent() {
Column() {
Column() {
Row() {
// 内层导航 - 使用List实现横向滚动
List({ initialIndex: Constants.TAB_INDEX_ZERO, scroller: this.listScroller }) {
ForEach(this.tabArray, (item: TabItem, index: number) => {
this.subTabBuilder(item.name, index)
}, (item: TabItem, index: number) => JSON.stringify(item) + index)
}
.listDirection(Axis.Horizontal)
.height(30)
.scrollBar(BarState.Off)
.width('85%')
.friction(0.6)
Image($r('app.media.more'))
.width(20)
.height(15)
.margin({ left: 16 })
}
.height(25)
.width('100%')
}
.alignItems(HorizontalAlign.Center)
.width('100%')
.padding({ left: 4 })
// 内容区 - 子级Tabs
Tabs({ barPosition: BarPosition.Start, controller: this.subController }) {
// TabContent内容
}
.barHeight(0) // 隐藏内层Tabs的导航条
.animationDuration(Constants.ANIMATION_DURATION)
.onAnimationStart((index: number, targetIndex: number) => {
this.focusIndex = targetIndex;
this.listScroller.scrollToIndex(targetIndex, true, ScrollAlign.CENTER);
})
}
}
.tabBar(this.tabBuilder(Constants.TAB_INDEX_ZERO, this.topTabData[Constants.TAB_INDEX_ZERO]))
三、侧边导航
3.1 基础侧边导航
效果展示
侧边导航采用左右布局,左侧为分类列表,右侧为内容展示区域。
实现方案:
Row() {
// 左侧导航列表
List({ scroller: this.classifyScroller }) {
ForEach(this.ClassifyArray, (item: ClassifyModel, index?: number) => {
ListItem() {
ClassifyItem({
classifyName: item.classifyName,
isSelected: this.currentClassify === index,
onClickAction: () => {
if (index !== undefined) {
this.classifyChangeAction(index, true);
}
}
})
}
}, (item: ClassifyModel, index: number) => JSON.stringify(item) + index)
}
.height('110%')
.width('27.8%')
.backgroundColor('#F1F3F5')
.scrollBar(BarState.Off)
.margin({ top: 74 })
// 右侧内容区
Column() {
ForEach(this.ClassifyArray, (item: ClassifyModel, index: number) => {
Text(this.currentClassify === index ? item.classifyName : '')
.fontSize(30)
},(item: ClassifyModel, index: number) => JSON.stringify(item) + index)
}
.width('72.2%')
}
3.2 抽屉式侧边导航
效果展示
抽屉式导航的核心思路是"隐藏",点击入口或侧滑可以像抽屉一样拉出菜单,节省屏幕空间。
实现要点:
使用 SideBarContainer 组件实现:
SideBarContainer(SideBarContainerType.Overlay) {
// 侧边栏内容
Column() {
ForEach(this.navList, (item: number, index: number) => {
Column() {
Row() {
Image(this.active === item ? $r('app.media.activeList') : $r('app.media.list'))
.width(24)
.height(24)
Text($r('app.string.list_name'))
.fontSize(16)
.fontColor(Color.Black)
.fontWeight(FontWeight.Medium)
.margin({ left: 17 })
}
.height(48)
.width('100%')
if (this.navList.length - 1 !== index) {
Row()
.height(0.5)
.backgroundColor('#0D000000')
.width('90%')
}
}
.onClick(() => {
this.active = item;
})
.justifyContent(FlexAlign.Center)
.width(264)
.height(48)
.padding({ left: 13 })
.borderRadius(16)
.backgroundColor(this.active === item ? '#1A0A59F7' : '')
}, (item: number, index: number) => JSON.stringify(item) + index)
}
.height('100%')
.padding({ top: 104 })
.backgroundColor('#E9EAEC')
.width(272)
.height(344)
.backgroundColor(Color.White)
.borderRadius(20)
// 主内容区
Column() {
// 内容
}
.onClick(() => {
this.getUIContext().animateTo({
duration: Constants.ANIMATION_DURATION,
curve: Curve.EaseOut,
playMode: PlayMode.Normal,
}, () => {
this.show = false;
})
})
.width('100%')
.height('110%')
.backgroundColor(this.show ? '#c1c2c4' : '')
}
.showSideBar(this.show) // 控制显示/隐藏
.controlButton({
left: 16,
top: 48,
height: 40,
width: 40,
icons: {
shown: $r('app.media.changeBack'),
hidden: $r('app.media.change'),
switching: $r('app.media.change')
}
})
.onChange((value: boolean) => {
this.show = value;
})
四、最佳实践建议
1. 导航样式选择原则
| 导航类型 | 适用场景 | 典型应用 |
|---|---|---|
| 基础底部导航 | 3-5个主要功能模块 | 微信、支付宝、淘宝 |
| 舵式底部导航 | 核心功能突出显示 | 闲鱼、小红书 |
| 顶部导航 | 内容分类较多 | 今日头条、抖音 |
| 侧边导航 | 分类层级较深 | 设置页面、文件管理器 |
2. 性能优化建议
- 使用
ForEach渲染列表时,务必提供唯一的keyGenerator - 对于大量页签的场景,建议使用
LazyForEach进行懒加载 - 合理设置动画时长(推荐200-300ms),避免过度动画影响用户体验
- 导航图标建议使用矢量图(SVG)或提供2x/3x分辨率的位图
3. 交互设计要点
- 保持导航状态的视觉反馈清晰明确
- 页签切换应提供平滑的过渡动画
- 确保可点击区域足够大,符合可用性标准(推荐最小44vp)
- 选中状态应有明显的视觉差异(颜色、大小、下划线等)
4. 代码复用
- 将通用的导航样式封装为自定义组件
- 使用
@Builder装饰器创建可复用的UI构建函数 - 统一管理导航相关的常量和样式
- 建立导航样式的配置化体系,提高可维护性
5. 无障碍设计
- 为导航按钮添加语义化描述
- 确保色彩对比度符合WCAG标准
- 支持键盘导航和屏幕阅读器
五、总结
本文详细介绍了鸿蒙应用开发中10种常见导航样式的实现方案:
底部导航类(2种)
- ✅ 基础底部导航:图标+文字,支持Badge消息提醒
- ✅ 舵式底部导航:中心按钮突出,视觉焦点明确
顶部导航类(6种)
- ✅ 居左对齐样式:自定义布局实现左对齐+动态下划线
- ✅ 可滑动居左对齐样式:支持横向滚动的多页签导航
- ✅ 下划线样式:简洁经典的下划线指示器
- ✅ 背景高亮式:背景色突出选中状态
- ✅ 文字缩放式:字体大小变化展示激活状态
- ✅ 双层嵌套式:二级导航,适合复杂信息架构
侧边导航类(2种)
- ✅ 基础侧边导航:左右布局,分类明确
- ✅ 抽屉式侧边导航:节省空间,按需展示
每种导航样式都有其适用场景和实现技巧。开发者应根据应用的具体需求、信息架构和用户习惯,选择最合适的导航方式。通过合理运用 Tabs、List、SideBarContainer 等组件,配合自定义布局和动画效果,可以打造出流畅、美观的用户体验。