文档管理中心
您当前浏览的HarmonyOS 5.0.0(API 12)文档归档不再维护,推荐您使用最新版本。详细请参考文档维护策略变更

Router切换Navigation

鉴于组件导航(Navigation)支持更丰富的动效、一次开发多端部署能力和更灵活的栈操作。本文主要从页面跳转、动效和生命周期等方面介绍如何从Router切换到Navigation。

页面结构

Router路由的页面是一个@Entry修饰的Component,每一个页面都需要在main_page.json中声明。

收起
自动换行
深色代码主题
复制
  1. // main_page.json
  2. {
  3. "src": [
  4. "pages/Index",
  5. "pages/pageOne",
  6. "pages/pageTwo"
  7. ]
  8. }

以下为Router页面的示例。

收起
自动换行
深色代码主题
复制
  1. // index.ets
  2. import { router } from '@kit.ArkUI';
  3. @Entry
  4. @Component
  5. struct Index {
  6. @State message: string = 'Hello World';
  7. build() {
  8. Row() {
  9. Column() {
  10. Text(this.message)
  11. .fontSize(50)
  12. .fontWeight(FontWeight.Bold)
  13. Button('router to pageOne', { stateEffect: true, type: ButtonType.Capsule })
  14. .width('80%')
  15. .height(40)
  16. .margin(20)
  17. .onClick(() => {
  18. router.pushUrl({
  19. url: 'pages/pageOne' // 目标url
  20. }, router.RouterMode.Standard, (err) => {
  21. if (err) {
  22. console.error(`Invoke pushUrl failed, code is ${err.code}, message is ${err.message}`);
  23. return;
  24. }
  25. console.info('Invoke pushUrl succeeded.');
  26. })
  27. })
  28. }
  29. .width('100%')
  30. }
  31. .height('100%')
  32. }
  33. }
收起
自动换行
深色代码主题
复制
  1. // pageOne.ets
  2. import { router } from '@kit.ArkUI';
  3. @Entry
  4. @Component
  5. struct pageOne {
  6. @State message: string = 'This is pageOne';
  7. build() {
  8. Row() {
  9. Column() {
  10. Text(this.message)
  11. .fontSize(50)
  12. .fontWeight(FontWeight.Bold)
  13. Button('router back to Index', { stateEffect: true, type: ButtonType.Capsule })
  14. .width('80%')
  15. .height(40)
  16. .margin(20)
  17. .onClick(() => {
  18. router.back();
  19. })
  20. }
  21. .width('100%')
  22. }
  23. .height('100%')
  24. }
  25. }

而基于Navigation的路由页面分为导航页和子页,导航页又叫Navbar,是Navigation包含的子组件,子页是NavDestination包含的子组件。

以下为Navigation导航页的示例。

收起
自动换行
深色代码主题
复制
  1. // index.ets
  2. @Entry
  3. @Component
  4. struct Index {
  5. pathStack: NavPathStack = new NavPathStack()
  6. build() {
  7. Navigation(this.pathStack) {
  8. Column() {
  9. Button('Push PageOne', { stateEffect: true, type: ButtonType.Capsule })
  10. .width('80%')
  11. .height(40)
  12. .margin(20)
  13. .onClick(() => {
  14. this.pathStack.pushPathByName('pageOne', null)
  15. })
  16. }.width('100%').height('100%')
  17. }
  18. .title("Navigation")
  19. .mode(NavigationMode.Stack)
  20. }
  21. }

以下为Navigation子页的示例。

收起
自动换行
深色代码主题
复制
  1. // PageOne.ets
  2. @Builder
  3. export function PageOneBuilder() {
  4. PageOne()
  5. }
  6. @Component
  7. export struct PageOne {
  8. pathStack: NavPathStack = new NavPathStack()
  9. build() {
  10. NavDestination() {
  11. Column() {
  12. Button('回到首页', { stateEffect: true, type: ButtonType.Capsule })
  13. .width('80%')
  14. .height(40)
  15. .margin(20)
  16. .onClick(() => {
  17. this.pathStack.clear()
  18. })
  19. }.width('100%').height('100%')
  20. }.title('PageOne')
  21. .onReady((context: NavDestinationContext) => {
  22. this.pathStack = context.pathStack
  23. })
  24. }
  25. }

每个子页也需要配置到系统配置文件route_map.json中(参考系统路由表)。

收起
自动换行
深色代码主题
复制
  1. // 工程配置文件module.json5中配置 {"routerMap": "$profile:route_map"}
  2. // route_map.json
  3. {
  4. "routerMap": [
  5. {
  6. "name": "pageOne",
  7. "pageSourceFile": "src/main/ets/pages/PageOne.ets",
  8. "buildFunction": "PageOneBuilder",
  9. "data": {
  10. "description": "this is pageOne"
  11. }
  12. }
  13. ]
  14. }

路由操作

Router通过@ohos.router模块提供的方法来操作页面,使用前需要先import。

收起
自动换行
深色代码主题
复制
  1. import { router } from '@kit.ArkUI';
  2. // push page
  3. router.pushUrl({ url:"pages/pageOne", params: null })
  4. // pop page
  5. router.back({ url: "pages/pageOne" })
  6. // replace page
  7. router.replaceUrl({ url: "pages/pageOne" })
  8. // clear all page
  9. router.clear()
  10. // 获取页面栈大小
  11. let size = router.getLength()
  12. // 获取页面状态
  13. let pageState = router.getState()

Navigation通过页面栈对象NavPathStack提供的方法来操作页面,需要创建一个栈对象并传入Navigation中。

收起
自动换行
深色代码主题
复制
  1. @Entry
  2. @Component
  3. struct Index {
  4. pathStack: NavPathStack = new NavPathStack()
  5. build() {
  6. // 设置NavPathStack并传入Navigation
  7. Navigation(this.pathStack) {
  8. // ...
  9. }.width('100%').height('100%')
  10. .title("Navigation")
  11. .mode(NavigationMode.Stack)
  12. }
  13. }
  14. // push page
  15. this.pathStack.pushPath({ name: 'pageOne' })
  16. // pop page
  17. this.pathStack.pop()
  18. this.pathStack.popToIndex(1)
  19. this.pathStack.popToName('pageOne')
  20. // replace page
  21. this.pathStack.replacePath({ name: 'pageOne' })
  22. // clear all page
  23. this.pathStack.clear()
  24. // 获取页面栈大小
  25. let size: number = this.pathStack.size()
  26. // 删除栈中name为PageOne的所有页面
  27. this.pathStack.removeByName("pageOne")
  28. // 删除指定索引的页面
  29. this.pathStack.removeByIndexes([1, 3, 5])
  30. // 获取栈中所有页面name集合
  31. this.pathStack.getAllPathName()
  32. // 获取索引为1的页面参数
  33. this.pathStack.getParamByIndex(1)
  34. // 获取PageOne页面的参数
  35. this.pathStack.getParamByName("pageOne")
  36. // 获取PageOne页面的索引集合
  37. this.pathStack.getIndexByName("pageOne")
  38. // ...

Router作为全局通用模块,可以在任意页面中调用,Navigation作为组件,子页面想要做路由需要拿到Navigation持有的页面栈对象NavPathStack,可以通过如下几种方式获取:

方式一:通过@Provide和@Consume传递给子页面(有耦合,不推荐)。

收起
自动换行
深色代码主题
复制
  1. // Navigation根容器
  2. @Entry
  3. @Component
  4. struct Index {
  5. // Navigation创建一个Provide修饰的NavPathStack
  6. @Provide('pathStack') pathStack: NavPathStack = new NavPathStack()
  7. build() {
  8. Navigation(this.pathStack) {
  9. // ...
  10. }
  11. .title("Navigation")
  12. .mode(NavigationMode.Stack)
  13. }
  14. }
  15. // Navigation子页面
  16. @Component
  17. export struct PageOne {
  18. // NavDestination通过Consume获取到
  19. @Consume('pathStack') pathStack: NavPathStack;
  20. build() {
  21. NavDestination() {
  22. // ...
  23. }
  24. .title("PageOne")
  25. }
  26. }

方式二:子页面通过OnReady回调获取。

收起
自动换行
深色代码主题
复制
  1. @Component
  2. export struct PageOne {
  3. pathStack: NavPathStack = new NavPathStack()
  4. build() {
  5. NavDestination() {
  6. // ...
  7. }.title('PageOne')
  8. .onReady((context: NavDestinationContext) => {
  9. this.pathStack = context.pathStack
  10. })
  11. }
  12. }

方式三: 通过全局的AppStorage接口设置获取。

收起
自动换行
深色代码主题
复制
  1. @Entry
  2. @Component
  3. struct Index {
  4. pathStack: NavPathStack = new NavPathStack()
  5. // 全局设置一个NavPathStack
  6. aboutToAppear(): void {
  7. AppStorage.setOrCreate("PathStack", this.pathStack)
  8. }
  9. build() {
  10. Navigation(this.pathStack) {
  11. // ...
  12. }.title("Navigation")
  13. .mode(NavigationMode.Stack)
  14. }
  15. }
  16. // Navigation子页面
  17. @Component
  18. export struct PageOne {
  19. // 子页面中获取全局的NavPathStack
  20. pathStack: NavPathStack = AppStorage.get("PathStack") as NavPathStack
  21. build() {
  22. NavDestination() {
  23. // ...
  24. }
  25. .title("PageOne")
  26. }
  27. }

方式四:通过自定义组件查询接口获取,参考queryNavigationInfo

收起
自动换行
深色代码主题
复制
  1. // 子页面中的自定义组件
  2. @Component
  3. struct CustomNode {
  4. pathStack: NavPathStack = new NavPathStack()
  5. aboutToAppear() {
  6. // query navigation info
  7. let navigationInfo: NavigationInfo = this.queryNavigationInfo() as NavigationInfo
  8. this.pathStack = navigationInfo.pathStack;
  9. }
  10. build() {
  11. Row() {
  12. Button('跳转到PageTwo')
  13. .onClick(() => {
  14. this.pathStack.pushPath({ name: 'pageTwo' })
  15. })
  16. }
  17. }
  18. }

生命周期

Router页面生命周期为@Entry页面中的通用方法,主要有如下四个生命周期:

收起
自动换行
深色代码主题
复制
  1. // 页面创建后挂树的回调
  2. aboutToAppear(): void {
  3. }
  4. // 页面销毁前下树的回调
  5. aboutToDisappear(): void {
  6. }
  7. // 页面显示时的回调
  8. onPageShow(): void {
  9. }
  10. // 页面隐藏时的回调
  11. onPageHide(): void {
  12. }

其生命周期时序如下图所示:

Navigation作为路由容器,其生命周期承载在NavDestination组件上,以组件事件的形式开放。

具体生命周期描述请参考Navigation页面生命周期

收起
自动换行
深色代码主题
复制
  1. @Component
  2. struct PageOne {
  3. aboutToDisappear() {
  4. }
  5. aboutToAppear() {
  6. }
  7. build() {
  8. NavDestination() {
  9. // ...
  10. }
  11. .onWillAppear(() => {
  12. })
  13. .onAppear(() => {
  14. })
  15. .onWillShow(() => {
  16. })
  17. .onShown(() => {
  18. })
  19. .onWillHide(() => {
  20. })
  21. .onHidden(() => {
  22. })
  23. .onWillDisappear(() => {
  24. })
  25. .onDisAppear(() => {
  26. })
  27. }
  28. }

转场动画

Router和Navigation都提供了系统的转场动画也提供了自定义转场的能力。

其中Router自定义页面转场通过通用方法pageTransition()实现,具体可参考Router页面转场动画

Navigation作为路由容器组件,其内部的页面切换动画本质上属于组件跟组件之间的属性动画,可以通过Navigation中的customNavContentTransition事件提供自定义转场动画的能力,具体实现可以参考Navigation自定义转场。(注意:Dialog类型的页面当前没有转场动画)

共享元素转场

页面和页面之间跳转的时候需要进行共享元素过渡动画,Router可以通过通用属性sharedTransition来实现共享元素转场,具体可以参考如下链接:

Router共享元素转场动画

Navigation也提供了共享元素一镜到底的转场能力,需要配合geometryTransition属性,在子页面(NavDestination)之间切换时,可以实现共享元素转场,具体可参考Navigation共享元素转场动画

跨包路由

Router可以通过命名路由的方式实现跨包跳转。

  1. 在想要跳转到的共享包HAR或者HSP页面里,给@Entry修饰的自定义组件EntryOptions命名。

    收起
    自动换行
    深色代码主题
    复制
    1. // library/src/main/ets/pages/Index.ets
    2. // library为新建共享包自定义的名字
    3. @Entry({ routeName: 'myPage' })
    4. @Component
    5. export struct MyComponent {
    6. build() {
    7. Row() {
    8. Column() {
    9. Text('Library Page')
    10. .fontSize(50)
    11. .fontWeight(FontWeight.Bold)
    12. }
    13. .width('100%')
    14. }
    15. .height('100%')
    16. }
    17. }
  2. 配置成功后需要在跳转的页面中引入命名路由的页面并跳转。

    收起
    自动换行
    深色代码主题
    复制
    1. import { router } from '@kit.ArkUI';
    2. import { BusinessError } from '@kit.BasicServicesKit';
    3. import('library/src/main/ets/pages/Index'); // 引入共享包中的命名路由页面
    4. @Entry
    5. @Component
    6. struct Index {
    7. build() {
    8. Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
    9. Text('Hello World')
    10. .fontSize(50)
    11. .fontWeight(FontWeight.Bold)
    12. .margin({ top: 20 })
    13. .backgroundColor('#ccc')
    14. .onClick(() => { // 点击跳转到其他共享包中的页面
    15. try {
    16. router.pushNamedRoute({
    17. name: 'myPage',
    18. params: {
    19. data1: 'message',
    20. data2: {
    21. data3: [123, 456, 789]
    22. }
    23. }
    24. })
    25. } catch (err) {
    26. let message = (err as BusinessError).message
    27. let code = (err as BusinessError).code
    28. console.error(`pushNamedRoute failed, code is ${code}, message is ${message}`);
    29. }
    30. })
    31. }
    32. .width('100%')
    33. .height('100%')
    34. }
    35. }

Navigation作为路由组件,默认支持跨包跳转。

  1. 从HSP(HAR)中完成自定义组件(需要跳转的目标页面)开发,将自定义组件申明为export。

    收起
    自动换行
    深色代码主题
    复制
    1. @Component
    2. export struct PageInHSP {
    3. build() {
    4. NavDestination() {
    5. // ...
    6. }
    7. }
    8. }
  2. 在HSP(HAR)的index.ets中导出组件。

    收起
    自动换行
    深色代码主题
    复制
    1. export { PageInHSP } from "./src/main/ets/pages/PageInHSP"
  3. 配置好HSP(HAR)的项目依赖后,在mainPage中导入自定义组件,并添加到pageMap中,即可正常调用。

    收起
    自动换行
    深色代码主题
    复制
    1. // 1.导入跨包的路由页面
    2. import { PageInHSP } from 'library/src/main/ets/pages/PageInHSP'
    3. @Entry
    4. @Component
    5. struct mainPage {
    6. pageStack: NavPathStack = new NavPathStack()
    7. @Builder pageMap(name: string) {
    8. if (name === 'PageInHSP') {
    9. // 2.定义路由映射表
    10. PageInHSP()
    11. }
    12. }
    13. build() {
    14. Navigation(this.pageStack) {
    15. Button("Push HSP Page")
    16. .onClick(() => {
    17. // 3.跳转到Hsp中的页面
    18. this.pageStack.pushPath({ name: "PageInHSP" });
    19. })
    20. }
    21. .mode(NavigationMode.Stack)
    22. .navDestination(this.pageMap)
    23. }
    24. }

以上是通过静态依赖的形式完成了跨包的路由,在大型的项目中一般跨模块的开发需要解耦,那就需要依赖动态路由的能力。

动态路由

动态路由设计的目的是解决多个产品(Hap)之间可以复用相同的业务模块,各个业务模块之间解耦(模块之间跳转通过路由表跳转,不需要互相依赖)和路由功能扩展整合。

业务特性模块对外暴露的就是模块内支持完成具体业务场景的多个页面的集合;路由管理就是将每个模块支持的页面都用统一的路由表结构管理起来。 当产品需要某个业务模块时,就会注册对应的模块的路由表。

动态路由的优势:

  1. 路由定义除了跳转的URL以外,可以丰富的配置任意扩展信息,如横竖屏默认模式,是否需要鉴权等等,做路由跳转时的统一处理。
  2. 给每个路由设置一个名字,按照名称进行跳转而不是ets文件路径。
  3. 页面的加载可以使用动态Import(按需加载),防止首个页面加载大量代码导致卡顿。

Router实现动态路由主要有下面三个过程:

  1. 定义过程: 路由表定义新增路由 -> 页面文件绑定路由名称(装饰器) -> 加载函数和页面文件绑定(动态import函数)

  2. 定义注册过程: 路由注册(可在入口ability中按需注入依赖模块的路由表)。

  3. 跳转过程: 路由表检查(是否注册过对应路由名称) -> 路由前置钩子(路由页面加载-动态Import) -> 路由跳转 -> 路由后置钩子(公共处理,如打点)。

Navigation实现动态路由有如下两种实现方案:

方案一: 自定义路由表

基本实现跟上述Router动态路由类似。

  1. 开发者自定义路由管理模块,各个提供路由页面的模块均依赖此模块;
  2. 构建Navigation组件时,将NavPathStack注入路由管理模块,路由管理模块对NavPathStack进行封装,对外提供路由能力;
  3. 各个路由页面不再提供组件,转为提供@build封装的构建函数,并再通过WrappedBuilder封装后,实现全局封装;
  4. 各个路由页面将模块名称、路由名称、WrappedBuilder封装后构建函数注册如路由模块。
  5. 当路由需要跳转到指定路由时,路由模块完成对指定路由模块的动态导入,并完成路由跳转。

具体的构建过程,可以参考Navigation自动生成动态路由示例。

方案二: 系统路由表

从API version 12版本开始,Navigation支持系统跨模块的路由表方案,整体设计是将路由表方案下沉到系统中管理,即在需要路由的各个业务模块(HSP/HAR)中独立配置router_map.json文件,在触发路由跳转时,应用只需要通过NavPathStack进行路由跳转,此时系统会自动完成路由模块的动态加载、组件构建,并完成路由跳转功能,从而实现了开发层面的模块解耦。

具体可参考Navigation系统路由表

生命周期监听

Router可以通过observer实现注册监听,接口定义请参考Router无感监听observer.on('routerPageUpdate')

收起
自动换行
深色代码主题
复制
  1. import { uiObserver } from '@kit.ArkUI';
  2. function callBackFunc(info: uiObserver.RouterPageInfo) {
  3. console.info("RouterPageInfo is : " + JSON.stringify(info))
  4. }
  5. // used in ability context.
  6. uiObserver.on('routerPageUpdate', this.context, callBackFunc);
  7. // used in UIContext.
  8. uiObserver.on('routerPageUpdate', this.getUIContext(), callBackFunc);

在页面状态发生变化时,注册的回调将会触发,开发者可以通过回调中传入的入参拿到页面的相关信息,如:页面的名字,索引,路径,生命周期状态等。

Navigation同样可以通过在observer中实现注册监听。

收起
自动换行
深色代码主题
复制
  1. // EntryAbility.ets
  2. import { BusinessError } from '@kit.BasicServicesKit';
  3. import { UIObserver } from '@kit.ArkUI';
  4. export default class EntryAbility extends UIAbility {
  5. // ...
  6. onWindowStageCreate(windowStage: window.WindowStage): void {
  7. // ...
  8. windowStage.getMainWindow((err: BusinessError, data) => {
  9. // ...
  10. let windowClass = data;
  11. // 获取UIContext实例。
  12. let uiContext: UIContext = windowClass.getUIContext();
  13. // 获取UIObserver实例。
  14. let uiObserver : UIObserver = uiContext.getUIObserver();
  15. // 注册DevNavigation的状态监听.
  16. uiObserver.on("navDestinationUpdate",(info) => {
  17. // NavDestinationState.ON_SHOWN = 0, NavDestinationState.ON_HIDE = 1
  18. if (info.state == 0) {
  19. // NavDestination组件显示时操作
  20. console.info('page ON_SHOWN:' + info.name.toString());
  21. }
  22. })
  23. })
  24. }
  25. }

页面信息查询

为了实现页面内自定义组件跟页面解耦,自定义组件中提供了全局查询页面信息的接口。

Router可以通过queryRouterPageInfo接口查询当前自定义组件所在的Page页面的信息,其返回值包含如下几个属性,其中pageId是页面的唯一标识符:

展开
名称 类型 必填 说明
context UIAbilityContext/ UIContext routerPage页面对应的上下文信息
index number routerPage在栈中的位置。
name string routerPage页面的名称。
path string routerPage页面的路径。
state RouterPageState routerPage页面的状态
pageId12+ string routerPage页面的唯一标识
收起
自动换行
深色代码主题
复制
  1. import { uiObserver } from '@kit.ArkUI';
  2. // 页面内的自定义组件
  3. @Component
  4. struct MyComponent {
  5. aboutToAppear() {
  6. let info: uiObserver.RouterPageInfo | undefined = this.queryRouterPageInfo();
  7. }
  8. build() {
  9. // ...
  10. }
  11. }

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。
收起
自动换行
深色代码主题
复制
  1. import { uiObserver } from '@kit.ArkUI';
  2. @Component
  3. export struct NavDestinationExample {
  4. build() {
  5. NavDestination() {
  6. MyComponent()
  7. }
  8. }
  9. }
  10. @Component
  11. struct MyComponent {
  12. navDesInfo: uiObserver.NavDestinationInfo | undefined
  13. aboutToAppear() {
  14. this.navDesInfo = this.queryNavDestinationInfo();
  15. console.log('get navDestinationInfo: ' + JSON.stringify(this.navDesInfo))
  16. }
  17. build() {
  18. // ...
  19. }
  20. }

路由拦截

Router原生没有提供路由拦截的能力,开发者需要自行封装路由跳转接口,并在自己封装的接口中做路由拦截的判断并重定向路由。

Navigation提供了setInterception方法,用于设置Navigation页面跳转拦截回调。具体可以参考文档:Navigation路由拦截

在 指南 中进行搜索
请输入您想要搜索的关键词