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

简介: 本文详细介绍了HarmonyOS页面跳转解决方案——HMRouter的使用方法,帮助开发者快速上手。HMRouter封装了系统Navigation能力,提供路由拦截、页面生命周期管理、自定义转场动画等功能,简化开发流程。文章通过具体示例讲解模块内及跨模块页面跳转、路由传参、自定义动画、拦截器和生命周期管理等核心功能。同时,提供了丰富的资源链接,包括接口文档、高级动画教程和常见问题解答,适合初学者系统学习HMRouter的使用与原理。

小白必看 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 模块

image-20241224094003606

配置环境

使用 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%')
  }
}

模块内跳转

PixPin_2024-12-24_10-22-23

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

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" });
});

效果

PixPin_2024-12-24_10-48-50

跳转动画

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

分类两个步骤

  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)
  }
}

效果

PixPin_2024-12-24_11-22-25

拦截器

拦截器可以分成 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

查看详情

原理介绍

查看详情

目录
相关文章
|
4月前
|
容器
HarmonyOS NEXT仓颉开发语言实战案例:外卖App
仓颉语言实战分享,教你如何用仓颉开发外卖App界面。内容包括页面布局、导航栏自定义、搜索框实现、列表模块构建等,附完整代码示例。轻松掌握Scroll、List等组件使用技巧,提升HarmonyOS应用开发能力。
|
3月前
|
移动开发 前端开发 JavaScript
鸿蒙NEXT时代你所不知道的全平台跨端框架:CMP、Kuikly、Lynx、uni-app x等
本篇基于当前各大活跃的跨端框架的现状,对比当前它们的情况和未来的可能,帮助你在选择框架时更好理解它们的特点和差异。
317 0
|
4月前
|
安全 API 开发工具
【HarmonyOS NEXT】一键扫码功能
这些Kit为我们应用开发提升了极大地效率。很多简单的功能,如果不需要太深的定制化需求,直接调用kit提供的API就可以实现,在android或者ios上需要很多代码才能实现的功能效果。
122 0
HarmonyOS NEXT仓颉开发语言实战案例:电影App
周末好!本文分享使用仓颉语言重构ArkTS实现的电影App案例,对比两者在UI布局、组件写法及语法差异。内容包括页面结构、列表分组、分类切换与电影展示等。通过代码演示仓颉在HarmonyOS开发中的应用。##仓颉##ArkTS##HarmonyOS开发
|
4月前
|
容器
HarmonyOS NEXT仓颉开发语言实战案例:健身App
本期分享一个健身App首页的布局实现,顶部采用Stack容器实现重叠背景与偏移效果,列表部分使用List结合Scroll实现可滚动内容。代码结构清晰,适合学习HarmonyOS布局技巧。
HarmonyOS NEXT仓颉开发语言实战案例:小而美的旅行App
本文分享了一个旅行App首页的设计与实现,使用List容器搭配Row、Column布局完成个人信息、功能列表及推荐模块的排版,详细展示了HarmonyOS下的界面构建技巧。
|
20天前
|
存储 缓存 5G
鸿蒙 HarmonyOS NEXT端云一体化开发-云存储篇
本文介绍用户登录后获取昵称、头像的方法,包括通过云端API和AppStorage两种方式,并实现上传头像至云存储及更新用户信息。同时解决图片缓存问题,添加上传进度提示,支持自动登录判断,提升用户体验。
93 0
|
20天前
|
存储 负载均衡 数据库
鸿蒙 HarmonyOS NEXT端云一体化开发-云函数篇
本文介绍基于华为AGC的端云一体化开发流程,涵盖项目创建、云函数开通、应用配置及DevEco集成。重点讲解云函数的编写、部署、调用与传参,并涉及环境变量设置、负载均衡、重试机制与熔断策略等高阶特性,助力开发者高效构建稳定云端服务。
193 0
鸿蒙 HarmonyOS NEXT端云一体化开发-云函数篇
|
20天前
|
存储 JSON 数据建模
鸿蒙 HarmonyOS NEXT端云一体化开发-云数据库篇
云数据库采用存储区、对象类型、对象三级结构,支持灵活的数据建模与权限管理,可通过AGC平台或本地项目初始化,实现数据的增删改查及端侧高效调用。
56 0
|
20天前
|
存储 开发者 容器
鸿蒙 HarmonyOS NEXT星河版APP应用开发-ArkTS面向对象及组件化UI开发使用实例
本文介绍了ArkTS语言中的Class类、泛型、接口、模块化、自定义组件及状态管理等核心概念,并结合代码示例讲解了对象属性、构造方法、继承、静态成员、访问修饰符等内容,同时涵盖了路由管理、生命周期和Stage模型等应用开发关键知识点。
156 0
鸿蒙 HarmonyOS NEXT星河版APP应用开发-ArkTS面向对象及组件化UI开发使用实例