# HMRouter **Repository Path**: GitIpman/hmrouter ## Basic Information - **Project Name**: HMRouter - **Description**: HMRouter作为HarmonyOS的页面跳转场景解决方案,聚焦应用内原生页面的跳转逻辑。 HMRouter底层对系统Navigation进行封装,集成了Navigation、NavDestination、NavPathStack的系统能力,提供了可复用的路由拦截、页面生命周期、自定义转场动画,并且在传参、额外的生命周期、服务型路由方面对系统能力进行了扩展。 帮助开发者更好的进行模块间解耦。 - **Primary Language**: TypeScript - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 51 - **Created**: 2024-09-06 - **Last Updated**: 2024-09-06 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # HMRouter简介 HMRouter作为HarmonyOS的页面跳转场景解决方案,聚焦解决应用内原生页面的跳转逻辑。 HMRouter底层对系统Navigation进行封装,封装了Navigation、NavDestination、NavPathStack的能力,提供了可复用的路由拦截、自定义转场动画、页面生命周期框架,目的是让开发者在开发中无需关注Navigation、NavDestination容器组件的相关细节及模板代码,更好的进行模块间解耦。 # 特性 - 基于注解声明路由信息 - 支持Har、Hsp、Hap - 支持Navigation路由栈嵌套 - 支持路由拦截器(包含全局拦截、单页面拦截、跳转时一次性拦截) - 支持生命周期回调(包含全局生命周期、单页面生命周期、跳转时一次性生命周期) - 内置转场动画(页面、Dialog),可配置方向、透明度、缩放,支持跟手抽屉式转场动画,同时支持配置某个页面的转场动画、跳转时的一次性动画 - 支持Dialog类型页面、支持单例页面 # 依赖版本 HarmonyOS NEXT Beta3及以上 # 下载安装 使用ohpm安装依赖 ```shell ohpm install @hadss/hmrouter ``` 或者按需在模块中配置运行时依赖,修改`oh-package.json5` ```json5 { "dependencies": { "@hadss/hmrouter": "^1.0.0-rc.0" } } ``` # 快速开始 ### 使用编译插件 [查看详情](#编译插件配置) ### 在UIAbility或者启动框架AppStartup中初始化路由框架 ```typescript export default class EntryAbility extends UIAbility { onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { HMRouterMgr.init({ context: this.context }) } } ``` ### 定义路由入口: ```typescript @Entry @Component export struct ShoppingBagContent { modifier: ShoppingNavModifier = new ShoppingNavModifier(); build() { // @Entry中需要再套一层容器组件,Column或者Stack Column(){ // 使用HMNavigation容器 HMNavigation({ navigationId: 'shoppingNavigation', options: { standardAnimator: HMDefaultGlobalAnimator.STANDARD_ANIMATOR, dialogAnimator: HMDefaultGlobalAnimator.DIALOG_ANIMATOR, modifier: modifier } }) { Row() { ShoppingBagView() ShoppingBagDiscounts() } .width(CommonConstants.FULL_PERCENT) .height(CommonConstants.FULL_PERCENT) .backgroundColor(ResourceUtil.getCommonBackgroundColor()[0]) } } .height('100%') .width('100%') } } class ShoppingNavModifier extends AttributeUpdater { initializeModifier(instance: NavigationAttribute): void { instance.mode(NavigationMode.Stack); instance.navBarWidth('100%'); instance.hideTitleBar(true); instance.hideToolBar(true); } } ``` ### 定义拦截器 ```typescript @HMInterceptor({ interceptorName: 'JumpInfoInterceptor', global: true }) export class JumpInfoInterceptor implements IHMInterceptor { handle(info: HMInterceptorInfo): HMInterceptorAction { let connectionInfo: string = info.type === 'push' ? 'jump to' : 'back to'; console.log(`${info.srcName} ${connectionInfo} ${info.targetName}`) return HMInterceptorAction.DO_NEXT; } } ``` ### 定义生命周期 ```typescript @HMLifecycle({ lifecycleName: 'PageDurationLifecycle' }) export class PageDurationLifecycle extends AbstractLifecycle { private time: number = 0; onShown(ctx: HMLifecycleContext): HMLifecycleAction { this.time = new Date().getTime(); return HMLifecycleAction.DO_NEXT(); } onHidden(ctx: HMLifecycleContext): HMLifecycleAction { const duration = new Date().getTime() - this.time; console.log(`Page ${ctx.navContext?.pathInfo.name} stay ${duration}`); return HMLifecycleAction.DO_NEXT(); } } ``` ### 自定义转场动画 ```typescript @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 } } ``` ### 组合使用 定义页面PageB,绑定生命周期及自定义转场动画 ```typescript @HMRouter({ pageUrl: 'pageB', lifecycle: 'pageLifecycle', animator: 'pageAnimator' }) @Component export struct PageB { // 获取生命周期中定义的状态变量 @State model: ObservedModel | null = (HMRouterMgr.getCurrentLifecycle() as PageLifecycle).model @State param: Object = HMRouterMgr.getCurrentParam() build() { Text(`${this.model?.property}`) Text(`${this.param?.msg}`) } } ``` 定义页面PageA,并执行路由跳转至PageB ```typescript @HMRouter({ pageUrl: 'pageA'}) @Component export struct PageA { build() { Column() { Button('Push') .onClick(() => { HMRouterMgr.push({pageUrl: 'pageB'}) }) } } } ``` # 使用配置 ## 编译插件配置 1. 修改项目的`hvigor/hvigor-config.json`文件,加入路由编译插件 ```json5 { "dependencies": { "@hadss/hmrouter-plugin": "^1.0.0-rc.0" // 使用npm仓版本号 }, // ...其他配置 } ``` 2. 项目根目录创建路由编译插件配置文件`hmrouter_config.json`(推荐) ```json5 { "scanDir": ["src/main/ets/components","src/main/ets/interceptors"], // 如果不传则扫描src/main/ets目录,对代码进行全量扫描,建议配置可缩短编译耗时 "saveGeneratedFile": false, // 默认为false,改成true则不删除编译产物 } ``` 3. 在模块中引入路由编译插件,修改`hvigorfile.ts` ```typescript import { hapTasks } from '@ohos/hvigor-ohos-plugin'; import { hapPlugin } from "@hadss/hmrouter-plugin"; export default { system: hapTasks, plugins: [hapPlugin()] } ``` > 如果是Har则使用`harPlugin()`, Hsp则使用`hspPlugin()` ## HMRouter运行时配置 1. 安装依赖 ```shell ohpm install @hadss/hmrouter ``` 或者在模块中配置运行时依赖,修改`oh-package.json5` ```json5 { "dependencies": { "@hadss/hmrouter": "^1.0.0-rc.0" } } ``` 2. 在Hap中配置动态依赖模块 由于拦截器、生命周期和自定义转场动画会在运行时动态创建实例,因此需要进行如下配置,使得HMRouter路由框架可以动态导入项目中的模块 在`oh-package.json5`中配置对Har和Hsp的依赖,这里需要注意**依赖的模块名称需要与模块的modulename保持一致**。 ```json5 { "dependencies": { "AppHar": "file:../AppHar", // AppHar库可以正确动态创建拦截器、生命周期和自定义转场动画对象 "@app/har": "file:../AppHar" // 无法动态创建对象 } } ``` 工程目录下的`build-profile.json5`中,配置`useNormalizedOHMUrl`属性为true ```json5 { "app": { "products": [ { "name": "default", "signingConfig": "default", "compatibleSdkVersion": "5.0.0(12)", "runtimeOS": "HarmonyOS", "buildOption": { "strictMode": { "useNormalizedOHMUrl": true } } } ], // ...其他配置 } } ``` # HMRouter标签的使用规则 ## 路由标签@HMRouter `@HMRouter(pageUrl, dialog, singleton, interceptors, lifecycle, animator)` 标签使用在自定义组件`struct`上,且该自定义组件需要添加`export`关键字 - pageUrl: string, 用来表示NavDestination,必填 - dialog: boolean, 是否是Dialog类型页面,非必填,默认为false - singleton: boolean, 是否是单例页面,单例页面即表示在一个HMNavigation容器中,只有一个此页面,非必填,默认为false - interceptors: string[], `@HMInterceptor`标记的拦截器名称列表,非必填 - lifecycle: string, `@HMLifecycle`标记的生命周期处理实例,非必填 - animator: string, `@HMAnimator`标记的自定义转场实例,非必填 示例: ```typescript @HMRouter({ pageUrl: 'pageOne', interceptors: ['LoginInterceptor'], lifecycle: 'pageLifecycle', animator: 'pageAnimator' }) @Component export struct PageOne { build() { } } ``` ## 拦截器标签 @HMInterceptor 标记在实现了`IHMInterceptor`的对象上,声明此对象为一个拦截器 - interceptorName: string, 拦截器名称,必填 - priority: number, 拦截器优先级,数字越大优先级越高,非必填,默认为9; - global: boolean, 是否为全局拦截器,当配置为true时,所有跳转均过此拦截器;默认为false,当为false时需要配置在@HMRouter的interceptors中才生效。 **执行时机:** 在路由栈发生变化前进行回调,转场动画发生前。 **拦截器执行顺序:** 1. 按照优先级顺序执行,不区分自定义或者全局拦截器,优先级相同时先执行@HMRouter中定义的自定义拦截器 2. 当srcPage , targetPage, global,严格按照优先级执行;当优先级一致时,先执行srcPage>targetPage>global) > srcPage表示跳转发起页面。 > > targetPage表示跳转结束时展示的页面。 示例: ```typescript @HMInterceptor({ priority : 9, interceptorName : "LoginInterceptor" }) export class LoginInterceptor implements IHMInterceptor { handle(info: HMInterceptorInfo): HMInterceptorAction { if (isLogin) { // 跳转下一个拦截器处理 return HMInterceptorAction.DO_NEXT; } else { HMRouterMgr.push({ pageUrl: 'loginPage', param: { targetUrl: info.targetName }, skipAllInterceptor: true }) // 拦截结束,不再执行下一个拦截器,不再执行相关转场和路由栈操作 return HMInterceptorAction.DO_REJECT; } } } ``` ## 生命周期标签 @HMLifecycle `@HMLifecycle(lifecycleName, priority, global)` 标记在实现了IHMLifecycle的对象上,声明此对象为一个自定义生命周期处理器 - lifecycleName: string, 自定义生命周期处理器名称,必填 - priority: number, 生命周期优先级,数字越大优先级越高,非必填,默认为9; - global: boolean, 是否为全局生命周期,当配置为true时,所有页面生命周期事件会转发到此对象;默认为false **生命周期触发顺序:** 按照优先级顺序执行,不区分自定义或者全局生命周期,优先级相同时先执行@HMRouter中定义的自定义生命周期 示例: ```typescript @HMLifecycle({lifecycleName : "exampleLifecycle"}) export class ExampleLifecycle extends AbstractLifecycle { } ``` ## 转场动画标签 @HMAnimator 标记在实现了IHMAnimator的对象上,声明此对象为一个自定义转场动画对象 - animatorName: string, 自定义动画名称,必填。 示例: ```typescript @HMAnimator({animatorName : "exampleAnimator"}) export class ExampleAnimator implements IHMAnimator { effect(enterHandle: HMAnimatorHandle, exitHandle: HMAnimatorHandle): void { } } ``` # HMRouter接口和属性列表 [查看详情](docs/Reference.md) # 原生到原生页面跳转场景解决方案 [查看详情](docs/Scene.md) # 原理介绍 [查看详情](https://developer.huawei.com/consumer/cn/forum/topic/0207153170697988820?fid=0109140870620153026) # 开源协议 本项目基于 [Apache License 2.0](LICENSE) ,请自由地享受和参与开源。