HarmonyOS NEXT 的导航体系
HarmonyOS NEXT 提供了两套导航方案,适合不同场景:
Navigation 组件(推荐)
ArkUI 原生导航容器,支持自适应单/双列布局(手机显示单列,平板自动变双列)、NavDestination 页面管理、转场动画、路由拦截。这是 HarmonyOS NEXT 官方推荐的主导航方案,适合绝大多数应用。
Router 模块(传统方案)
@ohos.router 提供的命令式路由 API,通过 router.pushUrl()、router.back() 控制页面栈。语法更简单,但功能不如 Navigation 丰富,不支持自适应布局。适合简单应用或旧代码迁移。
Navigation 组件
Navigation 是一个智能导航容器,它会根据设备屏幕大小自动决定显示方式:手机显示单页模式(Stack),平板/折叠屏展开后显示双栏模式(Split),开发者无需为不同设备单独适配。
基本结构
// NavPathStack 是页面栈的控制器,负责 push/pop/replace 操作
// 通常在根组件中创建,向下传递
@Entry
@Component
struct MainPage {
// NavPathStack:页面路由栈,管理所有 NavDestination 页面
navStack: NavPathStack = new NavPathStack()
build() {
// Navigation 组件包裹整个应用导航区域
Navigation(this.navStack) {
// 这里是"主页"内容,也是 Navigation 的首屏
HomeContent({ navStack: this.navStack })
}
// 设置标题栏模式
.title('首页')
// 隐藏返回按钮(首页不需要)
.hideBackButton(true)
// 设置导航模式:Auto 自适应(手机Stack/平板Split)
.mode(NavigationMode.Auto)
// 注册路由表:key → 对应的 NavDestination 组件
.navDestination(this.PageMap)
}
// @Builder 定义页面路由映射表
@Builder
PageMap(name: string, param: object) {
if (name === 'ArticleDetail') {
ArticleDetailPage({ param: param as ArticleParam })
} else if (name === 'UserProfile') {
UserProfilePage({ param: param as UserParam })
} else if (name === 'Settings') {
SettingsPage()
}
}
}
页面跳转与传参
// 定义参数类型(强烈推荐定义接口,保证类型安全)
interface ArticleParam {
articleId: number
title: string
source: string
}
// 首页组件:触发页面跳转
@Component
struct HomeContent {
navStack: NavPathStack = new NavPathStack()
build() {
Column() {
Button('查看文章详情')
.onClick(() => {
// pushPathByName:跳转到命名路由,传递参数
this.navStack.pushPathByName('ArticleDetail', {
articleId: 42,
title: 'ArkUI 深度解析',
source: 'home'
} as ArticleParam)
})
Button('个人主页')
.onClick(() => {
this.navStack.pushPathByName('UserProfile', { userId: 1001 })
})
Button('替换当前页(不留历史)')
.onClick(() => {
// replacePathByName:替换当前页,不产生历史记录
this.navStack.replacePathByName('Settings', {})
})
}
}
}
// 详情页:接收参数
@Component
struct ArticleDetailPage {
param: ArticleParam = { articleId: 0, title: '', source: '' }
// @Consume 获取 Navigation 注入的路由栈(在子页面中使用)
pathStack: NavPathStack = new NavPathStack()
build() {
// NavDestination 是每个子页面的根容器
NavDestination() {
Column({ space: 16 }) {
Text(this.param.title).fontSize(24).fontWeight(FontWeight.Bold)
Text(`文章 ID:${this.param.articleId}`)
Button('返回')
.onClick(() => {
this.pathStack.pop() // 普通返回
// this.pathStack.popToName('Home') // 返回到指定页面
// this.pathStack.clear() // 清空历史
})
}
.padding(24)
}
.title(this.param.title)
}
}
底部 Tab 导航
大多数应用都有底部 Tab 栏。在 HarmonyOS NEXT 中,可以用 Tabs 组件或在 Navigation 内部嵌套实现:
@Entry
@Component
struct MainTabsPage {
@State currentTab: number = 0
private tabsController: TabsController = new TabsController()
build() {
Tabs({
barPosition: BarPosition.End, // 底部 Tab 栏
controller: this.tabsController,
index: this.currentTab
}) {
// 每个 TabContent 对应一个 Tab 页
TabContent() {
HomePage()
}
.tabBar(this.TabBarItem('首页', '🏠', 0))
TabContent() {
DiscoverPage()
}
.tabBar(this.TabBarItem('发现', '🔍', 1))
TabContent() {
ProfilePage()
}
.tabBar(this.TabBarItem('我的', '👤', 2))
}
.onChange((index: number) => {
this.currentTab = index
})
.barMode(BarMode.Fixed) // Tab 均分宽度
}
// 自定义 TabBar 图标样式
@Builder
TabBarItem(label: string, icon: string, index: number) {
Column({ space: 4 }) {
Text(icon).fontSize(22)
Text(label)
.fontSize(11)
.fontColor(this.currentTab === index ? '#CF0A2C' : '#768390')
}
.padding({ top: 8, bottom: 8 })
}
}
页面转场动画
HarmonyOS NEXT 提供了丰富的页面转场动画,既有系统预设,也支持完全自定义:
// NavDestination 内设置转场动画
NavDestination() {
// 页面内容
}
.customNavContentTransition(this.customTransition)
// 自定义转场:从右侧滑入
customTransition(
from: NavContentInfo,
to: NavContentInfo,
operation: NavigationOperation
): NavigationAnimatedTransition | undefined {
if (operation === NavigationOperation.PUSH) {
return {
onTransitionEnd: (isSuccess: boolean) => {
console.log('转场完成', isSuccess)
},
timeout: 1000,
transition: (transitionProxy: NavigationTransitionProxy) => {
// 使用 animateTo 控制动画
animateTo({
duration: 350,
curve: Curve.EaseOut
}, () => {
transitionProxy.finishTransition()
})
}
}
}
return undefined // 使用默认转场
}
// 组件内动画(进入/退出)
Column()
.transition(
TransitionEffect.OPACITY.combine(
TransitionEffect.translate({ x: 100 })
)
.animation({ duration: 300, curve: Curve.EaseOut })
)
深链接与 Want
深链接(Deep Link)允许外部应用或网页直接跳转到应用内的特定页面。在 HarmonyOS NEXT 中,通过配置 Want 的 URI 和 Action 实现:
// module.json5 中配置深链接
// "skills" 字段声明 Ability 响应的 URI
{
"skills": [{
"entities": ["entity.system.home"],
"actions": ["ohos.want.action.viewData"],
"uris": [{
"scheme": "myapp",
"host": "article",
"pathStartWith": "/"
}]
}]
}
// EntryAbility.ts — 处理深链接跳转
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {
this.handleWant(want)
}
// 应用已在前台时收到新 Want
onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam) {
this.handleWant(want)
}
handleWant(want: Want) {
const uri = want.uri // e.g. "myapp://article/123"
if (uri) {
const url = new URL(uri)
const articleId = url.pathname.replace('/', '')
// 通过 AppStorage 传递跳转目标,让页面组件监听并跳转
AppStorage.setOrCreate('deepLinkTarget', {
page: 'ArticleDetail',
params: { articleId: Number(articleId) }
})
}
}
路由拦截与守卫
类似 Vue Router 的 beforeEach 守卫,Navigation 支持在页面跳转前执行拦截逻辑,常用于登录态检验:
Navigation(this.navStack) {
HomeContent()
}
.navDestination(this.PageMap)
// 路由拦截:跳转前执行检查
.onNavBarStateChange((isVisible: boolean) => {
console.log('导航栏可见性变化:', isVisible)
})
// 监听路由栈变化
.onNavigationModeChange((mode: NavigationMode) => {
console.log('导航模式变化(单/双栏切换):', mode)
})
// 在跳转时手动拦截
function navigateWithAuth(
navStack: NavPathStack,
pageName: string,
params: object
) {
const isLoggedIn = AppStorage.get<boolean>('isLoggedIn')
const needsAuth = ['UserProfile', 'Orders', 'Checkout']
if (needsAuth.includes(pageName) && !isLoggedIn) {
// 未登录跳转到登录页,带上原目标作为重定向参数
navStack.pushPathByName('Login', {
redirect: pageName,
redirectParams: params
})
return
}
navStack.pushPathByName(pageName, params)
}
Navigation vs Router 如何选择
新项目统一使用 Navigation + NavDestination 方案。它支持多设备自适应、转场动画定制、路由拦截,且与华为未来的多窗口特性兼容。Router 模块虽然简单,但不支持自适应布局,在折叠屏和平板上体验差。老项目可以逐步将 Router 迁移到 Navigation。
页面间数据回传
从详情页返回时,有时需要把结果传回给调用方(类似 Android 的 startActivityForResult)。HarmonyOS NEXT 通过 NavPathStack.pop() 的返回值机制实现:
// 详情页:带返回值弹出
@Component
struct EditProfilePage {
pathStack: NavPathStack = new NavPathStack()
@State newName: string = ''
build() {
NavDestination() {
Column({ space: 16 }) {
TextInput({ placeholder: '输入新名称' })
.onChange((val: string) => { this.newName = val })
Button('保存并返回')
.onClick(() => {
// pop 时携带返回值,第二个参数是回调
// 调用方通过 pushPathByName 的 onPop 回调接收
this.pathStack.pop({ savedName: this.newName })
})
}
}
}
}
// 主页:推入时注册 onPop 回调
@Component
struct ProfilePage {
navStack: NavPathStack = new NavPathStack()
@State userName: string = '未设置'
build() {
Column() {
Text(`当前名称:${this.userName}`)
Button('编辑')
.onClick(() => {
// pushPathByName 第三个参数:子页面 pop 时的回调
this.navStack.pushPathByName('EditProfile', {},
(info: PopInfo) => {
const result = info.result as { savedName: string }
if (result?.savedName) {
this.userName = result.savedName
}
}
)
})
}
}
}
NavPathStack 作用域陷阱
NavPathStack 实例必须在整个 Navigation 组件树中共享同一个引用,不能在每个子组件中
new NavPathStack()。常见错误是在子组件中直接 navStack: NavPathStack = new NavPathStack(),这会创建一个新的空栈,调用 push/pop 不会对父 Navigation 产生任何效果。应通过属性传递或 @Provide/@Consume 在组件树中共享同一个栈实例。
多 HAP 跨模块导航
大型应用通常拆分为多个 HAP 模块(entry HAP + feature HAP)。跨模块的页面导航需要额外配置路由表,因为 PageMap 里的 @Builder 函数无法直接引用另一个 HAP 的组件:
路由表(Router Map)
在 resources/base/profile/router_map.json 中配置页面名称与源码路径的映射。Navigation 组件通过 navigationId 绑定路由表,跨模块页面按路由名懒加载,不需要在 PageMap 里逐一 if/else 判断。这是大型项目的推荐方案(API 12+)。
HSP(HarmonyOS Shared Package)
Harmony Shared Package,可被多个 HAP 共享的模块。导航目标组件如果在 HSP 中,需要将该 HSP 声明为 feature HAP 的依赖,并通过路由表注册组件路径。
// resources/base/profile/router_map.json
{
"routerMap": [
{
"name": "ArticleDetail", // 路由名称(全局唯一)
"pageSourceFile": "src/main/ets/pages/ArticleDetailPage.ets",
"buildFunction": "ArticleDetailBuilder", // @Builder 函数名
"data": {
"description": "文章详情页"
}
},
{
"name": "UserProfile",
"pageSourceFile": "src/main/ets/pages/UserProfilePage.ets",
"buildFunction": "UserProfileBuilder"
}
]
}
// Navigation 绑定路由表
// Navigation 组件设置 navigationId,框架自动查找对应的 router_map.json
Navigation(this.navStack) {
HomeContent()
}
.navDestination(this.PageMap) // 仍可保留简单页面的 Builder
模态弹出(Sheet / Dialog)
除了页面级导航,HarmonyOS NEXT 还提供了半模态(Sheet)和自定义弹窗(CustomDialog)用于不完全切换页面的场景:
// 半模态弹出(底部抽屉)
@Entry
@Component
struct MainPage {
@State showSheet: boolean = false
build() {
Column() {
Button('查看评论')
.onClick(() => { this.showSheet = true })
}
// .bindSheet:绑定半模态弹出
.bindSheet($$this.showSheet, this.SheetContent(), {
detents: [SheetSize.MEDIUM, SheetSize.LARGE], // 可拖拽高度档位
showClose: true,
title: { title: '评论区' }
})
}
// Sheet 内容(独立的 @Builder 函数)
@Builder
SheetContent() {
ScrollView() {
Column({ space: 12 }) {
// 评论列表...
}
}
.height('100%')
}
}
// 自定义弹窗(CustomDialog)
@CustomDialog
struct ConfirmDialog {
controller: CustomDialogController
confirm: () => void = () => {}
cancel: () => void = () => {}
message: string = ''
build() {
Column({ space: 16 }) {
Text(this.message).fontSize(16)
Row({ space: 12 }) {
Button('取消').onClick(() => {
this.controller.close()
this.cancel()
})
Button('确认').onClick(() => {
this.controller.close()
this.confirm()
})
}
}
.padding(24)
}
}
// 使用自定义弹窗
@Entry
@Component
struct PageWithDialog {
dialogController: CustomDialogController = new CustomDialogController({
builder: ConfirmDialog({
message: '确认删除此文章吗?',
confirm: () => { /* 执行删除 */ },
cancel: () => { /* 取消操作 */ }
}),
alignment: DialogAlignment.Center,
maskColor: 'rgba(0,0,0,0.5)'
})
build() {
Button('删除')
.onClick(() => this.dialogController.open())
}
}
本章小结
HarmonyOS NEXT 的导航体系以 Navigation + NavDestination 为核心,支持自适应单/双栏布局。关键要点:NavPathStack 是路由栈的唯一控制器,必须在组件树中共享同一实例;路由表(router_map.json)适合大型多模块项目;页面间数据回传通过 pushPathByName 的 onPop 回调实现;半模态(Sheet)和 CustomDialog 用于轻量级弹出场景。掌握这套体系,可以构建任意复杂度的多页面鸿蒙应用。