前言
应用开发离不开页面路由,鸿蒙应用开发提供了两套路由机制分别是Router和Navigation,在刚开始接触鸿蒙开发的时候选择Router作为路由,但是随着版本不断更新官方也越来越推荐使用Navigation,所以本篇文章将以Navigation为基础配合使用路由表,对其进行封装。
官方文档:Navigation与Router对比和如何进行选择
准备工作
路由表
要使用Navigation配合使用路由表进行路由首先要配置我我们的路由表。我们在
entry/src/main/resources/base/profile下新建route_map.json
文件,route\_map文件标识模块配置的路由表的路径。routerMap配置文件描述模块的路由表信息,routerMap标签值为数组类型,并且在工程配置文件module.json5
中配置 {"routerMap": "$profile:route\_map"}。
route_map
各个标签说明如下:
属性名称 | 含义 | 数据类型 | 是否可缺省 |
---|---|---|---|
name | 标识跳转页面的名称。取值为长度不超过1023字节的字符串。 | 字符串 | 该标签不可缺省。 |
pageSourceFile | 标识页面在模块内的路径。取值为长度不超过255字节的字符串。 | 字符串 | 该标签不可缺省。 |
buildFunction | 标识被@Builder修饰的函数,该函数描述页面的UI。取值为长度不超过1023字节的字符串。 | 字符串 | 该标签不可缺省。 |
data | 标识字符串类型的自定义数据。 每个自定义数据字符串取值不超过128字节。 | 对象 | 该标签可缺省,缺省值为空。 |
customData | 标识任意类型的自定义数据,总长度不超过4096。 | 对象 | 该标签可缺省,缺省值为空。 |
根视图容器和目的地容器
下面我们需要创建好Navigation和NavDestination,将NavDestination的各种信息在路由表中注册即可。
Navigation:
@Entry
@Component
struct Index {
build() {
Navigation() {
Column() {
Button("Click to Nav")
.onClick(() => {
})
}
}
.height('100%')
.width('100%')
}
NavDestination:
//对应路由表中的buildFunction
@Builder
export function MinePageBuilder() {
MinePage()
}
@Component
export struct MinePage {
build() {
NavDestination() {
Column() {
Text('MinePage')
}
.width('100%')
.height('100%')
}
}
}
路由表:
{
"routerMap": [
{
"name": "page/MinePage",//标识跳转页面的名称
"buildFunction": "MinePageBuilder" ,//标识被@Builder修饰的函数
"pageSourceFile": "src/main/ets/pages/MinePage.ets"//标识页面在模块内的路径
}
]
}
NavHelper
新建static library,命名为navhelper,在entry的oh\_packages.json5文件中添加依赖:
"dependencies": {
'navhelper': 'file:../navhelper'
}
创建文件NavHelper
export class NavHelper {
private static pathStack: NavPathStack //路由栈
static getPathStack() {
if (NavHelper.pathStack) {
return NavHelper.pathStack
}
NavHelper.pathStack = new NavPathStack()
return NavHelper.pathStack
}
}
将路由栈传入Navigation中
Navigation(NavHelper.getPathStack())
跳转和返回
携带参数跳转
以pushDestination作为跳转方法为例子,我们先来看看方法需要哪些参数:
参数:
参数名 | 类型 | 必填 | 说明 |
---|---|---|---|
info | NavPathInfo | 是 | NavDestination页面的信息。 |
options | NavigationOptions | 否 | 页面栈操作选项。 |
其中传入的info含有跳转的目的地path名称,跳转所携带的参数,返回时携带的参数等等,options包含了跳转模式、跳转是否需要动画这两项。
现在我们将上述的参数合并到一个类中,名称为NavDesInfo
:
export class NavDesInfo {
//目标path
private _path: string = '';
public set path(value: string) {
this._path = value;
}
public get path(): string {
return this._path;
}
//是否使用动画
private _anime: boolean = true;
public set anime(value: boolean) {
this._anime = value;
}
public get anime(): boolean {
return this._anime;
}
//携带参数
private _param?: Object | undefined;
public set param(value: Object | undefined) {
this._param = value;
}
public get param(): Object | undefined {
return this._param;
}
//跳转模式
private _launchMode: LaunchMode = LaunchMode.STANDARD;
public set launchMode(value: LaunchMode) {
this._launchMode = value;
}
public get launchMode(): LaunchMode {
return this._launchMode;
}
//path为构造函数入参必须传入
constructor(path: string) {
this._path = path;
}
addParam(param: Object): NavDesInfo {
this._param = param
return this
}
setLaunchMode(launchMode: LaunchMode = LaunchMode.STANDARD): NavDesInfo {
this._launchMode = launchMode
return this
}
enableAnim(enableAnim: boolean = true): NavDesInfo {
this._anime = enableAnim
return this
}
}
并且创建文件NavConstant.ets
,每当有新的NavDestination创建时,就在该文件中声明对应的path,并且创建工厂类NavInfoFactory
用于创建新的NavDesInfo对象
import { NavDesInfo } from '../model/NavDesInfo'
export const MINE_PAGE = 'page/MinePage'
export class NavInfoFactory {
static create(path: string): NavDesInfo {
return new NavDesInfo(path)
}
}
接下来完成跳转方法的封装
static goto(navInfo: NavDesInfo, back?: (popInfo: PopInfo) => void) {
NavHelper.getPathStack().pushDestination({
name: navInfo.path,
param: navInfo.param,
onPop: (result: PopInfo) => {
back?.(result)
}
}, { animated: navInfo.anime, launchMode: navInfo.launchMode })
}
携带参数返回
以pop返回方法为例,该方法所需要的参数只有返回参数以及是否需要转场动画。
参数:
参数名 | 类型 | 必填 | 说明 |
---|---|---|---|
result | Object | 是 | 页面自定义处理结果。不支持boolean类型。 |
animated | boolean | 否 | 是否支持转场动画,默认值:true。 |
封装后的返回方法:
/**
* 返回
* @param param 返回所携带的参数
* @param animated 是否启用转场动画
*/
static back(param?: Object, animated: boolean = true) {
NavHelper.getPathStack().pop(param, animated)
}
路由拦截
在我们进行页面跳转之前我们还期望可以对跳转内容进行拦截并预处理,接下来开始封装路由拦截内容。我们可以使用最常见的责任链模式来设计路由拦截器
整个路由拦截器涉及到这几个类和接口:NavInterceptManager
NavInterceptChain
INavIntercept
以及实现INavIntercept的实现类。
NavInterceptManager
类在应用初始化时注册拦截器,并维护INavIntercept数组:
import { NavDesInfo } from "../model/NavDesInfo";
import { INavIntercept } from "./INavIntercept";
import { NavInterceptChain } from "./NavInterceptChain";
export class NavInterceptManager {
private static list: INavIntercept[] = []
//注册拦截器
static registerIntercept(intercept: INavIntercept) {
NavInterceptManager.list.push(intercept)
}
//进行拦截返回路由信息
static startIntercept(navInfo: NavDesInfo): NavDesInfo {
const chain = new NavInterceptChain(NavInterceptManager.list)
return chain.process(navInfo)
}
}
NavInterceptChain
类实现对拦截器的遍历和传递,返回路由信息。
import { NavDesInfo } from "../model/NavDesInfo";
import { INavIntercept } from "./INavIntercept";
export class NavInterceptChain {
private list: INavIntercept[] = []
private index: number = 0
constructor(list: INavIntercept[], index: number = 0) {
this.list = list;
this.index = index
}
process(navInfo: NavDesInfo): NavDesInfo {
//遍历完成结束
if (this.index >= this.list.length) {
return navInfo
}
const next: NavInterceptChain = new NavInterceptChain(this.list, this.index + 1)
const intercept: INavIntercept = this.list[this.index]
//异常处理
if (!intercept) {
return navInfo
}
//标志位变化终止传递
if (navInfo.intercept) {
return navInfo
}
//向下传递
return intercept.process(navInfo, next)
}
}
INavIntercept
接口声明处理方法process,接收路由信息对象和chain对象
import { NavDesInfo } from "../model/NavDesInfo";
import { NavInterceptChain } from "./NavInterceptChain";
export interface INavIntercept {
process(navInfo: NavDesInfo, chain: NavInterceptChain): NavDesInfo
}
NavHelper
....
static goto(navInfo: NavDesInfo, back?: (popInfo: PopInfo) => void) {
//路由拦截
const info: NavDesInfo = NavInterceptManager.startIntercept(navInfo)
if (info.intercept) {
return
}
....
}
在路由信息对象NavDesInfo
中声明字段 intercept 来判断是否需要处理,接下来在Ability中先注册并且路由方法中进行拦截。
假设我们现在需要登陆拦截,我们要进行以下操作:
创建并且注册拦登录截器
LoginIntercept
import { LOGIN_PAGE } from "../../constant/NavConstant";
import { NavDesInfo } from "../../model/NavDesInfo";
import { INavIntercept } from "../INavIntercept";
import { NavInterceptChain } from "../NavInterceptChain";
export class LoginIntercept implements INavIntercept {
process(navInfo: NavDesInfo, chain: NavInterceptChain): NavDesInfo {
if (!AppStorage.get('is_login')) {
navInfo.path = LOGIN_PAGE
}
return chain.process(navInfo)
}
}
EntryAbility
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
AppStorage.set('is_login', false)//默认未登录状态
NavInterceptManager.registerIntercept(new LoginIntercept())
}
添加登录页面:
import { NavHelper } from "navhelper"
@Builder
export function LoginPageBuilder() {
LoginPage()
}
@Component
export struct LoginPage {
build() {
NavDestination() {
Column() {
Text('Login Page')
Button("login").onClick(() => {
//模拟登陆
AppStorage.setOrCreate('is_login', true)
NavHelper.back()
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}
}
完善MinePage和Index代码:
Index
struct Index {
...
build() {
...
Button(..)
.onClick(() => {
//携带参数跳转至我的页面
NavHelper.goto(NavInfoFactory.create(MINE_PAGE).addParam('from Index Page'), (info: PopInfo) => { //页面返回携带的信息
if (typeof info.result == 'string') {
this.backMsg = info.result
}
})
})
}
最终效果:
结尾
从使用Router在Enry到Entry间跳转,到Navigation使用navDestination属性配合动态import实现页面跳转最后现在使用Navigation配合router\_map进行页面路由,经过版本的不断更新,鸿蒙现在的路由框架也趋于成熟,此外三方的路由框架也陆续推出其中也包括了官方的路由框架HMRouter,想了解的同学也可以接入试试哦,欢迎在评论区发表更多有关路由的问题和改进方法,谢谢捏!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。