【共创季稿事节】HarmonyOS布局深度解析
鸿蒙原生 ArkTS 布局方式之 Navigation + toolbar 工具栏布局深度解析
一、引言
在移动应用开发中,底部工具栏(Bottom Toolbar) 是最常见的导航模式之一。微信的「微信/通讯录/发现/我」、淘宝的「首页/微淘/消息/购物车/我的」、京东、美团等主流应用均采用这种布局。其核心优势在于:操作热区固定在拇指最易触及的屏幕底部;入口层级扁平,用户无需深层跳转即可到达一级页面;视觉结构清晰,底部工具栏成为稳定的视觉锚点。
本文基于 API 24,深入剖析 Navigation + toolbar 布局的实现原理与最佳实践,并通过完整示例代码帮助读者快速掌握。
二、Navigation 组件概述
2.1 组件结构
Navigation 是 ArkUI 的路由容器组件,天然划分为三个功能区:
┌──────────────────────────────────┐
│ 导航栏(NavBar) │
│ ┌─ 标题区 ──── 菜单区 ─┐ │
│ │ 鸿蒙示例 [搜][…]│ │
│ └────────────────────────┘ │
├──────────────────────────────────┤
│ 内容区(Content) │
├──────────────────────────────────┤
│ 首页 │ 分类 │ 消息 │ 购物车 │ 我的 │
│ 工具栏(Toolbar) │
└──────────────────────────────────┘
2.2 显示模式
| 模式 | 枚举 | 适用场景 |
|---|---|---|
| 单栏 | NavigationMode.Stack |
手机竖屏,标准导航 |
| 分栏 | NavigationMode.Split |
平板/折叠屏 |
| 自适应 | NavigationMode.Auto |
自动切换,响应式 |
底部工具栏场景主要用 Stack(默认值)。
2.3 API 24 关键变化
ToolbarItem和BarMenuItem成为内置类型,直接从全局空间使用,无需 import- ArkTS 严格模式:禁止
as any,对象字面量须显式标注类型,禁止类型名称冲突 - 符号图标体系成熟:
$r('sys.symbol.*')替代旧的$r('sys.media.*') - 注意:不能声明与内置类型同名的接口(如
interface ToolbarItem),否则触发arkts-no-decl-merging错误
三、核心 API 详解
3.1 toolbarConfiguration
设置工具栏内容,核心配置入口:
toolbarConfiguration(
value: Array<ToolbarItem> | CustomBuilder,
options?: NavigationToolbarOptions
): NavigationAttribute;
| 参数 | 类型 | 说明 |
|---|---|---|
value |
ToolbarItem[] 或 CustomBuilder |
工具栏内容 |
options |
NavigationToolbarOptions |
样式选项(背景色、模糊等) |
3.2 ToolbarItem 接口
内置类型,无需导入:
interface ToolbarItem {
value: ResourceStr; // 文本标签(必填)
icon?: ResourceStr; // 图标(可选)
action?: () => void; // 点击回调(可选)
status?: ToolbarItemStatus; // NORMAL / ACTIVE / DISABLED
badge?: ToolbarBadge; // 角标
activeIcon?: ResourceStr; // ACTIVE 态专用图标
symbolIcon?: SymbolGlyphModifier; // Symbol 图标
activeSymbolIcon?: SymbolGlyphModifier; // ACTIVE 态 Symbol
}
3.3 ToolbarItemStatus 枚举
| 值 | 说明 |
|---|---|
NORMAL |
正常态,可交互 |
ACTIVE |
激活态,显示 activeIcon |
DISABLED |
禁用态,灰色不可点 |
3.4 NavigationToolbarOptions
interface NavigationToolbarOptions {
backgroundColor?: ResourceColor;
backgroundBlurStyle?: BlurStyle; // 背景模糊
backgroundBlurStyleOptions?: BlurStyleOptions;
hideItemValue?: boolean; // 是否隐藏文本(仅图标)
}
3.5 menus 与 BarMenuItem
右上角菜单按钮配置:
interface BarMenuItem {
value: ResourceStr;
icon?: ResourceStr;
action?: () => void;
}
竖屏最多显示 3 个图标菜单,超出自动折叠。
3.6 titleMode
| 模式 | 说明 |
|---|---|
Full |
大标题(112vp),适合首页 |
Mini |
紧凑小标题(56vp),适合二级页 |
Free |
滚动联动,标题随内容缩放 |
四、完整示例代码
@Entry
@Component
struct Index {
@State currentTabIndex: number = 0;
private readonly TAB_COUNT: number = 5;
private readonly tabLabels: string[] = ['首页','分类','消息','购物车','我的'];
private readonly tabIcon: Resource = $r('app.media.startIcon');
private readonly tabEmojis: string[] = ['🏠','📂','💬','🛒','👤'];
get toolbarItems(): ToolbarItem[] {
const items: ToolbarItem[] = [];
for (let i = 0; i < this.TAB_COUNT; i++) {
const item: ToolbarItem = {
value: this.tabLabels[i],
icon: this.tabIcon,
action: () => { this.onTabClicked(i); },
};
items.push(item);
}
return items;
}
get menuItems(): BarMenuItem[] {
return [
{ value: '搜索', icon: $r('app.media.startIcon'),
action: () => { console.info('点击搜索'); } },
{ value: '更多', icon: $r('app.media.startIcon'),
action: () => { console.info('点击更多'); } },
];
}
onTabClicked(index: number): void {
if (this.currentTabIndex === index) return;
this.currentTabIndex = index;
}
build() {
Navigation() {
Scroll() {
Column() {
Text(this.tabLabels[this.currentTabIndex])
.fontSize(30).fontWeight(FontWeight.Bold)
.margin({ top: 20, bottom: 2 });
Row().width(28).height(4)
.backgroundColor('#FF007AFF').borderRadius(2)
.margin({ bottom: 24 });
Column() {
Text(this.tabEmojis[this.currentTabIndex]).fontSize(72);
Text(`「${this.tabLabels[this.currentTabIndex]}」页面`)
.fontSize(20).fontWeight(FontWeight.Medium);
Text('点击底部工具栏切换页面').fontSize(14)
.fontColor('#FF999999');
}
.width('90%').padding(24)
.backgroundColor('#FFF8F9FA').borderRadius(16)
.alignItems(HorizontalAlign.Center);
}
.width('100%').padding({ top: 8 })
.alignItems(HorizontalAlign.Center);
}
.width('100%').height('100%').scrollBar(BarState.Off);
}
.titleMode(NavigationTitleMode.Full)
.title('鸿蒙示例')
.menus(this.menuItems)
.toolbarConfiguration(this.toolbarItems)
.hideToolBar(false)
.navBarWidth('100%').height('100%');
}
}
代码要点
- 响应式状态:
@State currentTabIndex驱动工具栏选中态与内容联动 - 计算属性:
get toolbarItems()动态构建数组,状态变化时自动重算 - 闭包捕获:
action: () => { this.onTabClicked(i); }用块级作用域正确捕获i - 链式配置:Navigation 属性全部通过链式调用配置
五、布局要点与最佳实践
5.1 工具栏项数量控制
竖屏模式下最多显示 5 个图标项,多余的会被自动放入「更多」菜单。设计上建议保持 4~5 项——过少显得空旷,过多则核心入口被折叠降低效率。工具栏选项的排列顺序也有讲究:最重要、最常用的入口放在最左侧和中间,因为这两个位置拇指点击最方便,符合用户从左到右的阅读习惯。
5.2 选中态图标切换
如果希望选中和未选中显示不同图标,可以利用 activeIcon 字段:
const item: ToolbarItem = {
value: '首页',
icon: $r('app.media.ic_home_normal'), // 未选中
activeIcon: $r('app.media.ic_home_active'), // 选中态
action: () => {},
};
系统会将 ACTIVE 态选项自动切换为 activeIcon。如果使用 Symbol 图标体系(推荐),则用 symbolIcon 和 activeSymbolIcon。
5.3 角标配置
工具栏项支持角标来展示未读消息数、购物车商品数等动态信息:
badge: {
count: 99,
maxCount: 99, // 超过 maxCount 显示 "99+"
position: BadgePosition.TOP_RIGHT,
}
实现时注意:count 为 0 时应动态移除角标对象或设为 undefined,避免显示「0」给用户造成困惑。
5.4 工具栏纯图标模式
.toolbarConfiguration(this.toolbarItems, {
hideItemValue: true, // 仅显示图标
})
适合 Tab 数量少且图标辨识度高的场景,比如底部只有 3 个功能截然不同的入口。
5.5 背景模糊效果
.toolbarConfiguration(items, {
backgroundBlurStyle: BlurStyle.COMPONENT_REGULAR,
})
毛玻璃效果让工具栏与内容区产生空间层次感,配合沉浸式内容尤其出色。注意模糊效果会增加渲染开销,低端设备谨慎使用。
5.6 结合路由系统
真正的工程化项目中,工具栏入口通常需要配合 NavPathStack 实现路由跳转:
private navPathStack: NavPathStack = new NavPathStack();
Navigation(this.navPathStack) { }
.toolbarConfiguration([
{ value: '首页', icon: $r('app.media.ic_home'),
action: () => { this.navPathStack.clear(); } },
{ value: '详情', icon: $r('app.media.ic_detail'),
action: () => { this.navPathStack.pushPath({ name: 'Detail' }); } },
])
pushPath 入栈新页面、pop 返回上一页、clear 回到根页面——路由栈的操作与工具栏无缝配合。
5.7 自定义工具栏(CustomBuilder)
当均分模式不够灵活时(如需要居中大按钮),使用 CustomBuilder 完全自定义:
@Builder
customToolbar() {
Row() {
Button('发布').width(56).height(56)
.backgroundColor('#FF007AFF').borderRadius(28);
}
.width('100%').justifyContent(FlexAlign.Center);
}
.toolbarConfiguration(() => { this.customToolbar(); })
自定义模式失去自动均分和 ACTIVE 态管理能力,一切由开发者自己控制,适合发布按钮、扫码入口等特殊交互。
六、常见编译错误排查
在 API 24 的 ArkTS 严格模式下,以下错误最为高频。掌握它们的根因和修复方法,可以大幅减少编译调试时间。
6.1 声明冲突 arkts-no-decl-merging
ERROR: Declaration merging is not supported (arkts-no-decl-merging)
原因:在代码中声明了与 SDK 内置类型同名的接口。API 24 中 ToolbarItem、BarMenuItem、MenuItem 等类型均已内置在全局空间中,不需要也不允许重复声明。
解决:删除本地接口声明,直接使用内置类型名称。
6.2 禁止 any (arkts-no-any-unknown)
ERROR: Use explicit types instead of "any", "unknown" (arkts-no-any-unknown)
原因:使用了 as any 或 as unknown 类型断言,这被 ArkTS 严格模式禁止。
解决:为变量添加准确类型注解,而非靠断言逃避类型检查:
// ❌ 错误
const item = { value: '首页', icon: icon, action: fn } as any;
// ✅ 正确
const item: ToolbarItem = { value: '首页', icon: icon, action: fn };
6.3 对象字面量未标注类型 (arkts-no-untyped-obj-literals)
ERROR: Object literal must correspond to some explicitly declared class or interface
原因:直接使用对象字面量而未标注类型。
解决:在变量声明处添加类型标注:
const item: ToolbarItem = { value: '首页', icon: icon, action: fn };
6.4 toolbarBorder 属性不存在
ERROR: Property 'toolbarBorder' does not exist on type 'NavigationAttribute'
原因:API 24 已移除该属性,工具栏边框样式通过 NavigationToolbarOptions 配置。
解决:删除 .toolbarBorder() 或改用选项参数中的 backgroundColor 等间接实现。
七、性能优化
7.1 缓存工具栏数组
工具栏配置项如果内容固定(如只有文字和图标变化),可以在 aboutToAppear 中预构建一次,避免每次 @State 变化都重新计算:
private cachedItems: ToolbarItem[] = [];
aboutToAppear(): void {
this.cachedItems = this.tabLabels.map((label, idx) => ({
value: label,
icon: this.tabIcon,
action: () => { this.onTabClicked(idx); },
}));
}
get toolbarItems(): ToolbarItem[] {
return this.cachedItems;
}
注意闭包中引用的 idx 在 map 回调中已被正确捕获,不会出现「所有按钮都指向最后一个索引」的问题。
7.2 SVG 矢量图标
工具栏图标推荐使用 SVG 矢量图,尺寸建议 24vp × 24vp。相比 PNG,SVG 体积小、无锯齿、支持主题色随系统自动切换。图标资源放在 resources/base/media/ 目录下,通过 $r('app.media.xxx') 引用。
7.3 懒加载列表
如果工具栏中间的内容区包含长列表(如电商首页的商品流),务必使用 LazyForEach 替代 ForEach:
LazyForEach(this.dataSource, (item: Item) => {
ListItem() { ItemCard({ data: item }); }
}, (item: Item) => item.id)
LazyForEach 只渲染可见区域的列表项,内存占用和渲染性能远优于全量渲染的 ForEach。
八、总结
Navigation + toolbar 布局是 HarmonyOS NEXT 开发中最核心的页面模式之一,也是每个鸿蒙应用开发者必须掌握的技能。本文从组件结构、API 详解、完整示例、最佳实践、常见错误到性能优化,覆盖了该布局的完整知识体系。
核心要点回顾:
- Navigation 三区结构:导航栏 + 内容区 + 工具栏,一组件承载完整页面框架
- toolbarConfiguration 配置:通过
ToolbarItem[]数组快速构建底部入口 - 状态管理:
@State驱动选中态切换,响应式更新页面内容 - API 24 语法规范:内置类型直达使用、对象字面量显式标注、禁止 any 断言
- 错误排查:声明冲突、类型标注遗漏、已废弃属性引用——掌握这三类即可解决 90% 的编译问题
实践建议:
- 优先使用系统内置的
ToolbarItem和BarMenuItem类型,不要自定义同名接口 - 保持工具栏数量在 4~5 个,避免超出后自动折叠影响用户体验
- 使用 SVG 矢量图标兼顾清晰度与性能,尺寸统一为 24vp
- 确保
for或map循环中正确捕获闭包变量,避免按钮点击全部指向最后一项 - 遇到编译错误时先确认是否为 API 内置类型与本地声明的冲突
HarmonyOS NEXT 的 ArkUI 框架正在快速发展,Navigation 组件的 API 也在持续演进。本文基于 API 24 编写,核心概念在后续版本中应保持兼容,但具体语法细节请以华为官方文档为准。建议开发者关注每次 SDK 升级的 API 变更日志,及时调整代码以利用新特性和修复。
附录:API 版本速查
| 功能 | API 10~11 | API 12~13 | API 24 |
|---|---|---|---|
| 工具栏类型 | ToolbarItem(需导入) |
ToolbarItem(需导入) |
ToolbarItem(内置) |
| 菜单类型 | NavigationMenuItem |
BarMenuItem |
BarMenuItem(内置) |
| 工具栏样式 | 基础 | NavigationToolbarOptions |
同左 |
| 严格模式 | 宽松 | 逐步收紧 | 严格 |



更多推荐




所有评论(0)