在鸿蒙应用开发中,页面导航是构建应用骨架的关键。无论是底部菜单的切换,还是页面层级的跳转,ArkUI 主要为我们提供了两种核心的导航方式:Tabs(页签容器) 和 Navigation(路由导航)。
本文将深入探讨这两种组件的实战用法,从基础的 UI 构建到性能优化,再到系统级路由表的配置,助你掌握鸿蒙应用的页面管理。
Tabs 组件主要用于同一页面内不同内容的切换,它类似于传统的“轮播图”或“底部导航栏”模式。用户可以通过点击页签(TabBar)在不同的内容容器(TabContent)之间进行切换。
最简单的 Tabs 形态类似于轮播图。我们只需要在 Tabs 中包裹多个 TabContent 即可。
Column() { Tabs() { TabContent() { Text('1').fontSize(150) } TabContent() { Text('2').fontSize(150) } }}在移动端应用中,我们更常将其作为底部的 TabBar 使用。这需要配置 barPosition 属性为 BarPosition.End,并为每个 TabContent 设置 .tabBar 属性。
为了满足 UI 设计需求,系统默认的 Tab 样式往往不够用,这时我们可以利用 @Builder 装饰器来自定义构建 TabBar 的外观。
@Entry@Componentstruct TabsExample {// 自定义 TabBar 构建函数@Builder tabBuilder(text: string, fontSize: number) { Column() { Text(text).fontSize(fontSize) } } build() { Row() { Column() {// 设置导航栏位于底部 Tabs({ barPosition: BarPosition.End }) { TabContent() { Text('1').fontSize(150) } .tabBar(this.tabBuilder('首页', 20)) // 使用自定义 Builder TabContent() { Text('2').fontSize(150) } .tabBar(this.tabBuilder('我的', 20)) } } .width('100%') } .height('100%') }}在开发复杂应用时,性能是必须考虑的因素。Tabs 组件默认情况下会一次性渲染所有的 TabContent。如果每个页签内的内容非常复杂,这会造成巨大的内存压力和首屏加载延迟。
ArkUI 提供了 cachedMaxCount 属性来解决这个问题,它允许我们限制预加载和缓存的页面数量。此外,还可以通过 barMode 配合不同的缓存策略来优化体验:
cachedMaxCount(n),仅保留必要的页面缓存。CACHE_BOTH_SIDE:缓存当前页及其左右两侧的页面(适用于左右滑动的场景)。CACHE_LATEST_SWITCHED:缓存当前页和最近一次切换过的页面。对于页面之间的层级跳转(如从列表页进入详情页),ArkTS 推荐使用 Navigation 组件。它是鸿蒙应用中的“路由担当”,相比老旧的 router 接口,Navigation 支持更灵活的组件级路由和分栏显示。
Navigation 组件支持两种显示模式,通过 .mode() 属性设置:
NavigationMode.Stack(单页模式):适用于手机,页面层叠压入,类似栈结构。NavigationMode.Split(分栏模式):适用于折叠屏或平板,左侧导航,右侧内容。要让 Navigation 正常工作,必须集齐以下三个要素:
Navigation 组件:路由的根容器,必须包裹触发路由跳转的 UI。NavPathStack 类:路由的大脑,负责管理页面栈(Push, Pop, Clear 等操作)。NavDestination 组件:子页面的载体,所有跳转的目标页面内容必须用它包裹。以下演示如何结合 NavPathStack 实现参数传递与页面跳转:
第一步:定义子页面 (Page)
子页面必须使用 NavDestination 作为根节点。注意这里使用了 @Consume 来接收父组件提供的路由栈对象,以便实现“返回”功能。
@Componentstruct Page {@Prop pageIndex: number = -1;// 接收路由栈对象@Consume('navPathStack') navPathStack: NavPathStack; build() { NavDestination() { Column() { Text(this.pageIndex.toString()).fontSize(100) } .height('100%').width('100%').backgroundColor('#ffd25151') } .onBackPressed(() => {// 拦截物理返回键逻辑this.navPathStack.pop(); returntrue; // 返回 true 表示由于我们处理了返回逻辑,系统不再执行默认操作 }) }}第二步:定义主入口与路由逻辑 (NavDemo)
在主入口中,我们需要实例化 NavPathStack,并通过 @Provide 共享给子组件。
@Componentexportdefault struct NavDemo {// 初始化路由栈,并共享给后代组件@Provide('navPathStack') navPathStack: NavPathStack = new NavPathStack();private arr: number[] = [1, 2, 3];// 路由工厂:根据 name 决定渲染哪个页面@Builder pageBuilder(name: string) {if (name === 'Page1') { Page({ pageIndex: 1 }) } elseif (name === 'Page2') { Page({ pageIndex: 2 }) } else { Page({ pageIndex: 3 }) } } build() { Column() {// 绑定路由栈,并设置 navDestination Navigation(this.navPathStack) { List({ space: 12 }) { ForEach(this.arr, (item: number) => { ListItem() { Text('跳转到 Page' + item)// ...样式代码省略... .onClick(() => {// 执行跳转this.navPathStack.pushPath({ name: 'Page' + item }); }) } }) } } .title('主页面') .mode(NavigationMode.Stack) .navDestination(this.pageBuilder) // 绑定路由工厂 } .width('100%').height('100%') }}随着 App 页面越来越多,如果在 navDestination 的 @Builder 中写几十个 if-else 来判断跳转逻辑,代码将变得极其臃肿且难以维护。
为了解耦,鸿蒙提供了系统路由表方案。它允许我们通过 JSON 配置文件来定义路由,而无需手动编写 builder 函数。
module.json5**: 在 module 节点下增加 routerMap 字段,定义路由名称与页面文件的映射关系。{"module": {// ... 其他配置"routerMap": [ {"name": "PageOne","pageSourceFile": "src/main/ets/pages/PageOne.ets","buildFunction": "PageOneBuilder","data": {"description" : "这是页面1的描述" } } ] }}Navigation 组件上不再需要设置 .navDestination(this.pageBuilder),系统会自动根据 pushPath 中的 name 去匹配 JSON 中的配置。⚠️ 注意:预览器 (Preview) 限制系统路由表功能目前 仅支持模拟器(Emulator)和真机运行,在 DevEco Studio 的 Previewer(预览器)中是不起作用的。 如果你在开发过程中高度依赖预览器进行 UI 调试,请暂时保留手写的
navDestination方案,或准备好真机/模拟器进行测试。
cachedMaxCount 进行性能优化。navDestination 配合 Builder 即可;对于中大型项目,强烈建议使用 系统路由表 来实现模块解耦。希望这篇文章能帮你理清鸿蒙开发的导航逻辑。更多详细 API 用法,建议参考华为官方文档:Navigation-导航与切换 - 华为HarmonyOS开发者