智能客服
你问我答,随时在线为你解决问题
鉴于组件导航(Navigation)支持更丰富的动效、一次开发多端部署能力和更灵活的栈操作。本文主要从页面跳转、动效和生命周期等方面介绍如何从Router切换到Navigation。
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' })
- })
- }
- }
- }
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可以通过命名路由的方式实现跨包跳转。
在想要跳转到的共享包HAR或者HSP页面里,给@Entry修饰的自定义组件EntryOptions命名。
- // 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作为路由组件,默认支持跨包跳转。
从HSP(HAR)中完成自定义组件(需要跳转的目标页面)开发,将自定义组件申明为export。
- @Component
- export struct PageInHSP {
- build() {
- NavDestination() {
- // ...
- }
- }
- }
在HSP(HAR)的index.ets中导出组件。
- export { PageInHSP } from "./src/main/ets/pages/PageInHSP"
配置好HSP(HAR)的项目依赖后,在mainPage中导入自定义组件,并添加到pageMap中,即可正常调用。
- // 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实现动态路由主要有下面三个过程:
定义过程: 路由表定义新增路由 -> 页面文件绑定路由名称(装饰器) -> 加载函数和页面文件绑定(动态import函数)
定义注册过程: 路由注册(可在入口ability中按需注入依赖模块的路由表)。
Navigation实现动态路由有如下两种实现方案:
方案一: 自定义路由表
基本实现跟上述Router动态路由类似。
具体的构建过程,可以参考Navigation自动生成动态路由示例。
方案二: 系统路由表
从API version 12版本开始,Navigation支持系统跨模块的路由表方案,整体设计是将路由表方案下沉到系统中管理,即在需要路由的各个业务模块(HSP/HAR)中独立配置router_map.json文件,在触发路由跳转时,应用只需要通过NavPathStack进行路由跳转,此时系统会自动完成路由模块的动态加载、组件构建,并完成路由跳转功能,从而实现了开发层面的模块解耦。
具体可参考Navigation系统路由表。
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中实现注册监听。
- // EntryAbility.ets
- import { BusinessError } from '@kit.BasicServicesKit';
- import { UIObserver } from '@kit.ArkUI';
-
- export default class EntryAbility extends UIAbility {
- // ...
- onWindowStageCreate(windowStage: window.WindowStage): void {
- // ...
- windowStage.getMainWindow((err: BusinessError, data) => {
- // ...
- let 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页面的状态 |
| pageId12+ | 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组件的状态。 |
| index12+ | number | 是 | NavDestination在页面栈中的索引。 |
| param12+ | Object | 否 | NavDestination组件的参数。 |
| navDestinationId12+ | 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页面跳转拦截回调。具体可以参考文档:Navigation路由拦截