小白必看 HarmonyOS Next HMRouter 轻松上手秘籍 原创

万少skr
发布于 2024-12-29 09:32
浏览
0收藏

小白必看 HarmonyOS Next HMRouter 轻松上手秘籍

前言

HMRouter 作为 HarmonyOS 的页面跳转场景解决方案,聚焦解决应用内原生页面的跳转逻辑。

HMRouter 底层对系统 Navigation 进行封装,集成了 Navigation、NavDestination、NavPathStack 的系统能力,提供了可复用的路由拦截、页面生命周期、自定义转场动画,并且在跳转传参、额外的生命周期、服务型路由方面对系统能力进行了扩展。

目的是让开发者在开发过程中无需关注 Navigation、NavDestination 容器组件的相关细节及模板代码,屏蔽跳转时的判断逻辑,降低拦截器、自定义转场动画实现复杂度,更好的进行模块间解耦

对比

目前鸿蒙应用开发中,官方推出的路由方案有两个,分别是RouterNavigation。目前官方主要推荐的也是 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’)
获取页面栈对象 支持 不支持
路由拦截 支持通过 setInterception 做路由拦截 不支持
路由栈信息查询 支持 getState() & getLength()
路由栈 move 操作 moveToTop & moveIndexToTop 不支持
沉浸式页面 支持 不支持,需通过 window 配置
设置页面标题栏(titlebar)和工具栏(toolbar) 支持 不支持
模态嵌套路由 支持 不支持

但是原生的 Navigation 缺少了路由拦截、页面生命周期、自定义转场动画,并且在跳转传参、额外的生命周期、服务型路由。

因此 HMRouter 便是对此做出了拓展和增强。

学习目标

接下来,将通过这篇文章带领小伙伴上手HMRouter的应用。

工程目录

新建完工程后,再新建一个 Cart 动态共享包模块

  1. 工程的目录名称是 study
  2. 入口模块是 entry
  3. cart 是 hsp 模块

小白必看 HarmonyOS Next HMRouter 轻松上手秘籍-鸿蒙开发者社区

配置环境

使用 ohpm 安装依赖

ohpm install @hadss/hmrouter
ohpm install @hadss/hmrouter-transitions

编译插件配置

  1. 修改工程的hvigor/hvigor-config.json文件,加入路由编译插件

    {
      "dependencies": {
        "@hadss/hmrouter-plugin": "^1.0.0-rc.10"
        // 使用npm仓版本号
      }
      // ...其他配置
    }
    
  2. 在使用到 HMRouter 的模块中引入路由编译插件,修改hvigorfile.ts

    我们项目的模块无非是 Hap、Har 和 Hsp。对应你当前的模块是哪种类型,就使用对应的写法

    1. Hap

      // entry/hvigorfile.ts  entry模块的hvigorfile.ts
      import { hapTasks } from "@ohos/hvigor-ohos-plugin";
      import { hapPlugin } from "@hadss/hmrouter-plugin";
      
      export default {
        system: hapTasks,
        plugins: [hapPlugin()], // 使用HMRouter标签的模块均需要配置,与模块类型保持一致
      };
      
    2. Har

      import { harTasks } from "@ohos/hvigor-ohos-plugin";
      import { harPlugin } from "@hadss/hmrouter-plugin";
      
      export default {
        system: harTasks,
        plugins: [harPlugin()], // 使用HMRouter标签的模块均需要配置,与模块类型保持一致
      };
      
    3. Hsp

      import { hspTasks } from "@ohos/hvigor-ohos-plugin";
      import { hspPlugin } from "@hadss/hmrouter-plugin";
      
      export default {
        system: hspTasks,
        plugins: [hspPlugin()], // 使用HMRouter标签的模块均需要配置,与模块类型保持一致
      };
      

初始化路由框架

entry/src/main/ets/entryability/EntryAbility.ets

export default class EntryAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    HMRouterMgr.init({
      context: this.context,
    });
  }
}

定义路由入口

entry/src/main/ets/pages/Index.ets

当前页面作为整个路由的根容器

import { HMDefaultGlobalAnimator, HMNavigation } from "@hadss/hmrouter";
import { AttributeUpdater } from "@kit.ArkUI";

class MyNavModifier extends AttributeUpdater<NavigationAttribute> {
  initializeModifier(instance: NavigationAttribute): void {
    // instance.hideNavBar(true);  // 先注释掉  否则看不见结果
  }
}

@Entry
@Component
export struct Index {
  modifier: MyNavModifier = new MyNavModifier();

  build() {
    // @Entry中需要再套一层容器组件,Column或者Stack
    Column() {
      // 使用HMNavigation容器
      HMNavigation({
        navigationId: 'mainNavigation', homePageUrl: 'MainPage',
        options: {
          standardAnimator: HMDefaultGlobalAnimator.STANDARD_ANIMATOR,
          dialogAnimator: HMDefaultGlobalAnimator.DIALOG_ANIMATOR,
          modifier: this.modifier
        }
      }) {
        Column({ space: 10 }) {
          Button("跳转到 登录页面")
        }
      }
    }
    .height('100%')
    .width('100%')
  }
}

模块内跳转

小白必看 HarmonyOS Next HMRouter 轻松上手秘籍-鸿蒙开发者社区

我们先演示跳转到当前模块中的某个页面。

HMRouter 默认指定了 页面目录 为 entry/src/main/ets/components

我们在这个里新建一个组件 entry/src/main/ets/components/LoginPage.ets

import { HMRouter } from "@hadss/hmrouter"

@HMRouter({
  pageUrl: 'LoginPage',
})
@Component
export struct LoginPage {
  build() {
    Column() {
      Button('登录页面')
    }
    .width("100%")
    .height("100%")
    .justifyContent(FlexAlign.Center)
  }
}

此时,回到首页中,进行点击跳转登录

Button("跳转到 登录页面").onClick(() => {
  HMRouterMgr.push({ pageUrl: "LoginPage" });
});

路由传参

传递

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

接收

HMRouterMgr.getCurrentParam(HMParamType.all);

指定编译目录

刚才的登录页面是存放到 components 目录下的,实际开发中,我们可以会通过 views来存放页面,所以这里来设置下

在项目根目录创建路由编译插件配置文件study/hmrouter_config.json(可选)

{
  "scanDir": ["src/main/ets/views"]
}

然后重命名之前的文件夹名字 entry/src/main/ets/componentsentry/src/main/ets/views

重新编译执行即可

模块之间跳转

刚才的演示是在同一个模块内进行的,现在我们来演示不同模块之间的跳转

演示的目标是 entry 模块跳转到 cart 模块

cart 模块配置编译插件

cart 是 hsp

cart/hvigorfile.ts

import { hspTasks } from "@ohos/hvigor-ohos-plugin";
import { hspPlugin } from "@hadss/hmrouter-plugin";

export default {
  system: hspTasks,
  plugins: [hspPlugin()], // 使用HMRouter标签的模块均需要配置,与模块类型保持一致
};

新建购物详情页面

cart/src/main/ets/views/CartDetail.ets

import { HMRouter } from "@hadss/hmrouter"

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

entry 模块引入 cart 模块

entry/oh-package.json5

  "dependencies": {
    "cart": "file:../cart"
  },

首页中进行跳转

entry/src/main/ets/pages/Index.ets

Button("跳转到 购物车详情页面").onClick(() => {
  HMRouterMgr.push({ pageUrl: "CartDetail" });
});

效果

小白必看 HarmonyOS Next HMRouter 轻松上手秘籍-鸿蒙开发者社区

跳转动画

我们可以在跳转页面的时候来指定跳转动画

分类两个步骤

  1. 定义动画
  2. 使用动画

定义动画

假设 A 跳转 B, 那么就是 B 使用动画,为了方便使用,可以在 B 页面定义动画

我们继续使用上面的例子

index 跳转到 CarDetail , 所以在 CarDetail 定义动画

cart/src/main/ets/views/CartDetail.ets

@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;
  }
}

使用动画

在 HMRouter 上使用

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

效果

小白必看 HarmonyOS Next HMRouter 轻松上手秘籍-鸿蒙开发者社区

拦截器

拦截器可以分成 2 种,局部拦截器和全局拦截器

标记在实现了IHMInterceptor的对象上,声明此对象为一个拦截器

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

执行时机:

在路由栈发生变化前,转场动画发生前进行回调。 1.当发生 push/replace 路由时,pageUrl 为空时,拦截器不会执行,需传入 pageUrl 路径;

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

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

拦截器执行顺序:

  1. 按照优先级顺序执行,不区分自定义或者全局拦截器,优先级相同时先执行@HMRouter 中定义的自定义拦截器
  2. 当优先级一致时,先执行 srcPage>targetPage>global

srcPage 表示跳转发起页面。

targetPage 表示跳转结束时展示的页面。

局部拦截器

局部拦截器只对单个页面生效。我们拿 登录页面来测试 从首页 跳转到登录页面,登录页面的拦截器便会触发

entry/src/main/ets/views/LoginPage.ets

定义拦截器

@HMInterceptor({ interceptorName: "Loginterceptor", global: false })
export class JumpInfoInterceptor implements IHMInterceptor {
  handle(info: HMInterceptorInfo): HMInterceptorAction {
    console.log("拦截器", JSON.stringify(info));
    // DO_NEXT  正常跳转
    // DO_REJECT 拒绝跳转
    return HMInterceptorAction.DO_NEXT;
  }
}

使用拦截器

// 使用拦截器
@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
}

全局拦截器

直接在 index 页面上使用

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

生命周期

@HMLifecycle(lifecycleName, priority, global)

标记在实现了 IHMLifecycle 的对象上,声明此对象为一个自定义生命周期处理器

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

生命周期触发顺序:

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

我们可以继续在登录页面上测试对应的生命周期

entry/src/main/ets/views/LoginPage.ets

定义生命周期

@HMLifecycle({ lifecycleName: 'LoginLifecycle' })
export class PageDurationLifecycle implements IHMLifecycle {
  private time: number = 0;

  onShown(ctx: HMLifecycleContext): void {
    this.time = new Date().getTime();
    console.log("生命周期", JSON.stringify(ctx))
  }

  onHidden(ctx: HMLifecycleContext): void {
    const duration = new Date().getTime() - this.time;
  }
}

使用生命周期


// 使用拦截器
@HMRouter({
  pageUrl: 'LoginPage',
  interceptors: ['Loginterceptor'],
  lifecycle: "LoginLifecycle"
})
@Component
export struct LoginPage {
  build() {
    Column() {
      Button('登录页面')
    }
    .width("100%")
    .height("100%")
    .justifyContent(FlexAlign.Center)
  }
}

完整生命周期

export interface HMLifecycleContext {
    uiContext: UIContext;
    navContext?: NavDestinationContext;
}
export type HMLifecycleCallback = (ctx: HMLifecycleContext) => boolean | void;
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;
}

页面组件和生命周期数据交互

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

import {
  HMInterceptor,
  HMInterceptorAction,
  HMInterceptorInfo,
  HMLifecycle,
  HMLifecycleContext,
  HMRouter,
  HMRouterMgr,
  IHMInterceptor,
  IHMLifecycle
} from '@hadss/hmrouter';

// 定义拦截器
@HMInterceptor({ interceptorName: 'Loginterceptor', global: false })
export class JumpInfoInterceptor implements IHMInterceptor {
  handle(info: HMInterceptorInfo): HMInterceptorAction {
    console.log("拦截器", JSON.stringify(info))
    return HMInterceptorAction.DO_NEXT;
  }
}

@Observed
export class ObservedModel {
  isLoad: boolean = false;
}

@HMLifecycle({ lifecycleName: 'LoginLifecycle' })
export class PageDurationLifecycle implements IHMLifecycle {
  model: ObservedModel = new ObservedModel()

  onAppear(ctx: HMLifecycleContext): void {
  }
}


// 使用拦截器
@HMRouter({
  pageUrl: 'LoginPage',
  interceptors: ['Loginterceptor'],
  lifecycle: "LoginLifecycle"
})
@Component
export struct LoginPage {
  @State model: ObservedModel | null =
    (HMRouterMgr.getCurrentLifecycleOwner()?.getLifecycle() as PageDurationLifecycle).model;

  build() {
    Column() {
      Button('登录页面' + this.model?.isLoad)
        .onClick(() => {
          this.model!.isLoad = !this.model?.isLoad
        })

    }
    .width("100%")
    .height("100%")
    .justifyContent(FlexAlign.Center)
  }
}

小结

hmrouter 的官网文档还是挺零散的,需要结合文档配套学习使用

HMRouter 接口和属性列表

查看详情

HMRouterPlugin 编译插件使用说明

查看详情

HMRouterTransitions 高阶转场动画使用说明

查看详情

自定义模板使用说明

查看详情

自定义转场动画使用说明

查看详情

原生到原生页面跳转场景解决方案

查看详情

SampleCode

查看详情

更多示例

查看详情

FAQ

查看详情

原理介绍

查看详情

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
标签
收藏
回复
举报
回复
    相关推荐