导航组件主要实现页面间以及组件内部的界面跳转,支持在不同的组件间进行参数的传递,提供灵活的跳转栈操作,从而便捷的实现对不同页面的访问和复用。
我们之前学习过Tabs组件,这个组件里面也有支持跳转的方式,Navigation与Tabs最主要的区别就在于,Navigation里面含有一个跳转栈,且使用方式比Tabs更为复杂。这个相当于Android中Fragment的Navigation,异曲同工。下文中我们将会习得
- 导航组件的显示模式
- 导航组件的路由操作
- 子页面管理
- 跨包跳转
- 跳转动效
想要支持Navigation的栈式维护和跳转,需要在页面的build函数下,将Navigation
作为根节点,也就是视图的根容器!要不然不行。并且,其跳转到的页面的根容器,一定要是NavDestination
,否则即使跳转过去了,您的页面也是没有被渲染的,Navigation组件本身也提供了布局的结构,也可以供您进行设置。
Navigation体系的界面有两大部分构成
- 主页, 主页是所有导航的源头处,引发的第一个导航。我们的路由栈(
NavPathStack
)也是在这个入口处进行设置,并由子页一层一层传递始终能够拿到这个句柄,来进行导航的。 - 子页, 就是被导航的页面。 子页也可以导航到其他的页面,主要就是得拿到那个唯一的路由栈(
NavPathStack
)。
主页与子页之间的导航器,可以配合由 Provide 和 Consume 两个装饰器来进行传递,之后有案例,可以在代码中注意一下使用方式。
主页与子页内容构成图
未加任何复杂操作的主页(主要展示标题,菜单栏,工具栏的展示方式)
@Entry
@Component
struct NavigatePage {
@State message: string = 'Hello World';
@State TooTmp:ToolbarItem = {
value: 'func', icon: $r('app.media.ic_ok')
}
// 用这个@Provide装饰器装饰的变量,可以传递给子组件
@Provide('pageInfos') pageInfos: NavPathStack = new NavPathStack()
build() {
Column(){
Navigation(this.pageInfos) {
}
//设置为分栏模式
.mode(NavigationMode.Stack)
//设置顶部标题
.title("主标题")
// 设置顶部菜单栏
.menus([
{
value: "菜单1", icon: $r('app.media.ic_icon'), action: () => {
}
},
{
value: "菜单2", icon: $r('app.media.ic_icon'), action: () => {
}
},
{
value: "菜单3", icon: $r('app.media.ic_icon'), action: () => {
}
},
{
value: "菜单4", icon: $r('app.media.ic_icon'), action: () => {
}
},
{
value: "菜单5", icon: $r('app.media.ic_icon'), action: () => {
}
}
])
//设置底部工具栏
.toolbarConfiguration([this.TooTmp, this.TooTmp, this.TooTmp])
}
.height('100%')
.width('100%')
.backgroundColor('#F1F3F5')
}
}
界面设置基础
设置页面显示模式
在上文中的代码中,有一个属性叫 mode
, 它是用来设置页面模式的。 页面模式总共分为三种
- Stack:单栏
- Split: 分栏
- Auto:自适应
Split模式
上图中我们设置的主页面是 Stack模式,为单栏的效果。其实也可以设置分栏模式,其值设置为 Split即可。
下图为设置为分栏模式的样子,好让大家有个印象。
@Entry
@Component
struct NavigatePage {
@State message: string = 'Hello World';
@State TooTmp:ToolbarItem = {
value: 'func', icon: $r('app.media.ic_ok')
}
// 用这个@Provide装饰器装饰的变量,可以传递给子组件
@Provide('pageInfos') pageInfos: NavPathStack = new NavPathStack()
build() {
Column(){
Navigation(this.pageInfos) {
}
//设置为分栏模式
.mode(NavigationMode.Split)
//设置顶部标题
.title("主标题")
...省略其他设置代码...
}
.height('100%')
.width('100%')
.backgroundColor('#F1F3F5')
}
}
Auto模式
Auto模式是让导航页面自己计算大小,屏幕宽就双列展示,不宽就单列显示。
设置标题模式
如果您写了一个Navigation
组件,没有设置title的情况下这个title是不会显示的。但是如果设置了,从上图中你也看到了,这个标题是不是有些,太大了🥶。Navigetion组件可以通过titleMode
属性设置标题栏的模式。titleMode
属性的值为:
- Free:默认值,我试着是和Full是一样的效果,官网API没看出来啥区别。遇见区别之后再补修一下文章。
- Full:强调型标题栏,用于以及界面需要图书标题的场景.
- Mini:普通标题栏模式,用于一级页面不需要突出标题的场景。
Full模式
mini模式
设置菜单栏
菜单栏的展示行为
菜单栏位置是下方红圈中区域。
当设备处于竖屏模式下,最多展示3个图标,为横屏模式下,最多展示5个图标。这里需要注意的是,这个展示的情况是与设备所处的横竖屏状态有关。
设置菜单栏
菜单栏通过Navigation组件的menus
属性进行设置。其声明格式如下:
menus(value: Array<NavigationMenuItem> | CustomBuilder): NavigationAttribute;
其参数可以使一个数组,也可以是一个自定义构建函数。
采用数组设置menus
build() {
Column(){
Navigation(this.pageInfos) {
TextInput({placeholder: 'search'})
.width("90%")
.margin({
top: 20,
})
.backgroundColor('#FFFFFF')
List({space: 20}) {
ForEach(this.arr, (item:number) => {
ListItem() {
Text("Page" + item)
.width("100%")
.height(72)
.backgroundColor('#FFFFFF')
.borderRadius(24)
.fontSize(16)
.fontWeight(500)
.textAlign(TextAlign.Center)
.onClick(() => {
this.pageInfos.pushPath({name: "NavDestinationTitle" + item})
})
}
}, (item:number) => item.toString())
}
.width('90%')
.margin({top: 12})
}
//设置为分栏模式
.mode(NavigationMode.Stack)
.navDestination(this.pageMap)
//设置顶部标题
.title("主标题")
.titleMode(NavigationTitleMode.Full)
// 设置顶部菜单栏
.menus([
{
value: "菜单1", icon: $r('app.media.ic_icon'), action: () => {
}
},
{
value: "菜单2", icon: $r('app.media.ic_icon'), action: () => {
}
},
{
value: "菜单3", icon: $r('app.media.ic_icon'), action: () => {
}
},
{
value: "菜单4", icon: $r('app.media.ic_icon'), action: () => {
}
},
{
value: "菜单5", icon: $r('app.media.ic_icon'), action: () => {
}
}
])
//设置底部工具栏
.toolbarConfiguration([this.TooTmp, this.TooTmp, this.TooTmp])
}
.height('100%')
.width('100%')
.backgroundColor('#F1F3F5')
}
采用自定义构建函数设置menus
@Builder myMenus(){
Row(){
Text('菜单1')
Text('菜单2')
Text('菜单3')
Text('菜单4')
Text('菜单5')
}
}
build() {
Column(){
Navigation(this.pageInfos) {
TextInput({placeholder: 'search'})
.width("90%")
.margin({
top: 20,
})
.backgroundColor('#FFFFFF')
List({space: 20}) {
ForEach(this.arr, (item:number) => {
ListItem() {
Text("Page" + item)
.width("100%")
.height(72)
.backgroundColor('#FFFFFF')
.borderRadius(24)
.fontSize(16)
.fontWeight(500)
.textAlign(TextAlign.Center)
.onClick(() => {
this.pageInfos.pushPath({name: "NavDestinationTitle" + item})
})
}
}, (item:number) => item.toString())
}
.width('90%')
.margin({top: 12})
}
//设置为分栏模式
.mode(NavigationMode.Stack)
.navDestination(this.pageMap)
//设置顶部标题
.title("主标题")
.titleMode(NavigationTitleMode.Full)
// 设置顶部菜单栏
.menus(this.myMenus)
//设置底部工具栏
.toolbarConfiguration([this.TooTmp, this.TooTmp, this.TooTmp])
}
.height('100%')
.width('100%')
.backgroundColor('#F1F3F5')
}
设置工具栏
工具栏是下图中所示的位置,如果不进行设置,就不会展示。工具栏的设置由toolBarConfiguration
属性进行设置。
toolBarConfiguration的声明如下文所示:
toolbarConfiguration(value: Array<ToolbarItem> | CustomBuilder, options?: NavigationToolbarOptions): NavigationAttribute;
其参数可以是一个数组,也可以是一个自定义构建函数。具体写法与menus非常类似。
@State TooTmp:ToolbarItem = {
value: 'func', icon: $r('app.media.ic_ok')
}
build() {
Column(){
Navigation(this.pageInfos) {
TextInput({placeholder: 'search'})
.width("90%")
.margin({
top: 20,
})
.backgroundColor('#FFFFFF')
List({space: 20}) {
ForEach(this.arr, (item:number) => {
ListItem() {
Text("Page" + item)
.width("100%")
.height(72)
.backgroundColor('#FFFFFF')
.borderRadius(24)
.fontSize(16)
.fontWeight(500)
.textAlign(TextAlign.Center)
.onClick(() => {
this.pageInfos.pushPath({name: "NavDestinationTitle" + item})
})
}
}, (item:number) => item.toString())
}
.width('90%')
.margin({top: 12})
}
//设置为分栏模式
.mode(NavigationMode.Stack)
.navDestination(this.pageMap)
//设置顶部标题
.title("主标题")
.titleMode(NavigationTitleMode.Full)
// 设置顶部菜单栏
.menus(this.myMenus)
//设置底部工具栏
.toolbarConfiguration([this.TooTmp, this.TooTmp, this.TooTmp])
}
.height('100%')
.width('100%')
.backgroundColor('#F1F3F5')
}
路由操作
我们在Navitgation构建的时候,传递了一个参数。如图所示:
上图中NavPathStack
是我们这个小节学习的重点,这个对象必须在Navigation构建的时候传入。Navigation整个体系下所有和路由相关的操作都是由NavPathStack
提供的方法进行,这个对象主要就是用于页面的管理。其功能主要如下:
- 页面跳转
- 页面返回
- 页面替换
- 页面删除
- 参数获取,数据传递。
- 路由拦截
- ...
我们在页面进行操作之前,是有必要稍微了解下,导航组件完成完整功能的构成成分。以及与我们之前写的代码的对应部分(红色虚线)。好在心里有个蓝图。
页面跳转的一切基础,就是首先Navigation对应的页面栈NavPathStack
您得提供。这个对象会在Navigation初始化的时候被传入,您可以用ArkUI中已经提供的类,也可以自己写一个子类,创建一个对象实例,传入即可。
@Entry
@Component
struct Index {
// 创建一个页面栈对象并传入Navigation
pageStack: NavPathStack = new NavPathStack()
build() {
Navigation(this.pageStack) {
}
.title('主标题')
}
}
页面跳转
NavPathStack
通过Push相关的接口去实现页面跳转的功能,主要分为三类
- 普通跳转
- 带返回回调的跳转
- 带错误码的跳转,跳转结束会触发异步回调,返回错误码信息。
普通跳转
this.pageInfos.pushPath({name: "NavDestinationTitle" + item, param: ''})
this.pageInfos.pushPathByName("NavDestinationTitle" + item, '')
带返回回调的跳转
跳转时可以添加onPop回调,能在页面出栈时获取返回信息。不知道为什么我自己写的不会返回。这个我调试的表现跟官网不一样。
this.pageInfos.pushPathByName("NavDestinationTitle" + item, '', (popInfo) => {
hilog.info(DOMAIN_NAVIGATE, TAG, 'Pop page name is: ' + popInfo.info.name + ', result: ' + JSON.stringify(popInfo.result))
})
带错误码的跳转
跳转结束触发异步回调,返回错误码信息。当被导航的界面打开的时候就会回调success
this.pageInfos.pushDestination({name: "NavDestinationTitle" + item, param: ''})
.catch((error: BusinessError) => {
hilog.error(DOMAIN_NAVIGATE, TAG, 'error Name= ' + error.name + " code=" + error.code + " message=" + error.message + " data=" + error.data)
}).then(()=> {
hilog.info(DOMAIN_NAVIGATE, TAG, "open success")
})
或者这样写也可以
this.pageInfos.pushDestinationByName( "NavDestinationTitle" + item, '')
.catch((error: BusinessError) => {
hilog.error(DOMAIN_NAVIGATE, TAG, 'error Name= ' + error.name + " code=" + error.code + " message=" + error.message + " data=" + error.data)
}).then(()=> {
hilog.info(DOMAIN_NAVIGATE, TAG, "open success")
})
})
页面返回
NavPathStack通过pop相关接口去实现页面返回功能。
@Component
export struct PageOne {
@State message: string = 'PageOne';
@Consume('pageInfos') pageStack: NavPathStack
build() {
// 注意这里必须加上NavDestination方法,否则导航有效果但是界面展示不出来.
NavDestination(){
RelativeContainer() {
Text(this.message + ' content')
.id('PageOne')
.fontSize($r('app.float.page_text_font_size'))
.fontWeight(FontWeight.Bold)
.alignRules({
center: { anchor: '__container__', align: VerticalAlign.Center },
middle: { anchor: '__container__', align: HorizontalAlign.Center }
})
.onClick(() => {
this.message = 'Welcome';
})
}
.height('100%')
.width('100%')
.backgroundColor(Color.Red)
}
.title(this.message)
.onBackPressed(()=>{
const popInfo = this.pageStack.pop(); //弹出栈顶元素
console.log('pop' + '返回值' + JSON.stringify(popInfo))
return true;
})
}
}
另外还有几个其他的接口
// 返回到上一页
this.pageStack.pop()
//返回到上一个pageOne页
this.pageStack.popToName('pageOne')
// 返回到索引为1的页面
this.pageStack.popToIndex(1)
// 返回到根首页,也就是主页
this.pageStack.clear()
页面替换
NavPathStack通过Replace相关接口去实现页面的替换功能。
.onBackPressed(()=>{
// 注意此时的name,名称要是在RouterMap中注册的那些文字,否则找不到要替换哪一个的
const info = this.pageStack.replacePath({name: "NavDestinationTitle1", param: ""})
console.log('pop' + '返回值' + JSON.stringify(info))
return true;
})
// 将栈顶页面替换为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");
移动页面
移动页面指的是,移动栈中的某指定页面到栈顶的位置。
// 移动栈中name为PageOne的页面到栈顶
this.pageStack.moveToTop("PageOne");
// 移动栈中索引为1的页面到栈顶
this.pageStack.moveIndexToTop(1);
获取参数
NavPathStack中的获取参数是比较有意思的,因为它可以按照页面获取参数,比如您现在在pageTwo中,也是可以搞到pogeOne中的参数的。这个就会数据中心的实现提供了很多的便利。
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 | 单双栏显示发生变化时回调,主要是横竖屏切换的时候。 |
下方代码为,当跳转到pageTwo的时候,我们拦截,使其转向pageOne。
aboutToAppear(): void {
this.pageInfos.setInterception({
/**
* willShow需要格外注意的是, 当A跳转至B, B又跳转至A的时候, A的willShow也会被调用的,因为A的确是即将被展示了.
* 我们在理逻辑的时候,除了注意将要跳转的界面,也要注意将要返回的界面.
*/
willShow: (from: NavDestinationContext | NavBar, to: NavDestinationContext | NavBar, operation: NavigationOperation, isAnimated: boolean) => {
// 当为导航主页的时候,这里的to是一个string类型,内容为 "navBar"
if (typeof to === 'string') {
let toStr:string = to as string
hilog.warn(DOMAIN_NAVIGATE, TAG, 'navigation home page! to=' + to)
return
}
// 当为子导航页的时候,这里的to是NavDestinationContext类型
// 修改操作栈,发现是PageTwo的跳转,就改成PageOne
let target: NavDestinationContext = to as NavDestinationContext
hilog.info(DOMAIN_NAVIGATE, TAG, 'common page! to=' + JSON.stringify(target))
if (target.pathInfo.name === 'NavDestinationTitle2') {
target.pathStack.pop()
target.pathStack.pushPath({name: 'NavDestinationTitle1'})
}
},
didShow: (from: NavDestinationContext | NavBar, to: NavDestinationContext | NavBar, operation: NavigationOperation, isAnimated: boolean) => {
hilog.debug(DOMAIN_NAVIGATE, TAG, "didShow callback")
},
modeChange: (mode: NavigationMode) => {
hilog.debug(DOMAIN_NAVIGATE, TAG, "modeChange callback")
}
})
}
子页面
子页面对应的是NavDestination
组件,是Navigation体系中子页面部分的根容器,用于承载子页面的一些特殊属性和生命周期等。它可以设置独立的菜单栏和标题栏等属性,这类属性的设置方法与Navigation是一致的。
NavDestination的页面显示模式
NavDestination设置页面显示模式的属性,也叫 mode
。通过设置mode属性,可以满足不同页面的诉求。
页面显示类型
- 标准类型:NavDestination的默认显示模式就是标准类型,此时mode = NavDestinationMode.STANDARD 。 标准类型的子页面生命周期跟随在NavPathStack页面栈中的位置变化而变化。
- 弹窗类型:NavDestination设置mode为 NavDestinationMode.DIALOG类型时,代表当前子页面是弹窗类型,此时整个NavDestination默认透明显示,并且它的消失和出现并不会影响下层标准类型的显示和生命周期,两者是可以同时显示的。 弹窗默认动效是从低向上平滑弹出。
@Preview
@Component
export struct PageFourDialog {
@Consume('pageInfos') pageStack: NavPathStack
build() {
NavDestination(){
RelativeContainer() {
RelativeContainer(){
Text("这是一个弹窗")
.id('title')
.fontSize(20)
.alignRules({
top: {anchor: '__container__', align: VerticalAlign.Top},
start: {anchor: '__container__', align: HorizontalAlign.Start},
end: {anchor: '__container__', align: HorizontalAlign.End}
})
.margin({top: 80})
.fontWeight(FontWeight.Bold)
.textAlign(TextAlign.Center)
Button("退出")
.fontSize(18)
.alignRules({
top: {anchor: 'title', align: VerticalAlign.Bottom},
start: {anchor: '__container__', align: HorizontalAlign.Start},
end: {anchor: '__container__', align: HorizontalAlign.End}
})
.width('30%')
.margin({top: 50})
.onClick(()=>{
this.pageStack.pop()
})
}
.alignRules({
top: {anchor: '__container__', align: VerticalAlign.Top},
start: {anchor: '__container__', align: HorizontalAlign.Start},
end: {anchor: '__container__', align: HorizontalAlign.End},
bottom: {anchor: '__container__', align: VerticalAlign.Bottom}
})
.height('30%')
.width('80%')
.backgroundColor(Color.White)
.borderRadius(20)
}
.height('100%')
.width('100%')
.borderRadius(20)
}
.hideTitleBar(true) //关掉标题栏区域
.backgroundColor('rgba(0, 0, 0, 0.3)')
.mode(NavDestinationMode.DIALOG) //指定为弹窗模式
}
}
页面生命周期
页面监听和查询
为了方便组件与页面解耦,在NavDestination子页面定义的自定义组件可以通过全局方法监听或查询到页面的一些状态信息。
页面信息查询
自定义组件提供了quertNavDestinationInfo
方法。可以在NavDestination内部查询到当前页面的信息,返回值是NavDestination
,如果查询不到则返回undefined。
import { JSON } from "@kit.ArkTS"
@Preview
@Component
export struct PageFourDialog {
@Consume('pageInfos') pageStack: NavPathStack
build() {
NavDestination(){
RelativeContainer() {
RelativeContainer(){
Text("这是一个弹窗")
.id('title')
.fontSize(20)
.alignRules({
top: {anchor: '__container__', align: VerticalAlign.Top},
start: {anchor: '__container__', align: HorizontalAlign.Start},
end: {anchor: '__container__', align: HorizontalAlign.End}
})
.margin({top: 80})
.fontWeight(FontWeight.Bold)
.textAlign(TextAlign.Center)
BackButton()
}
.alignRules({
top: {anchor: '__container__', align: VerticalAlign.Top},
start: {anchor: '__container__', align: HorizontalAlign.Start},
end: {anchor: '__container__', align: HorizontalAlign.End},
bottom: {anchor: '__container__', align: VerticalAlign.Bottom}
})
.height('30%')
.width('80%')
.backgroundColor(Color.White)
.borderRadius(20)
}
.height('100%')
.width('100%')
.borderRadius(20)
}
.hideTitleBar(true) //关掉标题栏区域
.backgroundColor('rgba(0, 0, 0, 0.3)')
.mode(NavDestinationMode.DIALOG) //指定为弹窗模式
}
}
@Component
struct BackButton{
@Consume('pageInfos') pageStack: NavPathStack
navDInfo:NavDestinationInfo | undefined;
aboutToAppear(): void {
// navDInfo = {"navigationId":"","name":"NavDestinationTitle4","state":4,
// "index":0,"param":"NavDestinationTitle4","navDestinationId":"0"}
this.navDInfo = this.queryNavDestinationInfo();
console.log("navDInfo = " + JSON.stringify(this.navDInfo))
}
build() {
Button("退出")
.fontSize(18)
.alignRules({
top: {anchor: 'title', align: VerticalAlign.Bottom},
start: {anchor: '__container__', align: HorizontalAlign.Start},
end: {anchor: '__container__', align: HorizontalAlign.End}
})
.width('30%')
.margin({top: 50})
.onClick(()=>{
this.pageStack.pop()
})
}
}
页面状态监听
通过observer.on('navDestinationUpdate')提供的注册接口可以注册NavDestination生命周期变化的监听,使用方式如下:
import { JSON } from "@kit.ArkTS"
import { uiObserver } from "@kit.ArkUI"
@Preview
@Component
export struct PageFourDialog {
@Consume('pageInfos') pageStack: NavPathStack
aboutToAppear(): void {
uiObserver.on('navDestinationUpdate', (data: NavDestinationInfo)=>{
console.log("navDestinationUpdate data=" + JSON.stringify(data))
})
}
...
监听切换页面的行为
aboutToAppear(): void {
uiObserver.on('navDestinationUpdate', (data: NavDestinationInfo)=>{
console.log("navDestinationUpdate data=" + JSON.stringify(data))
})
// 页面切换状态回调
uiObserver.on('navDestinationSwitch', this.getUIContext(), (data:uiObserver.NavDestinationSwitchInfo) =>{
console.log('navDestinationSwitch data=' + JSON.stringify(data))
})
}
页面转场
Navigation默认提供了页面的转场动画,通过页面栈操作时会触发不同的转场效果。如果不想要,可以设置关闭系统转场动画,同时您也可以进行自定义转场,以及共享元素转场的能力。
关闭转场
关闭转场有关闭全局转场和开启子页面时临时设置关闭转场动画。首先将关闭全局的转场。用到了NavPathStack的disableAnimation
接口。
aboutToAppear(): void {
this.pageInfos.disableAnimation(true)
}
当设置关闭动画的时候,转场会显得很突兀🙃。根据特定需求谨慎设置吧。
临时设置关闭转场-单次关闭
NavPathStack中提供的Push, Pop, Replace等接口中可以设置animated
参数,默认为true,表示带有转场动画,如果您需要这次操作中没有动画,则设置为false即可。
pageStack: NavPathStack = new NavPathStack()
this.pageStack.pushPath({ name: "PageOne" }, false)
this.pageStack.pop(false)
跨包动态路由
我们首先看看之前写的代码, 在主导航页的时候,顶部出现了子页的import语句:
这种方式我们称之为静态路由跳转,这种方式能完成功能,但是存在缺陷
- 耦合过多,如果页面不出现这些import语句,在编译执行的时候,就不用处理这些连带的资源。
- 正是因为上个原因,会导致首页加载时间过长。
动态路由设计的初衷旨在解决多模块(HAR/HSP)能够复用相同的逻辑,实现业务模块之间的解耦,同时支持路由功能的扩展与整合。
系统路由表
上文提到过,动态路由是要解决多模块复用,解耦。系统路由表借助了Stage模型配置文件机制,在配置文件中添加路由表信息。这样的话,我们在配置文件中录入的就是路由表信息,也就是表示路由信息的字符串配置。之后代码中打开页面,仅仅指出路由表中配置的那些信息,系统就会找解析好的配置信息,动态调用配置好的方法。这个时候再引入上文中提到的那些静态资源也不迟。从而实现了页面懒加载。
让配置文件知道路由表的存在
很简单,在每个模块的module.json5 添加您的路由表配置即可。 module.json5是Stage开发模型中定义好的配置文件,每一个模块在创建之初都会新建一个这样的文件。
{
"module": {
"name": "entry",
"type": "entry",
"description": "$string:module_desc",
"mainElement": "EntryAbility",
"deviceTypes": [
"phone"
],
"deliveryWithInstall": true,
"installationFree": false,
"pages": "$profile:main_pages", //这个是我们设置Pages的配置
"routerMap": "$profile:router_map", //这个是我们路由导航的文件
"abilities": [
...
],
}
}
创建路由文件
很简单,看您模块mian_pages页面在哪里放着,我们的router_map文件就在哪里放着。因为就是同样的套路,连引用方式都是一样的,都是$profile:xxxx
找到的。创建一个新文件如下所示:
完善路由配置文件
{
"routerMap" : [
{
"name": "PageOne",
"pageSourceFile": "src/main/ets/pages/PageOne.ets",
"buildFunction": "open",
"data": {
"description": "this is PageOne"
}
}
]
}
- name: 跳转页面名称
- pageSourceFile: 跳转目标页的包内路径,相对于src的路径。
- buildFunction: 跳转目标页的入口函数名称,必须要用@Builder 装饰器修饰,且位置位于所指文件顶层。才能找到
- data: 应用自定义字段,可以通过配置项读取接口,
getConfigRouteMap
获取
在代码文件中补全配置文件中提到的入口函数
跳转代码
最后将原来文件中那些没必要的import, 设置RoutMap属性的代码都删掉。就完成解耦了。
自定义路由表
链接中是自定义路由表的实现方式。
参考文章:
CommonAppDevelopment/common/routermodule/README_AUTO_GENERATE.md · HarmonyOS-Cases/Cases - Gitee.com
文档中心