鸿蒙Navigation、三方库hmrouter使用

一、Navigation实现导航

概述

组件导航(Navigation)主要用于实现Navigation页面(NavDestination)间的跳转,支持在不同Navigation页面间传递参数,提供灵活的跳转栈操作,从而更便捷地实现对不同页面的访问和复用。本文将从组件导航(Navigation)的显示模式、路由操作、子页面管理、跨包跳转以及跳转动效等几个方面进行详细介绍。

Navigation是路由导航的根视图容器,一般作为页面(@Entry)的根容器,包括单栏(Stack)、分栏(Split)和自适应(Auto)三种显示模式。Navigation组件适用于模块内和跨模块的路由切换,通过组件级路由能力实现更加自然流畅的转场体验,并提供多种标题栏样式来呈现更好的标题和内容联动效果。一次开发,多端部署场景下,Navigation组件能够自动适配窗口显示大小,在窗口较大的场景下自动切换分栏展示效果。

Navigation组件主要包含导航页和子页。导航页由标题栏(包含菜单栏)、内容区和工具栏组成,可以通过hideNavBar属性进行隐藏,导航页不存在路由栈中,与子页,以及子页之间可以通过路由操作进行切换。

在API version 9上,Navigation需要配合NavRouter组件实现页面路由。从API version 10开始,更推荐使用NavPathStack实现页面路由。

设置页面显示模式

Navigation组件通过mode属性设置页面的显示模式。

自适应模式

Navigation组件默认为自适应模式,此时mode属性为NavigationMode.Auto。自适应模式下,当页面宽度大于等于一定阈值( API version 9及以前:520vp,API version 10及以后:600vp )时,Navigation组件采用分栏模式,反之采用单栏模式。


Navigation() { // ... } .mode(NavigationMode.Auto)

单页面模式

单页面模式适用于窄屏设备,发生路由跳转时,整个页面都会被替换。

将mode属性设置为NavigationMode.Stack,Navigation组件即可设置为单页面显示模式。


Navigation() { // ... } .mode(NavigationMode.Stack)

分栏模式

分栏模式适用于宽屏设备,分为左右两部分,发生路由跳转时,只有右边子页会被替换。

将mode属性设置为NavigationMode.Split,Navigation组件即可设置为分栏显示模式。

设置标题栏模式

标题栏在界面顶部,用于呈现界面名称和操作入口,Navigation组件通过titleMode属性设置标题栏模式。

说明

Navigation或NavDestination未设置主副标题并且没有返回键时,不显示标题栏。

Mini模式

普通型标题栏,用于一级页面不需要突出标题的场景。


Navigation() { // ... } .titleMode(NavigationTitleMode.Mini)

Full模式

强调型标题栏,用于一级页面需要突出标题的场景。


Navigation() { // ... } .titleMode(NavigationTitleMode.Full)

设置菜单栏

菜单栏位于Navigation组件的右上角,开发者可以通过menus属性进行设置。menus支持Array<NavigationMenuItem>和CustomBuilder两种参数类型。使用Array<NavigationMenuItem>类型时,竖屏最多支持显示3个图标,横屏最多支持显示5个图标,多余的图标会被放入自动生成的更多图标。



let TooTmp: NavigationMenuItem = {'value': "", 'icon': "./image/ic_public_highlights.svg", 'action': ()=> {}}
Navigation() {
  // ...
}
.menus([TooTmp,
  TooTmp,
  TooTmp])

图片也可以引用resources中的资源。



let TooTmp: NavigationMenuItem = {'value': "", 'icon': "resources/base/media/ic_public_highlights.svg", 'action': ()=> {}}
Navigation() {
  // ...
}
.menus([TooTmp,
  TooTmp,
  TooTmp])

设置了4个图标的菜单栏



let TooTmp: NavigationMenuItem = {'value': "", 'icon': "./image/ic_public_highlights.svg", 'action': ()=> {}}
Navigation() {
  // ...
}
// 竖屏最多支持显示3个图标,多余的图标会被放入自动生成的更多图标。
.menus([TooTmp,
  TooTmp,
  TooTmp,
  TooTmp])

设置工具栏

工具栏位于Navigation组件的底部,开发者可以通过toolbarConfiguration属性进行设置。

图7 工具栏



let TooTmp: ToolbarItem = {'value': "func", 'icon': "./image/ic_public_highlights.svg", 'action': ()=> {}};
let TooBar: ToolbarItem[] = [TooTmp,TooTmp,TooTmp];
Navigation() {
  // ...
}
.toolbarConfiguration(TooBar)

路由操作

Navigation路由相关的操作都是基于导航控制器NavPathStack提供的方法进行,每个Navigation都需要创建并传入一个NavPathStack对象,用于管理页面。主要涉及页面跳转、页面返回、页面替换、页面删除、参数获取、路由拦截等功能。

从API version 12开始,导航控制器允许被继承。开发者可以在派生类中自定义属性和方法,也可以重写父类的方法。派生类对象可以替代基类NavPathStack对象使用。Navigation中的NavDestination页面存在于NavPathStack中,以栈的结构管理,我们称为路由栈。具体示例代码参见:导航控制器继承示例代码。

说明

1.不建议开发者通过监听生命周期的方式管理自己的路由栈。

2.在应用处于后台状态下,调用NavPathStack的栈操作方法,会在应用再次回到前台状态时触发刷新。



@Entry
@Component
struct Index {
  // 创建一个导航控制器对象并传入Navigation
  pageStack: NavPathStack = new NavPathStack();
 
  build() {
    Navigation(this.pageStack) {
    }
    .title('Main')
  }
}

页面跳转

NavPathStack通过Push相关的接口去实现页面跳转的功能,主要分为以下三类:

普通跳转,通过页面的name去跳转,并可以携带param。



this.pageStack.pushPath({ name: "PageOne", param: "PageOne Param" });
this.pageStack.pushPathByName("PageOne", "PageOne Param");

带返回回调的跳转,跳转时添加onPop回调,能在页面出栈时获取返回信息,并进行处理。



this.pageStack.pushPathByName('PageOne', "PageOne Param", (popInfo) => {
  console.log('Pop page name is: ' + popInfo.info.name + ', result: ' + JSON.stringify(popInfo.result));
});

带错误码的跳转,跳转结束会触发异步回调,返回错误码信息。



this.pageStack.pushDestination({name: "PageOne", param: "PageOne Param"})
  .catch((error: BusinessError) => {
    console.error(`Push destination failed, error code = ${error.code}, error.message = ${error.message}.`);
  }).then(() => {
    console.info('Push destination succeed.');
  });
this.pageStack.pushDestinationByName("PageOne", "PageOne Param")
  .catch((error: BusinessError) => {
    console.error(`Push destination failed, error code = ${error.code}, error.message = ${error.message}.`);
  }).then(() => {
    console.info('Push destination succeed.');
  });

页面返回

NavPathStack通过Pop相关接口去实现页面返回功能。



// 返回到上一页
this.pageStack.pop();
// 返回到上一个PageOne页面
this.pageStack.popToName("PageOne");
// 返回到索引为1的页面
this.pageStack.popToIndex(1);
// 返回到根首页(清除栈中所有页面)
this.pageStack.clear();

页面替换

NavPathStack通过Replace相关接口去实现页面替换功能。



// 将栈顶页面替换为PageOne
this.pageStack.replacePath({ name: "PageOne", param: "PageOne Param" });
this.pageStack.replacePathByName("PageOne", "PageOne Param");
// 带错误码的替换,跳转结束会触发异步回调,返回错误码信息
this.pageStack.replaceDestination({name: "PageOne", param: "PageOne Param"})
  .catch((error: BusinessError) => {
    console.error(`Replace destination failed, error code = ${error.code}, error.message = ${error.message}.`);
  }).then(() => {
    console.info('Replace destination succeed.');
  })

页面删除

NavPathStack通过Remove相关接口去实现删除路由栈中特定页面的功能。



// 删除栈中name为PageOne的所有页面
this.pageStack.removeByName("PageOne");
// 删除指定索引的页面
this.pageStack.removeByIndexes([1, 3, 5]);
// 删除指定id的页面
this.pageStack.removeByNavDestinationId("1");

移动页面

NavPathStack通过Move相关接口去实现移动路由栈中特定页面到栈顶的功能。



// 移动栈中name为PageOne的页面到栈顶
this.pageStack.moveToTop("PageOne");
// 移动栈中索引为1的页面到栈顶
this.pageStack.moveIndexToTop(1);

参数获取

NavDestination子页第一次创建时会触发onReady回调,可以获取此页面对应的参数。



@Component
struct Page01 {
  pathStack: NavPathStack | undefined = undefined;
  pageParam: string = '';
 
  build() {
    NavDestination() {
      // ...
    }.title('Page01')
    .onReady((context: NavDestinationContext) => {
      this.pathStack = context.pathStack;
      this.pageParam = context.pathInfo.param as string;
    })
  }
}

NavDestination组件中可以通过设置onResult接口,接收返回时传递的路由参数。



class NavParam {
  desc: string = 'navigation-param'
}
 
@Component
struct DemoNavDestination {
  // ...
  build() {
    NavDestination() {
      // ...
    }
    .onResult((param: Object) => {
      if (param instanceof NavParam) {
        console.log('TestTag', 'get NavParam, its desc: ' + (param as NavParam).desc);
        return;
      }
      console.log('TestTag', 'param not instance of NavParam');
    })
  }
}

其他业务场景,可以通过主动调用NavPathStack的Get相关接口去获取指定页面的参数。



// 获取栈中所有页面name集合
this.pageStack.getAllPathName();
// 获取索引为1的页面参数
this.pageStack.getParamByIndex(1);
// 获取PageOne页面的参数
this.pageStack.getParamByName("PageOne");
// 获取PageOne页面的索引集合
this.pageStack.getIndexByName("PageOne");

路由拦截

NavPathStack提供了setInterception方法,用于设置Navigation页面跳转拦截回调。该方法需要传入一个NavigationInterception对象,该对象包含三个回调函数:

名称 描述
willShow 页面跳转前回调,允许操作栈,在当前跳转生效。
didShow 页面跳转后回调,在该回调中操作栈会在下一次跳转生效。
modeChange Navigation单双栏显示状态发生变更时触发该回调。

说明

无论是哪个回调,在进入回调时路由栈都已经发生了变化。

开发者可以在willShow回调中通过修改路由栈来实现路由拦截重定向的能力。



this.pageStack.setInterception({
  willShow: (from: NavDestinationContext | "navBar", to: NavDestinationContext | "navBar",
    operation: NavigationOperation, animated: boolean) => {
    if (typeof to === "string") {
      console.log("target page is navigation home page.");
      return;
    }
    // 将跳转到PageTwo的路由重定向到PageOne
    let target: NavDestinationContext = to as NavDestinationContext;
    if (target.pathInfo.name === 'PageTwo') {
      target.pathStack.pop();
      target.pathStack.pushPathByName('PageOne', null);
    }
  }
})

Navigation+路由实现跳转

步骤如下:

根页面将Navigation组件作为根容器,并传入NavPathStack对象

子页面将NavDestination组件作为根容器

为子页面创建入口Builder函数,如@Builder



export function MainPageBuilder() {
  MainPage()
}

在路由表中将子页面配置进去。



   {
      "name": "MainPage",
      "pageSourceFile": "src/main/ets/pages/MainPage.ets",
      "buildFunction": "MainPageBuilder",
      "data": {
        "description" : "this is mainPage"
      }
    }

将路由表route_map.json配置到module.json5文件中,从而保证系统路由表生效。如”routerMap”: “$profile:route_map”,

通过pathStack.pushPathByName方法跳转指定页面。如this.pathStack.pushPathByName('MainPage', null);

二、@hadss/hmrouter三方库使用

为什么选用HMRouter?

官方推荐的Navigation使用时,所有页面都添加一层NavDestination,且配置页面跳转路径等问题。

HMRouter是HarmonyOS官方开源的路由管理组件,旨在简化应用内原生页面的跳转逻辑。作为对系统Navigation组件的高层封装,HMRouter整合了路由拦截、生命周期管理、自定义动画等核心能力,支持多模块解耦开发,显著提升开发效率 。hmrouter官方文档。其核心优势包括:

注解驱动开发:通过@HMRouter注解声明路由路径动态路由支持:兼容HAR/HSP/HAP模块化方案高性能转场动画:支持全局/页面级动画配置生命周期管理:提供全局/单页面/跳转时生命周期回调

安装配置

参考 hmrouter官方文档

快速开始

初始化

 参考 hmrouter官方文档。

注意:在使用到HMRouter的模块中需引入路由编译插件,修改
hvigorfile.ts。
除entry外的所有独立模块需要配置,否则跳转不生效。

定义路由入口

 参考 hmrouter官方文档。路由入口HMNavigation 参数说明

navigationId :必填,容器ID并且全局唯一homePageUrl:指定默认加载的页面navigationOption:全局参数设置。
modifier:Navigation动态属性设置。Navigation的系统属性通过
modifier
传递,部分
modifier
不支持的属性使用
options
设置standardAnimator:页面全局动画配置dialogAnimator:弹窗全局动画配置title:navigation的Title设置menus:navigation的menus设置toolbar:navigation的toolbar设置  systemBarStyle:navigation的systemBarStyle设置

路由跳转使用

 参考 hmrouter官方文档相应模块和Navigation与HMRouter混用官方指南(对Navigation和hmrouter的使用步骤做了对比说明)。

Tabs中使用HMRouterMgr


import { HMRouterMgr } from "@hadss/hmrouter"
import { RouterConstants } from "router"
 
@ComponentV2
export struct TabsPage {
  @Local currentIndex: number = 0
  controller: TabsController = new TabsController()
  
  build() {
    Tabs({ barPosition: BarPosition.End, controller: this.controller }) {
      TabContent() {
        HMRouterMgr.getPageBuilderByUrl(RouterConstants.HomePage)?.builder()
      }.tabBar('首页')
 
      TabContent() {
        HMRouterMgr.getPageBuilderByUrl(RouterConstants.TabMyPage)?.builder()
      }.tabBar('我的')
    }
    .vertical(false)
    .barMode(BarMode.Fixed)
    .barHeight(60)
    .animationDuration(0)
    .onChange((index: number) => {
 
    })
    .width('100%')
    .height('100%')
  }
}
//https://developer.huawei.com/consumer/cn/blog/topic/03190556084201089

路由传参

传递

HMRouterMgr.push({ pageUrl: “LoginPage”, param: { 数据 } });



//HomePage.ets
Button("跳转到PageB").onClick(() => {
  HMRouterMgr.push({
    pageUrl:"/pageB?username=zhangsan&password=123456",	//路径参数
    param:{			//路由参数
      name:"小刚",
      age:20
    }
  })
})
接收

@HMRouter注解参数说明

pageUrl: string, 用来表示NavDestination,必填

1.支持使用本文件或者本模块定义的常量,或者Class中定义的静态变量

2.pageUrl配置支持的格式说明:

支持普通字符串定义,路由跳转采用全路径方式匹配,例如定义demo://xxxx,路由跳转时pageUrl=demo://xxx可以使用全路径匹配

3.pageUrl路由匹配优先级说明:优先全路径匹配,然后匹配正则格式路由,正则内的路由匹配优先级通过regexPriority属性设置;例如定义了两个路由:pageUrl/detail, pageUrl/.* ;当路由跳转时传入pageUrl=/pages/detail,将匹配第一个/pages/detail,当路由跳转时传入pageUrl=/pages/abcdef时,将匹配/pages/.*定义的路由页面

isRegex:boolean, 标识配置的pageUrl是否是正则表达式,如果配置为正则,会影响页面跳转效率,配置为true时,需要确保pageUrl为正确的正则表达式格式,非必填

regexPriority: number, pageUrl正则匹配优先级,数字越大越先匹配,默认值为0,优先级相同时,不保证先后顺序,非必填,默认为0

dialog: boolean, 是否是Dialog类型页面,非必填,默认为false

singleton: boolean, 是否是单例页面,单例页面即表示在一个HMNavigation容器中,只有一个此页面,非必填,默认为false

interceptors: string[], 
@HMInterceptor
标记的拦截器名称列表,非必填

lifecycle: string, 
@HMLifecycle
标记的生命周期处理实例,非必填

animator: string, 
@HMAnimator
标记的自定义转场实例,非必填

获取参数方法说明:

@State param: HMPageParam = HMRouterMgr.getCurrentParam(HMParamType.all);


HMRouterMgr.getCurrentParam(HMParamType.urlParam)
获取路径参数


HMRouterMgr.getCurrentParam(HMParamType.routerParam)
获取路由参数



//PageB.ets
@HMRouter({ pageUrl: "/pageB" })
@Component
export struct PageB {
  aboutToAppear(): void {
    
    //1.获取路径参数
    const urlParam = HMRouterMgr.getCurrentParam(HMParamType.urlParam) as Map<string, Object>
    if (urlParam) {
      const username = urlParam.get("username")
      const password = urlParam.get("password")
      console.log("用户名:" + username)
      console.log("密码:" + password)
    }
 
    //2.获取路由参数
    const student = HMRouterMgr.getCurrentParam(HMParamType.routeParam) as Student
    if (student) {
      console.log("姓名:" + student.name)
      console.log("年龄:" + student.age)
    }
  }
}
页面跳转并返回

有时候页面跳转后再返回,需要携带参数回原页面;如:从HomePage跳转到PageB,再从PageB返回到HomePage同时携带参数。



//HomePage.ets
Button("跳转到PageB").onClick(() => {
  let params: Record<string, Object> = {
              name: "小刚",
              age: 20
  }
  HMRouterMgr.push({
    pageUrl: "/pageB?username=zhangsan&password=123456",
    param: params,
  }, {
    onArrival: () => {
      //跳转完成
      console.log("页面跳转完成")
    },
    onResult: (popInfo: HMPopInfo) => {
        //接收返回参数:{"message":"success","data":{"id":100}}
        console.log("接收返回参数:" + JSON.stringify(popInfo.result))
        if (popInfo.result) {
            let params = popInfo.result as Record<string, Object>
            this.name = params.name as string ?? 'test'
        }
    }
  })
})
 
//PageB.ets
@HMRouter({ pageUrl: "/pageB" })
@Component
export struct PageB {
  build() {
    Column() {
      Text("PageB").fontSize(50).fontWeight(FontWeight.Bold)
      Button("返回HomePage").onClick(() => {
        let params: Record<string, Object> = {
              "name": "test",
        }
        HMRouterMgr.pop({
          param: params
        })
      })
    }
    .height('100%')
    .width('100%')
    .justifyContent(FlexAlign.Center)
  }
}

代码中页面pop返回的参数popInfo: HMPopInfo说明:

{
    “srcPageInfo”: {
        “name”: “pageB”
    },
    “info”: {
        “name”: “pageB”,
        “index”: 2,
        “navDestinationId”: “2”
    }

        …
}

转场动画

通过
@HMAnimator
标签定义转场动画,并实现
IHMAnimator
接口

animatorName:必填,自定义转场动画名称

effect动画定义说明

enterHandle:入场动画定义

exitHandle:出场动画定义

start:定义开始状态

finish:定义终止状态

onFinish:定义结束后的状态

passiveStart:定义被动触发的开始状态

passiveFinish:定义被动触发的终止状态

passiveOnFinish:定义结束后的状态

duration:动画持续时间

curve:动画曲线

在跳转页面时使用跳转动画步骤:1)、定义动画;2)、被跳转组件使用动画;

定义动画

从下到上转场动画



@HMAnimator({ animatorName: "liveCommentsAnimator" })
 
export class liveCommentsAnimator implements IHMAnimator {
  effect(enterHandle: HMAnimatorHandle, exitHandle: HMAnimatorHandle): void {
    // 入场动画
    enterHandle.start((translateOption: TranslateOption, scaleOption: ScaleOption,
      opacityOption: OpacityOption) => {
      translateOption.y = '100%'
    })
    enterHandle.finish((translateOption: TranslateOption, scaleOption: ScaleOption,
      opacityOption: OpacityOption) => {
      translateOption.y = '0'
    })
    enterHandle.duration = 500
 
    // 出场动画
    exitHandle.start((translateOption: TranslateOption, scaleOption: ScaleOption,
      opacityOption: OpacityOption) => {
      translateOption.y = '0'
    })
    exitHandle.finish((translateOption: TranslateOption, scaleOption: ScaleOption,
      opacityOption: OpacityOption) => {
      translateOption.y = '100%'
    })
    exitHandle.duration = 500
  }
}

交互式转场动画,侧滑



import { HMAnimator, HMAnimatorHandle, HMRouterMgr, IHMAnimator } from "@hadss/hmrouter";
​
@HMAnimator({ animatorName: 'liveInteractiveAnimator' })
export class LiveInteractiveAnimator implements IHMAnimator {
  effect(enterHandle: HMAnimatorHandle, exitHandle: HMAnimatorHandle): void {
    enterHandle.start((translateOption, scaleOption, opacity) => {
      translateOption.x = '100%'
      opacity.opacity = 0.4
    })
    enterHandle.finish((translateOption, scaleOption, opacity) => {
      translateOption.x = 0
      opacity.opacity = 1
    })
    enterHandle.onFinish((translateOption, scaleOption, opacity) => {
      translateOption.x = '0'
      opacity.opacity = 1
    })
    exitHandle.start((translateOption, scaleOption, opacity) => {
      translateOption.x = '0'
      opacity.opacity = 1
    })
    exitHandle.finish((translateOption, scaleOption, opacity) => {
      translateOption.x = '100%'
      opacity.opacity = 0.4
    })
    exitHandle.onFinish((translateOption, scaleOption, opacity) => {
      translateOption.x = '0'
      opacity.opacity = 0.4
    })
  }
​
  interactive(handle: HMAnimatorHandle): void {
    handle.actionStart((event: GestureEvent) => {
      if (event.offsetX > 0) {
        HMRouterMgr.pop();
      }
    });
    handle.updateProgress((event, proxy, operation, startOffset) => {
      if (!proxy?.updateTransition || !startOffset) {
        return;
      }
      let offset = 0;
      if (event.fingerList[0]) {
        offset = event.fingerList[0].localX - startOffset;
      }
      if (offset < 0) {
        proxy?.updateTransition(0);
        return;
      }
      let rectWidth = event.target.area.width as number;
      let rate = offset / rectWidth;
      proxy?.updateTransition(rate);
    });
    handle.actionEnd((event, proxy, operation, startOffset) => {
      if (!startOffset) {
        return;
      }
      let rectWidth = event.target.area.width as number;
      let rate = 0;
      if (event.fingerList[0]) {
        rate = (event.fingerList[0].localX - startOffset) / rectWidth;
      }
      if (rate > 0.4) {
        proxy?.finishTransition();
      } else {
        proxy?.cancelTransition?.();
      }
    });
  }
}
使用动画


@HMRouter({
pageUrl: 'CartDetail',
// 使用动画
animator: "liveCommentsAnimator"
 
})
 
@Component
export struct CartDetail {
    build() {
        Column() {
            Button('我的是购物车详情页面')
        }
        .width("100%")
        .height("100%")
        .justifyContent(FlexAlign.Center)
    }
}

拦截器

拦截器可以分成 2 种,局部拦截器和全局拦截器。局部拦截器只对单个页面生效。

使用
@HMInterceptor
标签定义拦截器,并实现
IHMInterceptor
接口。
@HMInterceptor
标签入参说明:

interceptorName: string, 拦截器名称,必填priority: number, 拦截器优先级,数字越大优先级越高,非必填,默认为 9;global: boolean, 是否为全局拦截器,当配置为 true 时,所有跳转均过此拦截器;默认为 false,当为 false 时需要配置在@HMRouter 的 interceptors 中才生效。

执行时机

在路由栈发生变化后,转场动画发生前进行回调。

1.当发生 push/replace 路由时,pageUrl 为空时,拦截器不会执行,需传入 pageUrl 路径;

2.当跳转 pageUrl 目标页面不存在时,执行全局以及发起页面拦截器,当拦截器未执行 DO_REJECT 时,然后执行路由的 onLost 回调

3.当跳转 pageUrl 目标页面存在时,执行全局,发起页面和目标页面的拦截器;

拦截器执行顺序

按照优先级顺序执行,不区分自定义或者全局拦截器,优先级相同时先执行@HMRouter 中定义的自定义拦截器

当优先级一致时,先执行 srcPage>targetPage>global,其中,srcPage 表示跳转发起页面;targetPage 表示跳转结束时展示的页面。

局部拦截器


//定义局部拦截器
@HMInterceptor({ interceptorName: "Loginterceptor", global: false })
export class Loginterceptor implements IHMInterceptor {
  handle(info: HMInterceptorInfo): HMInterceptorAction {
    let connectionInfo: string = info.type === 'push' ? 'jump to' : 'back to';
    Logger.info(`${info.srcName} ${connectionInfo} ${info.targetName}`)
    console.log("拦截器", JSON.stringify(info));
    return HMInterceptorAction.DO_NEXT;//DO_NEXT 正常跳转,DO_REJECT 拒绝跳转
  }
}
 
// 使用拦截器
@HMRouter({
pageUrl: 'LoginPage',
interceptors: ['Loginterceptor']
})
@Component
export struct LoginPage {
    build() {
        Column() {
            Button('登录页面')
        }
        .width("100%")
        .height("100%")
        .justifyContent(FlexAlign.Center)
    }
}
 
//输出效果
{
    "srcName": "HM_NavBar",
    "targetName": "LoginPage",
    "type": "push",
    "routerPathInfo": {
        "pageUrl": "LoginPage"
    },
    "context": {
        "instanceId_": 100000
    },
    "isSrc": false
}
全局拦截器


aboutToAppear(): void {
    // 注册全局拦截器
    HMRouterMgr.registerGlobalInterceptor({
        interceptorName: "拦截器的名字",
        // 优先级
        priority: 1,
        // 拦截器
        interceptor: {
            // 处理函数
            handle(info: HMInterceptorInfo) {
                return HMInterceptorAction.DO_NEXT
            }
        }
    })
}
举例:登陆判断拦截器

如果没登入,跳到登录页,并且设置skipAllInterceptor跳过其他拦截器,避免拦截判定的死循环



import {
  HMInterceptor,
  HMInterceptorAction, HMInterceptorInfo, HMRouterMgr, IHMInterceptor } from '@bmap/lib_router'
​
@HMInterceptor({interceptorName:"loginInterceptor"})
export class loginInterceptor implements IHMInterceptor {
  handle(info: HMInterceptorInfo): HMInterceptorAction {
    if(appStatus.getInstance().isLogin()){
      return HMInterceptorAction.DO_NEXT;
    }else {
      HMRouterMgr.push({ pageUrl: "loginPage", skipAllInterceptor: true });
      return HMInterceptorAction.DO_REJECT;
    }
  }
}

生命周期

@HMLifecycle(lifecycleName, priority, global)标记在实现了 IHMLifecycle 的对象上,声明此对象为一个自定义生命周期处理器。@HMLifecycle参数说明:

lifecycleName: string, 自定义生命周期处理器名称,必填priority: number, 生命周期优先级,数字越大优先级越高,非必填,默认为 9;global: boolean, 是否为全局生命周期,当配置为 true 时,所有页面生命周期事件会转发到此对象;默认为 false

生命周期触发顺序:

按照优先级顺序触发,不区分自定义或者全局生命周期,优先级相同时先执行@HMRouter 中定义的自定义生命周期

完整的生命周期方法


export interface IHMLifecycle {
    onPrepare?(ctx: HMLifecycleContext): void;
    onAppear?(ctx: HMLifecycleContext): void;
    onDisAppear?(ctx: HMLifecycleContext): void;
    onShown?(ctx: HMLifecycleContext): void;
    onHidden?(ctx: HMLifecycleContext): void;
    onWillAppear?(ctx: HMLifecycleContext): void;
    onWillDisappear?(ctx: HMLifecycleContext): void;
    onWillShow?(ctx: HMLifecycleContext): void;
    onWillHide?(ctx: HMLifecycleContext): void;
    onReady?(ctx: HMLifecycleContext): void;
    onBackPressed?(ctx: HMLifecycleContext): boolean;
}
使用举例


import { HMLifecycle, HMLifecycleContext, IHMLifecycle } from '@hadss/hmrouter';
​
@HMLifecycle({ lifecycleName: 'login' })
export class Login implements IHMLifecycle {
  viewModel?: ScanViewModel
 
  onPrepare(ctx: HMLifecycleContext) {
    console.log('login onPrepare')
  }
​
  onAppear(ctx: HMLifecycleContext) {
    console.log('login onAppear')
  }
​
  onDisAppear(ctx: HMLifecycleContext) {
    console.log('login onDisAppear')
  }
​
  onShown(ctx: HMLifecycleContext) {
    console.log('login onShown')
  }
​
  onHidden(ctx: HMLifecycleContext) {
    console.log('login onHidden')
  }
​
  onWillAppear(ctx: HMLifecycleContext) {
    console.log('login onWillAppear')
  }
​
  onWillDisappear(ctx: HMLifecycleContext) {
    console.log('login onWillDisappear')
  }
​
  onWillShow(ctx: HMLifecycleContext) {
    console.log('login onWillShow')
  }
​
  onWillHide(ctx: HMLifecycleContext) {
    console.log('login onWillHide')
  }
​
  onReady(ctx: HMLifecycleContext) {
    console.log('login onReady')
  }
​
  onBackPressed(ctx: HMLifecycleContext) {
    console.log('login onBackPressed')
    return true
  }
}
​
import { HMRouter, HMRouterMgr } from '@hadss/hmrouter';
import { common } from '@kit.AbilityKit';
import { Theme } from '@ohos.arkui.theme';
​
@HMRouter({ pageUrl: 'MainPage',lifecycle:'login'})
@Component
export struct MainPage {
​
  context = this.getUIContext().getHostContext() as common.UIAbilityContext
​
  aboutToAppear(): void {
    console.log('MainPage aboutToAppear')
    // 获取当前界面的生命周期管理类
    let lifeCircle = HMRouterMgr.getCurrentLifecycleOwner()?.getLifecycle() as Login
    if (lifeCircle) {
      lifeCircle.viewModel = this.viewModel
    }
  }
  onWillApplyTheme(theme: Theme): void {
    console.log('MainPage onWillApplyTheme')
  }
  onDidBuild(): void {
    console.log('MainPage onDidBuild')
  }
​
  build() {
    Column(){
      Text('Hello World! MainPage')
        .onClick(() => {
          HMRouterMgr.push({
            pageUrl: 'MainPage2',
          },{
            onResult: (popInfo) => {
              console.log('pop MainPage: ' + JSON.stringify(popInfo))
              this.getUIContext().getPromptAction().showToast({
                message: 'pop MainPage: ' + JSON.stringify(popInfo),
                duration: 5000,
              })
            },
          })
        })
    }
    .width('100%')
    .height('100%')
  }
}
 
//输出结果如下
login onPrepare
[HMRouter DEBUG][LoadModule] load module with info success ,module path is entry/src/main/ets/generated/HMMainPage56081416,module info is com.example.myapplication/entry
login onWillAppear
login onReady
login onAppear
login onWillShow
MainPage aboutToAppear
MainPage onWillApplyTheme
MainPage onDidBuild
login onShown
页面组件和生命周期数据交互

生命周期实例中可以初始化对象,并且在UI组件中获取做为状态变量。



@HMLifecycle({lifecycleName: 'exampleLifecycle'})
export class ExampleLifecycle implements IHMLifecycle {
    model: ObservedModel = new ObservedModel();
}
 
@Observed
export class ObservedModel {
    isLoad: boolean = false;
    bizModel?: Data;
}
 
@HMRouter({pageUrl:'PageOne', lifecycle:'exampleLifecycle'})
@Component
struct PageOne {
    @State model: ObservedModel | null = (HMRouterMgr.getLifecycleOwner().getLifecycle() as ExampleLifecycle).model;
}

 Navigation与HMRouter混用

对于已有大量Navigation代码的项目逐步适配或只使用部分HMRouter功能的场景,需通过NavigationHelper与NavDestinationHelper接口在保留现有Navigation/NavDestination组件的基础上使用HMRouter的部分功能。参考Navigation与HMRouter混用官方指南(对Navigation和hmrouter的使用步骤做了对比说明)。

服务路由使用

服务路由用于类似服务提供发现机制(Service Provider Interface),通过不依赖实现模块的方式获取接口实例并调用方法,当前仅提供方法级的调用



export class CustomService {
  @HMService({ serviceName: 'testConsole' })
  testConsole(): void {
    Logger.info('调用服务 testConsole')
  }
​
  @HMService({ serviceName: 'testFunWithReturn' })
  testFunWithReturn(param1: string, param2: string): string {
    return `调用服务 testFunWithReturn:${param1} ${param2}`
  }
​
  @HMService({ serviceName: 'testAsyncFun', singleton: true })
  async asyncFunction(): Promise<string> {
    return new Promise((resolve) => {
      resolve('调用异步服务 testAsyncFun')
    })
  }
}
​
@HMRouter({ pageUrl: 'test://MainPage' })
@Component
export struct Index {
​
build() {
  Row() {
    Column({ space: 8 }) {
      Button('service').onClick(() => {
        HMRouterMgr.request('testConsole')
        Logger.info(HMRouterMgr.request('testFunWithReturn', 'home', 'service').data)
        HMRouterMgr.request('testAsyncFun').data.then((res: string) => Logger.info(res))
      })
    }
    .width('100%')
  }
  .height('100%')
}
}

当前不支持同时和其他注解混用,也不支持静态方法



// 不支持类与类方法同时添加 @HM* 装饰器
@HMLifecycle({ serviceName: 'lifecycleName' })
export class CustomServiceErr1 {
  @HMService({ serviceName: 'testConsole' }) // 类已经添加 @HMLifecycle 装饰器,@HMService 无法识别
  testConsole(): void {
    Logger.info('调用服务 testConsole')
  }
}
​
// 不支持在静态方法上添加 @HMService 装饰器
export class CustomServiceErr2 {
  @HMService({ serviceName: 'testConsole' }) // 静态方法添加 @HMService 装饰器,调用时会报错
  static testConsole(): void {
    Logger.info('调用服务 testConsole')
  }
}
服务标签 @HMServiceProvider

标记在类上,声明此类为一个服务

serviceName: string,服务名称,必填。

singleton: boolean,是否是单例,非必填,默认为false

示例:



@HMServiceProvider({ serviceName: ServiceConstants.CLASS_SERVICE, singleton: true })
export class CustomService implements IService {
  testConsole(): void {
    Logger.info('Calling service testConsole');
  }
}
 
//使用HMRouterMgr.getService()进行调用
const res = HMRouterMgr.getService<IService>(ServiceConstants.CLASS_SERVICE).testFunWithReturn()
服务标签 @HMService

标记在类的方法上,声明此方法为一个服务

serviceName: string,服务名称,必填。

singleton: boolean,是否是单例,非必填,默认为false

示例:



export class ExampleClass {
  @HMService({ serviceName: 'ExampleService', singleton: true })
  exampleFun(params: string): void {
  }
}

工程配置最佳实践

指定编译时文件搜索目录,缩短编译时间

在项目根目录创建路由编译插件配置文件hmrouter_config.json,并配置scanDir属性;如果不配置则对代码进行全量扫描,如果配置则数组不能为空,建议配置指定目录可缩短编译耗时。
  “scanDir”: [
    “src/main/ets/components”,
    “src/main/ets/interceptors”
  ],

动态导入配置(build-profile.json5)



{
  "app": {
    "products": [
      {
        "buildOption": {
          "strictMode": {
            "useNormalizedOHMUrl": true
          }
        }
      }
    ]
  }
}

HMRouter官方文档整理

hmrouter 的官网文档比较零散,整理如下:HMRouter 接口和属性列表、HMRouterPlugin 编译插件使用说明、HMRouterTransitions 高阶转场动画使用说明、自定义模板使用说明、自定义转场动画使用说明、常用跳转举例、HMRouter使用说明App、使用常见问题FAQ、原理介绍、Navigation问题说明及HMRouter优势说明;

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容