
从ArkUI组件树层级上来看,原先由Router管理的page在页面栈管理节点stage的下面。Navigation作为导航容器组件,可以挂载在单个page节点下,也可以叠加、嵌套。Navigation管理了标题栏、内容区和工具栏,内容区用于显示用户自定义页面的内容,并支持页面的路由能力。Navigation的这种设计上有如下优势:

业务场景 | Navigation | Router |
|---|---|---|
一多能力 | 支持,Auto模式自适应单栏跟双栏显示 | 不支持 |
跳转指定页面 | pushPath & pushDestination | pushUrl & pushNameRoute |
跳转HSP中页面 | 支持 | 支持 |
跳转HAR中页面 | 支持 | 支持 |
跳转传参 | 支持 | 支持 |
获取指定页面参数 | 支持 | 不支持 |
传参类型 | 传参为对象形式 | 传参为对象形式,对象中暂不支持方法变量 |
跳转结果回调 | 支持 | 支持 |
跳转单例页面 | 支持 | 支持 |
页面返回 | 支持 | 支持 |
页面返回传参 | 支持 | 支持 |
返回指定路由 | 支持 | 支持 |
页面返回弹窗 | 支持,通过路由拦截实现 | showAlertBeforeBackPage |
路由替换 | replacePath & replacePathByName | replaceUrl & replaceNameRoute |
路由栈清理 | clear | clear |
清理指定路由 | removeByIndexes & removeByName | 不支持 |
转场动画 | 支持 | 支持 |
自定义转场动画 | 支持 | 支持,动画类型受限 |
屏蔽转场动画 | 支持全局和单次 | 支持 设置pageTransition方法duration为0 |
geometryTransition共享元素动画 | 支持(NavDestination之间共享) | 不支持 |
页面生命周期监听 | UIObserver.on('navDestinationUpdate') | UIObserver.on('routerPageUpdate') |
获取页面栈对象 | 支持 | 不支持 |
路由拦截 | 支持通过setInercption做路由拦截 | 不支持 |
路由栈信息查询 | 支持 | getState() & getLength() |
路由栈move操作 | moveToTop & moveIndexToTop | 不支持 |
沉浸式页面 | 支持 | 不支持,需通过window配置 |
设置页面标题栏(titlebar)和工具栏(toolbar) | 支持 | 不支持 |
模态嵌套路由 | 支持 | 不支持 |
Router路由的页面是一个@Entry修饰的Component,每一个页面都需要在main_page.json中声明。
// main_page.json
{
"src": [
"pages/Index",
"pages/pageOne",
"pages/pageTwo"
]
}以下为Router页面的示例。
// index.ets
import { router } from '@kit.ArkUI';
@Entry
@Component
struct Index {
@State message: string = 'Hello World';
build() {
Row() {
Column() {
Text(this.message)
.fontSize(50)
.fontWeight(FontWeight.Bold)
Button('router to pageOne', { stateEffect: true, type: ButtonType.Capsule })
.width('80%')
.height(40)
.margin(20)
.onClick(() => {
router.pushUrl({
url: 'pages/pageOne' // 目标url
}, router.RouterMode.Standard, (err) => {
if (err) {
console.error(`Invoke pushUrl failed, code is ${err.code}, message is ${err.message}`);
return;
}
console.info('Invoke pushUrl succeeded.');
})
})
}
.width('100%')
}
.height('100%')
}
}// pageOne.ets
import { router } from '@kit.ArkUI';
@Entry
@Component
struct pageOne {
@State message: string = 'This is pageOne';
build() {
Row() {
Column() {
Text(this.message)
.fontSize(50)
.fontWeight(FontWeight.Bold)
Button('router back to Index', { stateEffect: true, type: ButtonType.Capsule })
.width('80%')
.height(40)
.margin(20)
.onClick(() => {
router.back();
})
}
.width('100%')
}
.height('100%')
}
}而基于Navigation的路由页面分为导航页和子页,导航页又叫Navbar,是Navigation包含的子组件,子页是NavDestination包含的子组件。
以下为Navigation导航页的示例。
// index.ets
@Entry
@Component
struct Index {
pathStack: NavPathStack = new NavPathStack()
build() {
Navigation(this.pathStack) {
Column() {
Button('Push PageOne', { stateEffect: true, type: ButtonType.Capsule })
.width('80%')
.height(40)
.margin(20)
.onClick(() => {
this.pathStack.pushPathByName('pageOne', null)
})
}.width('100%').height('100%')
}
.title("Navigation")
.mode(NavigationMode.Stack)
}
}以下为Navigation子页的示例。
// PageOne.ets
@Builder
export function PageOneBuilder() {
PageOne()
}
@Component
export struct PageOne {
pathStack: NavPathStack = new NavPathStack()
build() {
NavDestination() {
Column() {
Button('回到首页', { stateEffect: true, type: ButtonType.Capsule })
.width('80%')
.height(40)
.margin(20)
.onClick(() => {
this.pathStack.clear()
})
}.width('100%').height('100%')
}.title('PageOne')
.onReady((context: NavDestinationContext) => {
this.pathStack = context.pathStack
})
}
}每个子页也需要配置到系统配置文件route_map.json中(参考 系统路由表 )。
// 工程配置文件module.json5中配置 {"routerMap": "$profile:route_map"}
// route_map.json
{
"routerMap": [
{
"name": "pageOne",
"pageSourceFile": "src/main/ets/pages/PageOne.ets",
"buildFunction": "PageOneBuilder",
"data": {
"description": "this is pageOne"
}
}
]
}Router通过@ohos.router模块提供的方法来操作页面,使用前需要先import。
import { router } from '@kit.ArkUI';
// push page
router.pushUrl({ url:"pages/pageOne", params: null })
// pop page
router.back({ url: "pages/pageOne" })
// replace page
router.replaceUrl({ url: "pages/pageOne" })
// clear all page
router.clear()
// 获取页面栈大小
let size = router.getLength()
// 获取页面状态
let pageState = router.getState()Navigation通过页面栈对象 NavPathStack 提供的方法来操作页面,需要创建一个栈对象并传入Navigation中。
@Entry
@Component
struct Index {
pathStack: NavPathStack = new NavPathStack()
build() {
// 设置NavPathStack并传入Navigation
Navigation(this.pathStack) {
// ...
}.width('100%').height('100%')
.title("Navigation")
.mode(NavigationMode.Stack)
}
}
// push page
this.pathStack.pushPath({ name: 'pageOne' })
// pop page
this.pathStack.pop()
this.pathStack.popToIndex(1)
this.pathStack.popToName('pageOne')
// replace page
this.pathStack.replacePath({ name: 'pageOne' })
// clear all page
this.pathStack.clear()
// 获取页面栈大小
let size: number = this.pathStack.size()
// 删除栈中name为PageOne的所有页面
this.pathStack.removeByName("pageOne")
// 删除指定索引的页面
this.pathStack.removeByIndexes([1, 3, 5])
// 获取栈中所有页面name集合
this.pathStack.getAllPathName()
// 获取索引为1的页面参数
this.pathStack.getParamByIndex(1)
// 获取PageOne页面的参数
this.pathStack.getParamByName("pageOne")
// 获取PageOne页面的索引集合
this.pathStack.getIndexByName("pageOne")
// ...Router作为全局通用模块,可以在任意页面中调用,Navigation作为组件,子页面想要做路由需要拿到Navigation持有的页面栈对象NavPathStack,可以通过如下几种方式获取:
方式一:通过@Provide和@Consume传递给子页面(有耦合,不推荐)。
// Navigation根容器
@Entry
@Component
struct Index {
// Navigation创建一个Provide修饰的NavPathStack
@Provide('pathStack') pathStack: NavPathStack = new NavPathStack()
build() {
Navigation(this.pathStack) {
// ...
}
.title("Navigation")
.mode(NavigationMode.Stack)
}
}
// Navigation子页面
@Component
export struct PageOne {
// NavDestination通过Consume获取到
@Consume('pathStack') pathStack: NavPathStack;
build() {
NavDestination() {
// ...
}
.title("PageOne")
}
}方式二:子页面通过OnReady回调获取。
@Component
export struct PageOne {
pathStack: NavPathStack = new NavPathStack()
build() {
NavDestination() {
// ...
}.title('PageOne')
.onReady((context: NavDestinationContext) => {
this.pathStack = context.pathStack
})
}
}方式三: 通过全局的AppStorage接口设置获取。
@Entry
@Component
struct Index {
pathStack: NavPathStack = new NavPathStack()
// 全局设置一个NavPathStack
aboutToAppear(): void {
AppStorage.setOrCreate("PathStack", this.pathStack)
}
build() {
Navigation(this.pathStack) {
// ...
}.title("Navigation")
.mode(NavigationMode.Stack)
}
}
// Navigation子页面
@Component
export struct PageOne {
// 子页面中获取全局的NavPathStack
pathStack: NavPathStack = AppStorage.get("PathStack") as NavPathStack
build() {
NavDestination() {
// ...
}
.title("PageOne")
}
}方式四:通过自定义组件查询接口获取,参考 queryNavigationInfo 。
// 子页面中的自定义组件
@Component
struct CustomNode {
pathStack: NavPathStack = new NavPathStack()
aboutToAppear() {
// query navigation info
let navigationInfo: NavigationInfo = this.queryNavigationInfo() as NavigationInfo
this.pathStack = navigationInfo.pathStack;
}
build() {
Row() {
Button('跳转到PageTwo')
.onClick(() => {
this.pathStack.pushPath({ name: 'pageTwo' })
})
}
}
}欢迎大家关注公众号<程序猿百晓生>,可以了解到一下知识点。1.OpenHarmony开发基础
2.OpenHarmony北向开发环境搭建
3.鸿蒙南向开发环境的搭建
4.鸿蒙生态应用开发白皮书V2.0 & V3.0
5.鸿蒙开发面试真题(含参考答案)
6.TypeScript入门学习手册
7.OpenHarmony 经典面试题(含参考答案)
8.OpenHarmony设备开发入门【最新版】
9.沉浸式剖析OpenHarmony源代码
10.系统定制指南
11.【OpenHarmony】Uboot 驱动加载流程
12.OpenHarmony构建系统--GN与子系统、部件、模块详解
13.ohos开机init启动流程
14.鸿蒙版性能优化指南
.......Router页面生命周期为@Entry页面中的通用方法,主要有如下四个生命周期:
// 页面创建后挂树的回调
aboutToAppear(): void {
}
// 页面销毁前下树的回调
aboutToDisappear(): void {
}
// 页面显示时的回调
onPageShow(): void {
}
// 页面隐藏时的回调
onPageHide(): void {
}其生命周期时序如下图所示:

Navigation作为路由容器,其生命周期承载在NavDestination组件上,以组件事件的形式开放。
具体生命周期描述请参考Navigation 页面生命周期 。
@Component
struct PageOne {
aboutToDisappear() {
}
aboutToAppear() {
}
build() {
NavDestination() {
// ...
}
.onWillAppear(() => {
})
.onAppear(() => {
})
.onWillShow(() => {
})
.onShown(() => {
})
.onWillHide(() => {
})
.onHidden(() => {
})
.onWillDisappear(() => {
})
.onDisAppear(() => {
})
}
}Router和Navigation都提供了系统的转场动画也提供了自定义转场的能力。
其中Router自定义页面转场通过通用方法pageTransition()实现,具体可参考Router 页面转场动画 。
Navigation作为路由容器组件,其内部的页面切换动画本质上属于组件跟组件之间的属性动画,可以通过Navigation中的 customNavContentTransition 事件提供自定义转场动画的能力,具体实现可以参考Navigation 自定义转场 。(注意:Dialog类型的页面当前没有转场动画)
页面和页面之间跳转的时候需要进行共享元素过渡动画,Router可以通过通用属性sharedTransition来实现共享元素转场。
Navigation也提供了共享元素一镜到底的转场能力,需要配合geometryTransition属性,在子页面(NavDestination)之间切换时,可以实现共享元素转场,具体可参考 Navigation共享元素转场动画 。
Router可以通过命名路由的方式实现跨包跳转。
// library/src/main/ets/pages/Index.ets
// library为新建共享包自定义的名字
@Entry({ routeName: 'myPage' })
@Component
export struct MyComponent {
build() {
Row() {
Column() {
Text('Library Page')
.fontSize(50)
.fontWeight(FontWeight.Bold)
}
.width('100%')
}
.height('100%')
}
}import { router } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';
import('library/src/main/ets/pages/Index'); // 引入共享包中的命名路由页面
@Entry
@Component
struct Index {
build() {
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
Text('Hello World')
.fontSize(50)
.fontWeight(FontWeight.Bold)
.margin({ top: 20 })
.backgroundColor('#ccc')
.onClick(() => { // 点击跳转到其他共享包中的页面
try {
router.pushNamedRoute({
name: 'myPage',
params: {
data1: 'message',
data2: {
data3: [123, 456, 789]
}
}
})
} catch (err) {
let message = (err as BusinessError).message
let code = (err as BusinessError).code
console.error(`pushNamedRoute failed, code is ${code}, message is ${message}`);
}
})
}
.width('100%')
.height('100%')
}
}Navigation作为路由组件,默认支持跨包跳转。
@Component
export struct PageInHSP {
build() {
NavDestination() {
// ...
}
}
}export { PageInHSP } from "./src/main/ets/pages/PageInHSP"// 1.导入跨包的路由页面
import { PageInHSP } from 'library/src/main/ets/pages/PageInHSP'
@Entry
@Component
struct mainPage {
pageStack: NavPathStack = new NavPathStack()
@Builder pageMap(name: string) {
if (name === 'PageInHSP') {
// 2.定义路由映射表
PageInHSP()
}
}
build() {
Navigation(this.pageStack) {
Button("Push HSP Page")
.onClick(() => {
// 3.跳转到Hsp中的页面
this.pageStack.pushPath({ name: "PageInHSP" });
})
}
.mode(NavigationMode.Stack)
.navDestination(this.pageMap)
}
}以上是通过静态依赖的形式完成了跨包的路由,在大型的项目中一般跨模块的开发需要解耦,那就需要依赖动态路由的能力。
动态路由设计的目的是解决多个产品(Hap)之间可以复用相同的业务模块,各个业务模块之间解耦(模块之间跳转通过路由表跳转,不需要互相依赖)和路由功能扩展整合。
业务特性模块对外暴露的就是模块内支持完成具体业务场景的多个页面的集合;路由管理就是将每个模块支持的页面都用统一的路由表结构管理起来。 当产品需要某个业务模块时,就会注册对应的模块的路由表。
动态路由的优势:
Router实现动态路由主要有下面三个过程:
Navigation实现动态路由有如下两种实现方案:
方案一: 自定义路由表
基本实现跟上述Router动态路由类似。
具体的构建过程,可以参考Navigation 自动生成动态路由 示例。
方案二: 系统路由表
从API version 12版本开始,Navigation支持系统跨模块的路由表方案,整体设计是将路由表方案下沉到系统中管理,即在需要路由的各个业务模块(HSP/HAR)中独立配置router_map.json文件,在触发路由跳转时,应用只需要通过NavPathStack进行路由跳转,此时系统会自动完成路由模块的动态加载、组件构建,并完成路由跳转功能,从而实现了开发层面的模块解耦。
Router可以通过observer实现注册监听,接口定义请参考Router无感监听 observer.on('routerPageUpdate') 。
import { uiObserver } from '@kit.ArkUI';
function callBackFunc(info: uiObserver.RouterPageInfo) {
console.info("RouterPageInfo is : " + JSON.stringify(info))
}
// used in ability context.
uiObserver.on('routerPageUpdate', this.context, callBackFunc);
// used in UIContext.
uiObserver.on('routerPageUpdate', this.getUIContext(), callBackFunc);在页面状态发生变化时,注册的回调将会触发,开发者可以通过回调中传入的入参拿到页面的相关信息,如:页面的名字,索引,路径,生命周期状态等。
Navigation同样可以通过在observer中实现注册监听。
export default class EntryAbility extends UIAbility {
// ...
onWindowStageCreate(windowStage: window.WindowStage): void {
// ...
windowStage.getMainWindow((err: BusinessError, data) => {
// ...
windowClass = data;
// 获取UIContext实例。
let uiContext: UIContext = windowClass.getUIContext();
// 获取UIObserver实例。
let uiObserver : UIObserver = uiContext.getUIObserver();
// 注册DevNavigation的状态监听.
uiObserver.on("navDestinationUpdate",(info) => {
// NavDestinationState.ON_SHOWN = 0, NavDestinationState.ON_HIDE = 1
if (info.state == 0) {
// NavDestination组件显示时操作
console.info('page ON_SHOWN:' + info.name.toString());
}
})
})
}
}为了实现页面内自定义组件跟页面解耦,自定义组件中提供了全局查询页面信息的接口。
Router可以通过 queryRouterPageInfo 接口查询当前自定义组件所在的Page页面的信息,其返回值包含如下几个属性,其中pageId是页面的唯一标识符:
名称 | 类型 | 必填 | 说明 |
|---|---|---|---|
context | UIAbilityContext/ UIContext | 是 | routerPage页面对应的上下文信息 |
index | number | 是 | routerPage在栈中的位置。 |
name | string | 是 | routerPage页面的名称。 |
path | string | 是 | routerPage页面的路径。 |
state | RouterPageState | 是 | routerPage页面的状态 |
pageId<sup>12+</sup> | string | 是 | routerPage页面的唯一标识 |
import { uiObserver } from '@kit.ArkUI';
// 页面内的自定义组件
@Component
struct MyComponent {
aboutToAppear() {
let info: uiObserver.RouterPageInfo | undefined = this.queryRouterPageInfo();
}
build() {
// ...
}
}Navigation也可以通过 queryNavDestinationInfo 接口查询当前自定义组件所在的NavDestination的信息,其返回值包含如下几个属性,其中navDestinationId是页面的唯一标识符:
名称 | 类型 | 必填 | 说明 |
|---|---|---|---|
navigationId | ResourceStr | 是 | 包含NavDestination组件的Navigation组件的id。 |
name | ResourceStr | 是 | NavDestination组件的名称。 |
state | NavDestinationState | 是 | NavDestination组件的状态。 |
index<sup>12+</sup> | number | 是 | NavDestination在页面栈中的索引。 |
param<sup>12+</sup> | Object | 否 | NavDestination组件的参数。 |
navDestinationId<sup>12+</sup> | string | 是 | NavDestination组件的唯一标识ID。 |
import { uiObserver } from '@kit.ArkUI';
@Component
export struct NavDestinationExample {
build() {
NavDestination() {
MyComponent()
}
}
}
@Component
struct MyComponent {
navDesInfo: uiObserver.NavDestinationInfo | undefined
aboutToAppear() {
this.navDesInfo = this.queryNavDestinationInfo();
console.log('get navDestinationInfo: ' + JSON.stringify(this.navDesInfo))
}
build() {
// ...
}
}Router原生没有提供路由拦截的能力,开发者需要自行封装路由跳转接口,并在自己封装的接口中做路由拦截的判断并重定向路由。
Navigation提供了 setInterception 方法,用于设置Navigation页面跳转拦截回调。
如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。