鸿蒙(HarmonyOS)组件化路由框架——Navigation的路由管理

打印 上一主题 下一主题

主题 839|帖子 839|积分 2517

Navigation介绍

Navigation简介


  • Navigation:路由导航的根视图容器,一样平常作为页面(@Entry)的根容器去使用,包括单页面(stack)、分栏(split)和自适应(auto)三种表现模式。Navigation组件实用于模块内和跨模块的路由切换,通过组件级路由能力实现更加天然流通的转场体验,并提供多种标题栏样式来出现更好的标题和内容联动效果。一次开发,多端部署场景下,Navigation组件能够主动适配窗口表现巨细,在窗口较大的场景下主动切换分栏展示效果。


  • Title:标题栏,通过title属性对标题栏举行设置。通过menus配置菜单
  • NavContent:内容地区,默认首页表现导航内容(Navigation的子组件)或非首页表现(NavDestination的子组件),首页和非首页通过路由举行切换。
  • ToolBar:工具栏,通过toolbarConfiguration实现对工具栏的配置,如果不配置此属性,ToolBar不表现。竖屏最多支持表现5个图标,多余的图标会被放入主动天生的更多图标。
  • NavDestination:作为子页面的根容器,用于表现Navigation的内容区。具备两种范例:STANDARD(标准范例,NavDestination的生命周期跟随NavPathStack栈中标准NavDestination变化而改变),DIALOG(默认透明。不影响其他NavDestination的生命周期)。
  • NavPathStack:Navigation路由栈,由于管理NavDestination组件的路由跳转。推荐使用NavPathStack共同navDestination****属性举行页面路由。
Navigation路由页面生命周期简介

Navigation由NavDestination组件构成页面路由,在实现过程中NavDestination组件会被封装在一个自界说组件中,从而作为一个页面被路由栈使用。当前支持的生命周期函数:aboutToAppear、onReady、onAppear、onShow、onHide、onDisappear、aboutToDisappear 、onWillAppear、onWillDisappear

事件名称形貌aboutToAppear自界说组件析构销毁之前实验。onAppear组件挂载表现时触发此回调。onReady当NavDestination即将构建子组件之前会触发此回调。onShown当该NavDestination页面表现时触发此回调。onHidden当该NavDestination页面隐藏时触发此回调。onDisAppear组件卸载消散时触发此回调。aboutToDisappear在自界说组件析构销毁之前实验。 Navigation VS Router

当前HarmonyOS支持两套路由机制(Navigation和Router),Navigation作为后续长期演进及推荐的路由选择方案,其与Router比较的优势如下:


  • 易用性层面:

  • Navigation天然具备标题、内容、回退按钮的功能联动,开发者可以直接使用此能力。Router若要实现此能力,必要自行界说;
  • Navigation的页面是由组件构成,易于实现共享元素的转场。


  • 功能层面:

  • Navigation天然支持一多,Router不支持;
  • Navigation没有路由数目限制,Router限制32个;
  • Navigation可以获取到路由栈NavPathStack,并对路由栈举行操作;
  • Navigation可以嵌套在模态对话框中,也就是说可以模态框中界说路由,Router不支持;
  • Navigation的组件全量由开发者自行控制,开发者可以自界说复杂的动效和属性的设置(背景、暗昧等),Router的page对象不对外暴露,开发者无法对page举行处置处罚。


  • 性能层面

  • Navigation通报参数性能更优,Navigation通过引用通报,Router通过深拷贝完成;
  • Navigation可以共同动态加载,实现组件动态加载,Router页面使用@Entry举行修饰,当前模块加载时会天生全量页面。
Navigation & Router****结构对比

  • Navigation中的每个页面,承载在一个page里,通过NavDestination容器实现基于组件的页面跳转。
  • Router的每一个页面配置在一个单独的page中,通过@Entry举行标识。

Navigation & Router****能力对比
业务场景Navigation能力Router能力跳转指定页面pushPath & pushDestinationpushUrl & pushNameRouter跳转HSP中页面支持,必要先import页面支持跳转HAR中页面支持,必要先import页面支持跳转传参支持支持获取指定页面参数支持不支持跳转结果回调支持支持跳转单例页面可通过判定栈内有没有此页面,调用moveToTop实现支持页面返回popback页面返回传参支持支持返回指定路由popToName&popToIndex不支持页面返回弹窗通过路由拦截实现showAlertBeforeBackPage路由替换replacePath & replacePathByNamereplaceUrl & replaceNameRouter路由栈清理clearclear清理指定路由removeByIndexes & removeByName不支持转场动画支持支持自界说转场动画支持支持屏蔽转场动画pushDestination(info: NavPathInfo, animated?: boolean) & patshStack.disableAnimation(true)支持 duration属性设置为0共享元素动画支持不支持页面生命周期监听UIObserver.on(‘navDestinationUpdate’)UIObserver.on(‘routerPageUpdate’)获取页面栈对象支持不支持路由拦截setInterception不支持路由栈信息查询getAllPathName & getParamByIndex & getParamByName&sizegetState() & getLength()路由栈操作moveToTop & moveIndexToTop不支持沉浸式页面支持不支持,需通过window配置设置页面属性(背景,暗昧等)支持,backgroundBlurStyle不支持设置页面标题栏(title)和工具栏(toolbar)支持不支持模态嵌套路由支持不支持 Navigation常见场景&办理方案

路由跳转场景
页面跳转是路由最常用的能力,Navigation通过NavPathStack提供了诸多方法,下文以pushDestination方法为例,介绍Navigation的路由跳转相关能力。
页面间跳转
NavPathStack提供了路由管理的能力,通过NavPathStack举行页面跳转,主要实用于页面较多的应用。
Step1:创建NavPathStack对象pageStack,通常使用@Provide举行修饰,方便后续子组件通过@Consumer获取,以实现子页面的路由跳转。也可以将pageStack传入路由框架,以实现路由框架开发(后续路由框架章节会介绍)的开发
  1. @Entry
  2. @Component
  3. struct mainPageView {
  4.   @Provide('pageStack') pageStack: NavPathStack = new NavPathStack()
  5.   ...
  6.   build() {
  7.     ...
  8.   }
  9. }
复制代码
Step2:构建路由表pageMap,该方法通过@Builder举行修饰,通过传入的pageName属性,返回不同页面。
  1. @Entry
  2. @Component
  3. struct mainPageView {
  4.   @Provide('pageStack') pageStack: NavPathStack = new NavPathStack()
  5.   @Builder
  6.   PageMap(pageName: string) {
  7.     if (pageName === 'loginPage') {
  8.       loginPageView()
  9.     } else if (pageName === 'mainPage') {
  10.       mainPageView()
  11.     }
  12.   }
  13.   build() {
  14.     ...
  15.   }
  16. }
复制代码
Step3:在build创建Navigation组件(必要传入pageStack参数),通过navDestination属性传入路由表pageMap,并通过pageStack.pushPath()实现页面跳转。
  1. @Entry
  2. @Component
  3. struct mainPageView {
  4.   @Provide('pageStack') pageStack: NavPathStack = new NavPathStack()
  5.   @Builder
  6.   pageMap(pageName: string) {
  7.     if (pageName === 'loginPage') {
  8.       loginPageView()
  9.     } else if (pageName === 'mainPage') {
  10.       mainPageView()
  11.     }
  12.   }
  13.   build() {
  14.     Navigation(this.pageStack){
  15.       ...
  16.       Button('login').onClick( ent => {
  17.         let pathInfo : NavPathInfo = new NavPathInfo('loginPage', null)
  18.         this.pageStack.pushDestination(pathInfo, true);
  19.       })
  20.     }.navDestination(this.pageMap)
  21.     ...
  22.   }
  23. }
复制代码
页面间参数通报
Navigation的页面间,通过NavPathInfo对象中的params属性,实现从发起页到目的页的数据通报;通过onPop回调参数,实现处置处罚目的页面的返回。
Step1:构建NavPathInfo对象,输入必要通报给目的页面的参数。params参数:将必要通报的数据封装起来举行通报。onPop参数:目的页面触发pop时的返回,在回调中通过PopInfo.info.param获取到返回的对象。
  1. // 发起页 mainPage
  2. let loginParam : LoginParam = new LoginParam()
  3. // 构建pathInfo对象
  4. let pathInfo : NavPathInfo = new NavPathInfo('loginPage', loginParam
  5.   , (popInfo: PopInfo) => {
  6.     let loginParam : LoginParam = popInfo.info.param as LoginParam;
  7.     ...
  8.   })
  9. // 讲参数传递到目标页
  10. this.pageStack.pushDestination(pathInfo, true);   
复制代码
Step2:目的页面在NavDestination的onReady函数中,通过通过cxt.pathInfo.param,获取通报过来的参数。
  1. build() {
  2.   NavDestination(){
  3.     ...
  4.   }.hideTitleBar(true)
  5.   .onReady(cxt => {
  6.     this.loginParam = cxt.pathInfo.param as LoginParam;
  7.     ...
  8.   })
  9. }
复制代码
Step3:目的页通过NavPathStack.pop方法返回起始页,其result参数用来通报必要返回给起始页的对象。
  1. @Component
  2. export struct loginPageView {
  3.   @Consume('pageInfo') pageStack : NavPathStack;
  4.   // 页面构建的对象
  5.   private loginParam! : LoginParam;
  6.   ...
  7.   build() {
  8.     NavDestination(){
  9.       ...
  10.       Button('login').onClick( ent => {
  11.         // 将对象返回给起始页
  12.         this.pageStack.pop(this.loginParam, true)
  13.       })
  14.     }
  15.   }
  16. }
复制代码
跨模块页面跳转
当应用模块较多,必要使用HSP(HAR)举行多模块开发,好比登录模块是一个独立团队开发,以HSP(HAR)的形式交付。此时主页应当从mainPage跳转到HSP(HAR)中的页面,必要先导入模块的自界说组件,将组件添加到pageMap中,再通过pushDestination举行跳转。
Step1:从HSP(HAR)中完成自界说组件(必要跳转的目的页面)开发,将自界说组件分析为export。
  1. @Component
  2. export struct loginPageInHSP {
  3.   @Consume('pageStack') pageStack: NavPathStack;
  4.   ...
  5.   build() {
  6.     NavDestination() {
  7.       ...
  8.     }
  9.   }
  10. }
复制代码
Step2:在HSP(HAR)的index.ets中导出组件。
export { loginPageInHSP } from “./src/main/ets/pages/loginPageInHSP”
Step3:配置好HSP(HAR)的项目依赖后,在mainPage中导入自界说组件,并添加到pageMap中,即可正常调用。
  1. // 导入模块目标页自定义组件
  2. import { loginPageInHSP } from 'library/src/main/ets/pages/loginPageInHSP'
  3. @Entry
  4. @Component
  5. struct mainPage {
  6.   @Provide('pageStack') pageStack: NavPathStack = new NavPathStack()
  7.   @Builder pageMap(name: string) {
  8.     if (name === 'loginPageInHSP') {
  9.       // 路由到hsp包中的登录页面
  10.       loginPageInHSP()
  11.     }
  12.   }
  13.   build() {
  14.     Navigation(this.pageStack) {
  15.       Button("login With HSP module")
  16.         .onClick(() => {
  17.           let loginParam : LoginParamInHSP = new LoginParamInHSP()
  18.           let pathInfo : NavPathInfo = new NavPathInfo('loginPageInHSP', loginParam, (popInfo: PopInfo) => {})
  19.           this.pageStack.pushDestination(pathInfo, true);
  20.         })
  21.     }
  22.     .navDestination(this.pageMap)
  23.   }
  24. }
复制代码
页面转场

默认转场动画
Navigation的pushXXX和pop方法中都带有一个参数animated,将animated设置成false则会取消转场动画,路由到Dialog模式页面或者路由出Dialog模式页面是,均无转场动画,如果必要转场动画,可以通过自界说转场动画实现。
自界说转场动画
Navigation通过customNavContentTransition事件提供自界说转场动画的能力,当转场开始时,通过回调函数告知开发者,告知此次动画from(从哪来)、to(到哪去)、是Push、Pop亦或是Repalce。这里必要注意当为根视图时,NavContentInfo的name值为undefined。

开发者可以在customNavContentTransition的回调函数中举行动画处置处罚,返回NavigationAnimatedTransition自界说转场协议已实现自界说转场。 NavigationAnimatedTransition对象中包罗三个参数,timeout(动画超时结束时间),transition(自界说动画实验回调),onTransitionEnd(转场完成回调),必要在transition方法中实现具体动画逻辑。 由于自界说转场参数是在Navigation层级,但是每个页面都会有其特定的自界说转场效果,因此必要界说一套转场动画框架,已实现在Navigation层面对框架举行统一管理,各个页面通过实现框架提供的回调函数,将其特定的动画效果通报给Navigation。
Step1:构建动画框架,通过一个Map管理各个页面自界说自界说动画对象CustomTransition,CustomTransition对象提供了Push、Pop、Replace各个动画阶段的回调函数给各个页面举行补充,此处将各个阶段细分为In和Out,从而实现页面进入和退出时不同的转场效果。 自界说动画的构建必要结合in、out两个页面同时举行,因此案例针对不同路由方式均提供了in、out两个方法。
  1. // 自定义动画对象,定义了Push、Pop、Replace各个动画阶段的回调函数
  2. export class CustomTransition {
  3.   pageID : number = -1;
  4.   onPushInStart: () => void = () => {};
  5.   onPushInEnd: () => void = () => {};
  6.   onPushInFinish: () => void = () => {};
  7.   onPopInStart: () => void = () => {};
  8.   onPopInEnd: () => void = () => {};
  9.   onPopInFinish: () => void = () => {};
  10.   onReplaceInStart: () => void = () => {};
  11.   onReplaceInEnd: () => void = () => {};
  12.   onReplaceInFinish: () => void = () => {};
  13.   onPushOutStart: () => void = () => {};
  14.   onPushOutEnd: () => void = () => {};
  15.   onPushOutFinish: () => void = () => {};
  16.   onPopOutStart: () => void = () => {};
  17.   onPopOutEnd: () => void = () => {};
  18.   onPopOutFinish: () => void = () => {};
  19.   onReplaceOutStart: () => void = () => {};
  20.   onReplaceOutEnd: () => void = () => {};
  21.   onReplaceOutFinish: () => void = () => {};
  22.   ...
  23.   // 获取启动阶段参数回调
  24.   public getStart(operation : NavigationOperation, isInPage : boolean) : () => void {
  25.     if (operation == NavigationOperation.PUSH) {
  26.       if (isInPage) {
  27.         return this.onPushInStart;
  28.       } else {
  29.         return this.onPushOutStart;
  30.       }
  31.     } else if (operation == NavigationOperation.POP) {
  32.       if (isInPage) {
  33.         return this.onPopInStart;
  34.       } else {
  35.         return this.onPopOutStart;
  36.       }
  37.     } else {
  38.       if (isInPage) {
  39.         return this.onReplaceInStart;
  40.       } else {
  41.         return this.onReplaceOutStart;
  42.       }
  43.     }
  44.   }
  45.   // 获取动画结束阶段参数回调
  46.   public getEnd(operation : NavigationOperation, isInPage : boolean) : () => void {
  47.     ...
  48.   }
  49.   // 获取动画结束后参数回调
  50.   public getFinished(operation : NavigationOperation, isInPage : boolean) : () => void {
  51.     ...
  52.   }
  53. }
  54. // 自定义动画对象框架
  55. export class CustomTransitionFW {
  56.   // 各个页面自定义动画对象映射表
  57.   private customTransitionMap: Map<number, CustomTransition> = new Map<number, CustomTransition>()
  58.   ...
  59.   registerNavParam(ct : CustomTransition): void {
  60.     ...
  61.     this.customTransitionMap.set(ct.pageID, ct);
  62.   }
  63.   unRegisterNavParam(pageId: number): void {
  64.     ...
  65.     this.customTransitionMap.delete(pageId);
  66.   }
  67.   getAnimateParam(pageId: number): CustomTransition {
  68.     ...
  69.     return this.customTransitionMap.get(pageId) as CustomTransition;
  70.   }
  71. }
复制代码
Step2:配置Navigation的customNavContentTransition属性,当返回undefined时,使用体系默认动画。
  1. build() {
  2.   Navigation(this.pageStack){
  3.     ...
  4.   }.hideTitleBar(true)
  5.   .hideToolBar(true)
  6.   .navDestination(this.pageMap)
  7.   .customNavContentTransition((from: NavContentInfo, to: NavContentInfo, operation: NavigationOperation) => {
  8.     // 对于Dialog型的页面,此处统一做了自定义动画的屏蔽,若需要动画,可以不做此判断。
  9.     if (from.mode == NavDestinationMode.DIALOG || to.mode == NavDestinationMode.DIALOG) {
  10.       console.error(`==== no transition because Dialog`);
  11.       return undefined;
  12.     }
  13.     let pageIn : CustomTransition | undefined;
  14.     let pageOut : CustomTransition | undefined;
  15.     pageIn = CustomTransitionFW.getInstance().getAnimateParam(to.index)
  16.     pageOut = CustomTransitionFW.getInstance().getAnimateParam(from.index)
  17.     // 业务首页跳转时若没有自定义动画诉求,此处可以通过判断页面id是否为-1(-1表示Navigation根视图)进行跳出。
  18.     if (from.index === -1 || to.index === -1) {
  19.       return undefined;
  20.     }
  21.     // 创建自定义转场协议,各个页面都会根据协议中的配置进行转场,当返回undefined时,使用系统默认动画。
  22.     let customAnimation: NavigationAnimatedTransition = {
  23.       onTransitionEnd: (isSuccess: boolean)=>{
  24.         ...
  25.       },
  26.       transition: (transitionProxy: NavigationTransitionProxy)=>{
  27.         ...
  28.       },
  29.       timeout: 100,
  30.     };
  31.     return customAnimation;
  32.   })
  33. }
复制代码
Step3:customNavContentTransition事件必要返回NavigationAnimatedTransition对象,具体的动画实现必要在NavigationAnimatedTransition的transition属性中实现。transition中通过各个页面在框架中注册的回调函数,配置框架必要的动画属性。案例中各个页面注册了PUSH\POP\REPLACE的各个阶段动画参数。此处必要注意由于Navigation根页面不在栈中,因此无法与NavDestination无法产生跳转联动,因此如果第一个入栈的页面也必要自界说动画,那么就必要判定pageId是否为-1(-1及表现为根视图),如果是-1则不就行动画设置。
  1. let customAnimation: NavigationAnimatedTransition = {
  2.   ...
  3.   transition: (transitionProxy: NavigationTransitionProxy)=>{
  4.   // 配置起始参数
  5.   if (pageOut != undefined && pageOut.pageID != -1) {
  6.   pageOut.getStart(operation, false)();
  7. }
  8. if (pageIn != undefined && pageIn.pageID != -1) {
  9.   pageIn.getStart(operation, true)();
  10. }
  11. // 执行动画
  12. animateTo({
  13.   duration: 1000,
  14.   curve: Curve.EaseInOut,
  15.   onFinish: ()=>{
  16.     if (pageOut != undefined && pageOut.pageID != -1) {
  17.       pageOut.getFinished(operation, false)();
  18.     }
  19.     if (pageIn != undefined && pageIn.pageID != -1) {
  20.       pageIn.getFinished(operation, true)();
  21.     }
  22.     transitionProxy.finishTransition();
  23.   }}, ()=>{
  24.   if (pageOut != undefined && pageOut.pageID != -1) {
  25.     pageOut.getEnd(operation, false)();
  26.   }
  27.   if (pageIn != undefined && pageIn.pageID != -1) {
  28.     pageIn.getEnd(operation, true)();
  29.   }
  30. })
  31. }
  32. }
复制代码
Step4:在各个页面中界说动画回调,并往自界说动画框架中注册。并在组件onDisAppear生命周期中注销框架中的页面动画回调。
Step5:界说NavDestination的translate属性,已实现动画效果。
  1. @Component
  2. export struct loginPageView {
  3.   ...
  4.   private pageId: number = 0;
  5.   @State transX: number = 0;
  6.   @State transY: number = 0;
  7.   aboutToAppear(): void {
  8.     this.pageId = this.pageStack.getAllPathName().length - 1;
  9.     let ct : CustomTransition = new CustomTransition();
  10.     ct.pageID = this.pageId;
  11.     ct.onPushInStart = ct.onPushOutEnd = ct.onPopInStart = ct.onPopOutEnd = ct.onReplaceInStart = ct.onReplaceOutEnd = () => {
  12.       this.transX = -300;
  13.     }
  14.     ct.onPushInEnd = ct.onPushOutStart = ct.onPopInEnd = ct.onPopOutStart = ct.onReplaceInEnd = ct.onReplaceOutStart = () => {
  15.       this.transX = 0;
  16.     }
  17.     ct.onPushInFinish = ct.onPopInFinish  = ct.onReplaceInFinish = () => {
  18.       this.transX = 0;
  19.     }
  20.     ct.onPushOutFinish = ct.onPopOutFinish  = ct.onReplaceOutFinish = () => {
  21.       this.transX = -300;
  22.     }
  23.     // 将页面的动画效果注册到动画框架中
  24.     CustomTransitionFW.getInstance().registerNavParam(ct)
  25.   }
  26.   build() {
  27.     NavDestination(){
  28.       ...
  29.     }.hideTitleBar(true)
  30.     .onDisAppear(()=>{
  31.       // 组件销毁的时候,需要将页面的动画效果从框架中删除
  32.       CustomTransitionFW.getInstance().unRegisterNavParam(this.pageId)
  33.     })
  34.     // 定义translate,已实现动画
  35.     .translate({x: this.transX, y: this.transY, z: 0})
  36.   }
  37. }
复制代码
共享元素转场
NavDestination之间可以通过geometryTransition实现共享元素转场。
Step1:起始页为必要实现共享元素转场的元素添加geometryTransition属性,id参数必须在两个NavDestination之间保持一致。
起始页代码
  1. Column() {
  2.   Image($r('app.media.startIcon'))
  3.     .geometryTransition('1')
  4.   Text("起始页共享的图片")
  5. }
  6. .width(100)
  7. .height(100)
复制代码
目的页代码
  1. Column() {
  2.   Image($r('app.media.startIcon'))
  3.     .geometryTransition('1')
  4.   Text("目的页共享的图片")
  5. }
  6. .width(200)
  7. .height(200)
复制代码
Step2:animateTo方法发起页面跳转(push 或者 pop),触发共享元素转场动画实验。注意此处必要关闭页面默认的跳转动画
  1. Button('跳转目的页')
  2.   .width('80%')
  3.   .height(40)
  4.   .margin(20)
  5.   .onClick(() => {
  6.     animateTo({ duration: 1000 }, () => {
  7.       this.pageStack.pushPath({ name: 'DestinationPage' }, false)
  8.     })
  9.   })
复制代码
其他常见业务功能场景
基于Dialog范例NavDestination,实现弹窗页面跳转返回后弹窗不关闭

NavDestination有两个范例,通过mode属性举行配置,前文介绍的NavDestination均是STANDARD范例。
名称形貌STANDARD标准范例NavDestination的生命周期跟随NavPathStack栈中标准Destination变化而改变。DIALOG默认透明。不影响其他NavDestination的生命周期。 DIALOG范例的NavDestination背景透明,且不会影响其他NavDestination生命周期,也就是说前面的页面不会隐藏,因此比较适合开发类似“高德地图”的应用,此类应用特点是:底层一个固定的页面,其余页面都是覆盖在底层页面之上,但是底层页面始终可见。mode为DIALOG的NavDestination在转入和转出时,默认不支持动画,可以通过自界说动画的方式配置动画。在全局弹窗需求交付之前,可以通过Dialog来实现弹窗,Dialog实现的弹窗可以实现解耦,还可以实现弹窗跳转页面返回,弹窗不关闭等效果(好比隐私弹窗,该弹窗还可以接续跳转到隐私条款页)。构建Dialog范例的NavDestination,由于Dialog范例页面背景是透明的,为了更好的效果,可以增加一层蒙层。如果要对弹窗组件增加类似移动出现效果,必要在组件中自行实现。
  1. @Component
  2. export struct PrivacyDialog {
  3.   @Consume('pageInfo') pageStack : NavPathStack;
  4.   @State isAgree: string = "Not Agree";
  5.   build() {
  6.     NavDestination(){
  7.       Stack({ alignContent: Alignment.Center }){
  8.         // 蒙层
  9.         Column() {
  10.         }
  11.         .width("100%")
  12.         .height("100%")
  13.         .backgroundColor('rgba(0,0,0,0.5)')
  14.         // 隐私弹窗
  15.         Column() {
  16.           Text("注册应用账号").fontSize(30).height('20%')
  17.           Text("请您仔细阅读一下协议并同意,我们将全力保护您的个人信息安全,您可以使用账号登录APP。").height('40%')
  18.           Divider()
  19.           Row(){
  20.             // 点击隐私条款,跳转到隐私条款页面,并接受隐私条款的返回值,用来刷新页面的同意状态。
  21.             Text("《应用隐私政策》").onClick(ent => {
  22.               let pathInfo : NavPathInfo = new NavPathInfo('PrivacyItem', null
  23.                 , (popInfo: PopInfo) => {
  24.                   this.isAgree = popInfo.result.toString();
  25.                 })
  26.               this.pageStack.pushDestination(pathInfo, true)
  27.             })
  28.             Text(this.isAgree)
  29.           }.height('20%')
  30.           Divider()
  31.           // 点击同意\不同意按钮,将状态返回登录页
  32.           Row(){
  33.             Button("不同意").onClick(ent => {
  34.               this.pageStack.pop("Not Agree", true)
  35.             }).width('30%')
  36.             Button("同意").onClick(ent => {
  37.               this.pageStack.pop("Agree", true)
  38.             }).width('30%')
  39.           }.height('20%')
  40.         }.backgroundColor(Color.White)
  41.         .height('50%')
  42.         .width('80%')
  43.       }
  44.     }.hideTitleBar(true)
  45.     // 设置Dialog类型
  46.     .mode(NavDestinationMode.DIALOG)
  47.   }
  48. }
复制代码
基于页面生命周期监听,实现页面埋点
在应用运维时,必要相识哪些业务功能用户使用频率较高,为了实现此能力,可以在页面表现\隐藏的的时候举行相应的埋点操作,以方便背景举行统计分析。此类诉求可以通过DevNavigation的状态监听实现。可以在Ability中的onWindowStageCreate方法中通过uiObserver.on(“navDestinationUpdate”,(info) => {})方法注册DevNavigation的状态监听,样例代码通过打样日记的方式记录各个DevNavigation表现\隐藏时的状态,在真实业务中可以相应举行替换。
  1. export default class EntryAbility extends UIAbility {
  2.   ...
  3.   onWindowStageCreate(windowStage: window.WindowStage): void {
  4.     ...
  5.     windowStage.getMainWindow((err: BusinessError, data) => {
  6.       ...
  7.       windowClass = data;
  8.       // 获取UIContext实例。
  9.       let uiContext: UIContext = windowClass.getUIContext();
  10.       // 获取UIObserver实例。
  11.       let uiObserver : UIObserver = uiContext.getUIObserver();
  12.       // 注册DevNavigation的状态监听.
  13.       uiObserver.on("navDestinationUpdate",(info) => {
  14.         // NavDestinationState.ON_SHOWN = 0, NavDestinationState.ON_HIDE = 1
  15.         if (info.state == 0) {
  16.           // NavDestination组件显示时操作
  17.           console.info('page ON_SHOWN:' + info.name.toString());
  18.         } else if (info.state == 1){
  19.           // NavDestination组件隐藏时操作
  20.           console.info('page ON_HIDE' + info.name.toString());
  21.         } else {
  22.           // NavDestination组件其他操作
  23.           console.info('page state:' + info.state);
  24.         }
  25.       })
  26.     })
  27.   }
  28. }
复制代码
基于路由拦截实现页面返回弹窗确认
业务场景:要针对页面举行统一的逻辑处置处罚,好比在页面返回时根据页面参数配置规则匹配是否必要给出弹窗提示,若规则匹配到就弹框,否则默认走返回流程。

为了实现此功能,必要依赖路由拦截能力,通过路由跳转拦截,实现控制路由跳转,弹窗或制止路由跳转等操作。 为此NavPathStack提供了setInterception方法,用于设置Navigation页面跳转拦截回调。该方法必要一个NavigationInterception对象,该对象包罗三个回调函数:
名称形貌willShow页面跳转前拦截,答应操作栈,在当前跳转中生效。didShow页面跳转后回调。在该回调中操作栈在下一次跳转中革新。modeChangeNavigation单双栏表现状态发生变更时触发该回调。 简单理解就是willShow会在from页面动效完成之前回调;didiShow会在from页面动效完成之后回调。此处必要注意的是无论哪个回调,在进入回调时页面栈都已经发生了变化。案例中,由于在弹出确认框时,不能出现登录页面返回的转场动画,因此必要在willShow回调中实验相关业务。

  • 代码判定srcPage为登录页面loginPageView,targetPage为主页面MainPage是实验弹窗逻辑。
  • 由于loginPageView此时已经出栈,必要重新将loginPageView重新入栈。此处必要注意必要将参数重新通报给loginPageView,否则无法顺遂完成页面初始化。如果必要保持页面之前的输入值,则必要为组件独立注册LocalStorage以保持状态。
  • 弹窗ConfirmDialog通过Dialog范例的NavDestination实现,启动ComfirmDialog时,将点击OK和Cancel的回调一并传入,从而可以进步ComfirmDialog的独立封装能力。
  • 当点击‘OK’确认返回时,必要将ConfirmDialog和loginPageView同时出栈。此时必要注意这一帧的起点栈为Dialog范例,因此不会有转场动画,如果必要转场动画,必要通过自界说动画实现。
  1. export function registerInterception() {
  2.   RouterManager.setInterception({
  3.     willShow: (from: NavDestinationContext | "navBar", to: NavDestinationContext | "navBar",
  4.       operation: NavigationOperation, animated: boolean) => {
  5.       if (typeof to === "string") {
  6.         console.log("target page is navigation home");
  7.         return;
  8.       }
  9.       if (typeof from === "string") {
  10.         console.log("target page is navigation home");
  11.         return;
  12.       }
  13.       // redirect target page.Change pageTwo to pageOne.
  14.       let target: NavDestinationContext = to as NavDestinationContext;
  15.       let srcPage: NavDestinationContext = from as NavDestinationContext;
  16.       console.log("==== setInterception target.pathInfo.name = " + target.pathInfo.name)
  17.       if (target.pathInfo.name === 'MainPage'&& srcPage.pathInfo.name === 'loginPage') {
  18.         RouterManager.pushPath('loginPage', srcPage.pathInfo.param, srcPage.pathInfo.onPop, true)
  19.         let cdd = new ConfirmDialogData();
  20.         cdd.onCancelClick = () => {
  21.           RouterManager.popWithoutParam(false)
  22.         }
  23.         cdd.onConfirmClick = () => {
  24.           // RouterManager.removeByName(RouterInfo.LOGIN_PAGE)
  25.           RouterManager.popWithoutParam(true)
  26.           RouterManager.popWithoutParam(true)
  27.         }
  28.         RouterManager.pushPath("confirmDialog", cdd, () => {}, false)
  29.       }
  30.     },
  31.     didShow: (from: NavDestinationContext | "navBar", to: NavDestinationContext | "navBar",
  32.       operation: NavigationOperation, isAnimated: boolean) => {
  33.     },
  34.     modeChange: (mode: NavigationMode) => {
  35.     }
  36.   })
  37. }
复制代码
补充一些有用的知识
从案例中可以看出,loginPage履历了出栈再入栈两个动作,纵然再次入栈时,将初始的入参重新通报了回去,但是页面表单之前填入的信息任然无法规复,也就是如果之前表单填入了’abc’,再次入栈时表现的时初始值,之前填入的’abc’无法规复。为相识决这个题目,可以接纳组件级别的localStorage来办理。
  1. let localStorageLoginPage: LocalStorage = new LocalStorage();
  2. localStorageLoginPage.setOrCreate('userName', '');
  3. @Component
  4. export struct loginPageView {
  5.   @LocalStorageLink('userName') userName : string = ''
  6.   @State initUserName : string = ''
  7.   build() {
  8.     NavDestination(){
  9.       TextInput({placeholder: 'User Name', text: this.initUserName})
  10.         .onChange((value: string) => {
  11.           this.userName = value;
  12.         }).width('80%')
  13.       ...
  14.     }
  15.   }.hideTitleBar(true)
  16.   .onReady((ctx)=>{
  17.     this.loginParam = ctx.pathInfo.param as LoginParamInHAR;
  18.     // 判断页面初始输入框初始阶段显示的指是来自传参还是localStorage
  19.     if (this.userName == '') {
  20.       this.initUserName = this.loginParam.userName == 'not login' ? '' : this.loginParam.userName
  21.     } else {
  22.        this.initUserName = this.userName;
  23.     }
  24.   })
  25. }
  26. @Builder
  27. export function getLoginPage() : void {
  28.   loginPageView({}, localStorageLoginPage);
  29. }
复制代码
路由框架
大型应用为了实现更好的模块间解耦,往往会计划一套路由框架,用于解耦各个模块间的路由关系(A模块不感知路由具体通过哪个模块实现、如何实现,只通过路由名称实现路由跳转,闭环业务功能)
体系跨模块路由框架
体系支持跨模块的路由表方案,整体计划是基于模块路由表的思想。即在必要路由的各个业务模块(HSP/HAR)中独立配置router_map.json文件,在触发路由跳转时,应用只必要通过NavPactStack举行路由跳转。此时体系会完成路由模块的动态加载、组件构建,并完成路由跳转功能,从而实现了开发层面的模块接口。
Step1:在必要配置路由表的模块的module.json5中添加路由表配置
  1. {
  2.   "module" : {
  3.   ...
  4.   "routerMap": "$profile:router_map"
  5. }
  6. }
复制代码
Step2:在src\main\resources\base\profile(若之前没有profile目次,则必要新建)目次下新建router_map.json文件,此处文件名称必要与module.json5中配置的文件名称一致。
配置项分析name跳转页面名称,用于路由跳转时使用,如this.pageStack.pushPathByName(“MainPage”, null, false);pageSourceFile跳转目的页在包内的路径,相对src目次的相对路径。buildFunction跳转目的页的入口函数名称,必须以@Builder修饰。data应用自界说字段。可以通过配置项读取接口getConfigInRouteMap获取。
  1. {
  2.   "routerMap": [
  3.     {
  4.       "name": "MainPage",
  5.       "pageSourceFile": "src/main/ets/components/mainpage/MainPage.ets",
  6.       "buildFunction": "getMainPageRouterMap",
  7.       "data": {
  8.         "description" : "this is mainPage"
  9.       }
  10.     },
  11.     {
  12.       "name": "PersonDetail",
  13.       "pageSourceFile": "src/main/ets/components/mainpage/PersonDetail.ets",
  14.       "buildFunction": "getPersonDetailRouterMap",
  15.       "data": {
  16.         "description" : "this is PersonDetailPage"
  17.       }
  18.     }
  19.   ]
  20. }
复制代码
Step3:Navigation主页开发:使用体系路由表方案可以不配置.navDestination()属性,如若配置.navDestination()属性,则会先查询.navDestination()中配置的路由,再查询router_map中的路由。
  1. @Entry
  2. @Component
  3. struct IndexRouterMap {
  4.   @Provide('pageInfo') pageStack : NavPathStack = new NavPathStack();
  5.   build() {
  6.     Navigation(this.pageStack){
  7.     }.onAppear(() => {
  8.       this.pageStack.pushPathByName("MainPage", null, false);
  9.     })
  10.     .hideNavBar(true)
  11.   }
  12. }
复制代码
Step3:Navigation主页开发:使用体系路由表方案可以不配置.navDestination()属性,如若配置.navDestination()属性,则会先查询.navDestination()中配置的路由,再查询router_map中的路由。
  1. @Entry
  2. @Component
  3. struct IndexRouterMap {
  4.   @Provide('pageInfo') pageStack : NavPathStack = new NavPathStack();
  5.   build() {
  6.     Navigation(this.pageStack){
  7.     }.onAppear(() => {
  8.       this.pageStack.pushPathByName("MainPage", null, false);
  9.     })
  10.     .hideNavBar(true)
  11.   }
  12. }
复制代码
更近一步,我们可以轻微做一些封装,将pageStack封装到路由管理对象中,这样就可以克制通过Provide的方式来通报pageStack
  1. @Entry
  2. @Component
  3. struct IndexRouterMap {
  4.   private pageStack : NavPathStack = new NavPathStack();
  5.   aboutToAppear(): void {
  6.     RouterManager.createNavPathStack(this.pageStack)
  7.     ...
  8.   }
  9.   build() {
  10.     Navigation(this.pageStack){
  11.     }.onAppear(() => {
  12.       RouterManager.pushPath("MainPage", null, ()=>{}, false);
  13.     })
  14.     .hideNavBar(true)
  15.   }
  16. }
复制代码
Step4:NavDestination页面开发:与不使用路由框架对比,接纳路由框架后,必要注意一下两点:

  • 页面必要增加@Builder函数,函数名称与router_map.json中的buildFunction属性必须一致。
  • 在router_map.json中配置的data信息,通过onReady回调中实验ctx.getConfigInRouteMap()获取。
  1. @Component
  2. export struct MainPage {
  3.   ...
  4.   build() {
  5.     NavDestination() {
  6.       ...
  7.     }.hideTitleBar(true)
  8.     .onReady(ctx => {
  9.       let config : RouteMapConfig | undefined = ctx.getConfigInRouteMap()
  10.       if (config) {
  11.         let value : string = config.data['description'];
  12.         ...
  13.       }
  14.     })
  15.   }
  16. }
  17. @Builder
  18. export function getMainPageRouterMap() : void {
  19.   MainPage();
  20. }
复制代码
特别注意
由于路由表框架涉及SDK和编译构建多个阶段,在工程整体迁移到新SDK版本后,必要手动更新oh-package.json5的devDependencies项下下依赖信息,具体版本号建议通过新建工程的方法确认。
  1. {
  2.   ...
  3.   "devDependencies": {
  4.   "@ohos/hypium": "1.0.18-rc2",
  5.   "@ohos/hamock": "1.0.1-rc2"
  6. }
  7. }
复制代码
自界说跨模块路由框架
应用可以自界说实现路由框架,整体方案如下:

  • 界说个路由管理模块,各个提供路由页面的模块均依赖此模块
  • 构建Navgation组件时,将NavPactStack注入路由管理模块,路由管理模块对NavPactStack举行封装,对外提供路由能力。
  • 各个路由页面不再提供组件,转为提供@build封装的构建函数,并再通过WrappedBuilder封装后,实现全局封装。
  • 各个路由页面将模块名称、路由名称、WrappedBuilder封装后构建函数注册如路由模块。
  • 当路由必要跳转到指定路由时,路由模块完成对指定路由模块的动态导入,并完成路由跳转。
具体的构建过程,可以参考 应用导航计划 ,也可以参考开源工程 routermodule 。
Router切换Navigation
对于原先使用Router的应用,建议尽快切换Navigation,克制由于页面的一连增加导致迁移工作量变大。当前美团、高德地图是基于Navigation举行应用开发,微博已经完成Router到Navigation切换。
Navigation底子容器构建
在应用路由根节点新增Navigation节点,界说好pageStack,pageMap及Navigation,将页面原先的内容封装入Navigation容器。若不使用路由框架,则此时必要将全量路由在pageMap中分析,也可以在切换时新增使用【路由框架】)具体步调可以参考【页面间跳转】

页面切换为NavDestination组件

业务模块页面新增NavDestination组件,删除@Entry装饰器,并export导出。比较快速的查找方式就是工程内搜索@Entry关键字,这样就可以快速找到页面。如果切换时新增使用路由框架,此时必要同时注册路由,参考【路由框架】)

转场动画切换
ruter通过pageTransition设置页面间转场动画,Navigation中的页面是组件级的,都从属于一个页面,因此必要通过Navigation的customNavContentTransition事件实现自界说转场。
Router相关路由能力替换

Router路由配置信息从main_pages.json中移除
模块中的main_pages.json文件删除转换为NavDestination的页面路由信息。此处初始页面必要保存,筹划任然使用router的页面也必要保存(并不推荐这样做)。

生命周期切换
router路由页面切换为NavDestination路由后,之前每个页面的onPageShow\onPageHide生命周期函数不在生效,必要替换为NavDestination路由相关生命周期onShow\onHide。
路由框架切换
使用部分应用如果之前基于router构建了路由框架,则必要对路由框架就行Navigation化处置处罚。具体过程可以参考【体系跨模块路由框架】
Step1:完成各个页面的Navigation/NavDestination改造。
Step2:页面切换为@build构建函数。
Step3:完成体系路由配置,并将NavPathSatck传入路由模块。
Step4:路由框架router相关方法通过NavPathSatck相关方法举行替换。
Step5:给涉及路由的模块配置路由表router_map.json
NavigationAPI 介绍
参考gitee官方指南 Navigation 、 NavDestination 、 NavPathStack
Navigation 常见题目
Q:Router和Navigation是否可以混用?A:可以混用,但是推荐使用Navigation举行开发。
Q:Navigation页面层级是否有限制?A:无限制。
Q:Navigation通报参数时,参数对象内里是否可以有方法?A:参数对象可以包罗方法,目的页面可以调用对象方法。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

盛世宏图

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表