Navigation是路由容器组件,一般作为首页的根容器,包括单栏(Stack)、分栏(Split)和自适应(Auto)三种显示模式。Navigation组件适用于模块内和跨模块的路由切换,一次开发,多端部署场景。通过组件级路由能力实现更加自然流畅的转场体验,并提供多种标题栏样式来呈现更好的标题和内容联动效果。在不同尺寸的设备上,Navigation组件能够自适应显示大小,自动切换分栏展示效果。
个人认为项目用的相对不是很多,一般首页使用
一次开发,多端部署场景 很香 不过也可换其他内置组件复现
https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/ts-container-tabcontent-V5#%E7%A4%BA%E4%BE%8B2
1 Navigation
@Entry @Component struct Index { build() { Navigation() { } .title("主标题") .mode() .toolbarConfiguration() .menus() } }常用属性
title 设置标题
显示在这个主标题的位置
mode 设置模式
Navigation是路由容器组件,一般作为首页的根容器,包括单栏(Stack)、分栏(Split)和自适应(Auto)三种显示模式.Navigation组件适用于模块内和跨模块的路由切换,一次开发,多端部署场景
Navigation组件通过mode属性设置页面的显示模式。自适应模式下,当页面宽度大于等于一定阈值( API version 9及以前:520vp,API version 10及以后:600vp )时,Navigation组件采用分栏模式,反之采用单栏模式。
将mode属性设置为NavigationMode.Stack,Navigation组件即可设置为单页面显示模式。
toolbarConfiguration 底层标题栏
.toolbarConfiguration() 里面设置的是一个数组,数组里面每个元素ToolbarItem类型,数据的结构如下
{ value: "我的", icon: "https://s1.aigei.com/src/img/png/e7/e79cacc5161a4a6ca589e097f87a2526.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:TLXitIEF_QaGqfeKVbpaGb8_qyQ=", action: () => { }, activeIcon: "", status: ToolbarItemStatus.DISABLED, }value 就是显示的文字
icon 显示的图标
action 当点击时触发做某些事情,例如跳转路由
activeIcon 激活时的图标
status 当前图标的状态,例如不可用等等
menus 菜单栏
菜单栏位于Navigation组件的右上角,开发者可以通过menus属性进行设置。menus支持Array<NavigationMenuItem>和CustomBuilder两种参数类型。使用Array<NavigationMenuItem>类型时,竖屏最多支持显示3个图标,横屏最多支持显示5个图标,多余的图标会被放入自动生成的更多图标。
{ value: "我的", icon: "https://s1.aigei.com/src/img/png/e7/e79cacc5161a4a6ca589e097f87a2526.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:TLXitIEF_QaGqfeKVbpaGb8_qyQ=", action: () => { }, isEnabled:false }value 就是显示的文字
icon 显示的图标
action 当点击时触发做某些事情,例如跳转路由
isEnabled 是否可用
titleMode 标题栏模式
标题栏在界面顶部,用于呈现界面名称和操作入口,Navigation组件通过titleMode属性设置标题栏模式
Mini模式
普通型标题栏,用于一级页面不需要突出标题的场景
Full模式
强调型标题栏,用于一级页面需要突出标题的场景。
@Entry @Component struct Index { build() { Navigation() { } .title("主标题") .mode(NavigationMode.Stack) .titleMode(NavigationTitleMode.Free) .toolbarConfiguration([ { value: "我的", icon: "https://s1.aigei.com/src/img/png/e7/e79cacc5161a4a6ca589e097f87a2526.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:TLXitIEF_QaGqfeKVbpaGb8_qyQ=", action: () => { }, }, { value: "购物车", icon: "https://s1.aigei.com/src/img/png/5d/5dc2f07ba8224cd4883648c9ea939ac5.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:Q5dD_6ZS_ry4kaVXHgv6gnKw2IQ=", action: () => { } }, { value: "更多", icon: "https://s1.aigei.com/src/img/png/03/03d71ede7b404e70838f3d575b31a930.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:ABookBJ6PVBeSxtM2hEuQHPlGLE=", action: () => { } } ]) .menus([ { value: "设置", icon: "https://s1.4sai.com/src/img/png/6c/6ca77e26c31548e680b784ab33f72900.png?e=1735488000&token=1srnZGLKZ0Aqlz6dk7yF4SkiYf4eP-YrEOdM1sob:9x9bgI9kDs15UsWPzGN7ZuaICXM=", action: () => { }, }, { value: "小红书", icon: "https://s1.aigei.com/src/img/png/3e/3e3bda0ac48046b08e560e8764942bd8.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:JPuRl9bkD4UfIc8fGN4ApLuS_rE=", action: () => { } }, { value: "微信", icon: "https://s1.aigei.com/src/img/png/ef/efd714270fdd44f485156053901ecb51.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:GfcdaqAj6Uie-yCw4Wt1pPqAGwg=", action: () => { } }, { value: "抖音", icon: "https://s1.aigei.com/src/img/png/5b/5b26e982f0b34c47817d3b40c9bf2d1f.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:YCO6IvUtFIqf6x1hmy82VctIElo=", action: () => { } } ]) } }2 NavRouter
导航组件,默认提供点击响应处理,不需要开发者自定义点击事件逻辑。
必须包含两个子组件,其中第二个子组件必须为NavDestination。
其中第一个组件是需要点击的元素
第二个必须是NavDestination
@State list:Array<number>=[1,2,3] Navigation(this.pageStack) { ForEach(this.list, (item: number) => { NavRouter() { // 这里是点击的内容 Column() { Text("文本" + item) .width("85%") .height(60) .backgroundColor("#ccc") .borderRadius(25) .margin({ top: 10 }) .textAlign(TextAlign.Center) } // 这里是跳转显示的页面 NavDestination() { Text("文本" + item) .fontSize(30) .fontColor("red") }.title("标题" + item) }.mode(NavRouteMode.PUSH) }) }这里有一个属性
mode(mode: NavRouteMode)
设置指定点击NavRouter跳转到NavDestination页面时,使用的路由模式
PUSH_WITH_RECREATE 跳转到新的NavDestination页面时,替换当前显示的NavDestination页面,页面销毁,但该页面信息仍保留在路由栈中。
PUSH 跳转到新的NavDestination页面时,覆盖当前显示的NavDestination页面,该页面不销毁,且页面信息保留在路由栈中。
REPLACE 跳转到新的NavDestination页面时,替换当前显示的NavDestination页面,页面销毁,且该页面信息从路由栈中清除。
还有一个事件
onStateChange(callback: (isActivated: boolean) => void)
组件激活状态切换时触发该回调。开发者点击激活NavRouter,加载对应的NavDestination子组件时,回调onStateChange(true)。NavRouter对应的NavDestination子组件不再显示时,回调onStateChange(false)。
最后效果
@Entry @Component struct Index { pageStack: NavPathStack = new NavPathStack(); @State list: Array<number> = [1, 2, 3] build() { Navigation(this.pageStack) { ForEach(this.list, (item: number) => { NavRouter() { Column() { Text("文本" + item) .width("85%") .height(60) .backgroundColor("#ccc") .borderRadius(25) .margin({ top: 10 }) .textAlign(TextAlign.Center) } NavDestination() { Text("文本" + item) .fontSize(30) .fontColor("red") }.title("标题" + item) }.mode(NavRouteMode.PUSH) .onStateChange((isActivated: boolean) => { console.log(item + "激活:" + isActivated) }) }) } .title("主标题") .mode(NavigationMode.Stack) .titleMode(NavigationTitleMode.Free) .toolbarConfiguration([ { value: "我的", icon: "https://s1.aigei.com/src/img/png/e7/e79cacc5161a4a6ca589e097f87a2526.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:TLXitIEF_QaGqfeKVbpaGb8_qyQ=", action: () => { this.pageStack.pushPath({ name: "pageOne", param: "aaa" }) }, }, { value: "购物车", icon: "https://s1.aigei.com/src/img/png/5d/5dc2f07ba8224cd4883648c9ea939ac5.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:Q5dD_6ZS_ry4kaVXHgv6gnKw2IQ=", action: () => { } }, { value: "更多", icon: "https://s1.aigei.com/src/img/png/03/03d71ede7b404e70838f3d575b31a930.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:ABookBJ6PVBeSxtM2hEuQHPlGLE=", action: () => { } } ]) .menus([ { value: "设置", icon: "https://s1.4sai.com/src/img/png/6c/6ca77e26c31548e680b784ab33f72900.png?e=1735488000&token=1srnZGLKZ0Aqlz6dk7yF4SkiYf4eP-YrEOdM1sob:9x9bgI9kDs15UsWPzGN7ZuaICXM=", action: () => { }, }, { value: "小红书", icon: "https://s1.aigei.com/src/img/png/3e/3e3bda0ac48046b08e560e8764942bd8.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:JPuRl9bkD4UfIc8fGN4ApLuS_rE=", action: () => { } }, { value: "微信", icon: "https://s1.aigei.com/src/img/png/ef/efd714270fdd44f485156053901ecb51.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:GfcdaqAj6Uie-yCw4Wt1pPqAGwg=", action: () => { } }, { value: "抖音", icon: "https://s1.aigei.com/src/img/png/5b/5b26e982f0b34c47817d3b40c9bf2d1f.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:YCO6IvUtFIqf6x1hmy82VctIElo=", action: () => { } } ]) } }3 独立的NavDestination
1、Page1.ets 把页面内容NavDestination独立出去
@Component export default struct Page1 { build() { NavDestination() { Column() { Text("实际的内容") } } .title("标题") } }2、Navigation通过属性关联
import Page1 from './Page1' @Entry @Component export struct Index { @Builder PagesMap(name: string) { if (name === "Page1") { Page1() } } build() { Navigation() { } .title("主标题") .mode(NavigationMode.Stack) .navDestination(this.PagesMap) // 通过属性的方式动态设置 } }3、控制跳转(之前没有独立NavRouter 独立之后必须通过Navigation提供的页面栈管理实例来操作
3.1 创建页面栈 pageStack: NavPathStack = new NavPathStack(); // 创建页面栈实例化对象 控制页面栈路由增删改等 3.2 注册关联 Navigation(this.pageStack) { 3.3 跳转 https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/arkts-navigation-navigation-V5#%E8%B7%AF%E7%94%B1%E6%93%8D%E4%BD%9C最终代码
import Page1 from './Page1' @Entry @Component export struct Index { pageStack: NavPathStack = new NavPathStack(); // 创建页面栈实例化对象 控制页面栈路由增删改等 @Builder PagesMap(name: string) { if (name === "Page1") { Page1() } } build() { Navigation(this.pageStack) { // 给Navigation注入pageStack实例 // Navigation() { Button("按钮").onClick(() => { // router.push this.pageStack.pushPath({ name: "Page1", param: "实际需要显示的内容" }) }) } .title("主标题") .mode(NavigationMode.Stack) .navDestination(this.PagesMap) // 通过属性的方式动态设置 } }如果要设置独立的NavDestination来完成不同的页面可以通过下面的步骤
import Test1 from './Test1' @Entry @Component export struct Index { pageStack: NavPathStack = new NavPathStack(); @Builder PagesMap(name: string) { if (name === "Test1") { Test1() } } build() { Navigation(this.pageStack) { Button("按钮").onClick(() => { this.pageStack.pushPath({ name: "Test1", param: "实际需要显示的内容" }) }) } .title("主标题") .mode(NavigationMode.Stack) .navDestination(this.PagesMap) } }Test1.ets
@Component export default struct Page1 { build() { NavDestination() { Column() { Text("实际的内容") } } .title("标题") } }NavDestination的模式
NavDestination的mode属性有两种值
标准类型 NavDestinationMode.STANDARD
标准类型的NavDestination的生命周期跟随其在NavPathStack页面栈中的位置变化而改变。
弹窗类型 NavDestinationMode.DIALOG
整个NavDestination默认透明显示。弹窗类型的NavDestination显示和消失时不会影响下层标准类型的NavDestination的显示和生命周期,两者可以同时显示。
下面处理一个弹窗问题
@Component export struct DialogPage { @Consume pageStack: NavPathStack; param: string = "" build() { NavDestination() { Column() { Stack({ alignContent: Alignment.TopEnd }) { Text(this.param) .fontSize(30) .fontColor("red") .textAlign(TextAlign.Center) .width("100%") .margin({ top: 80 }) Button("×").onClick(() => { this.pageStack.pop(); }) }.width("70%") .height("30%") .backgroundColor("white") .borderRadius(30) }.justifyContent(FlexAlign.Center) .alignItems(HorizontalAlign.Center) .width("100%") .height("100%") } .backgroundColor("rgba(0,0,0,0.5)") .mode(NavDestinationMode.DIALOG) .title("标题") .hideTitleBar(true) } }import { DialogPage } from '../components/DialogPage'; @Entry @Component struct Index { @Provide pageStack: NavPathStack = new NavPathStack(); @Builder PagesMap(name: string, param: string) { if (name === "dialogPage") { DialogPage({ param: param }) } } build() { Navigation(this.pageStack) { } .title("主标题") .mode(NavigationMode.Stack) .titleMode(NavigationTitleMode.Free) .navDestination(this.PagesMap) .toolbarConfiguration([ { value: "我的", icon: "https://s1.aigei.com/src/img/png/e7/e79cacc5161a4a6ca589e097f87a2526.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:TLXitIEF_QaGqfeKVbpaGb8_qyQ=", action: () => { this.pageStack.pushPath({ name: "dialogPage", param: "aaa" }) }, }, { value: "购物车", icon: "https://s1.aigei.com/src/img/png/5d/5dc2f07ba8224cd4883648c9ea939ac5.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:Q5dD_6ZS_ry4kaVXHgv6gnKw2IQ=", action: () => { } }, { value: "更多", icon: "https://s1.aigei.com/src/img/png/03/03d71ede7b404e70838f3d575b31a930.png?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:ABookBJ6PVBeSxtM2hEuQHPlGLE=", action: () => { } } ]) } }4 NavPathStack
NavPathStrack是页面栈为路由跳转提供的方法对象,每个Navigation都需要创建并传入一个NavPathStack对象,用于管理页面。主要涉及页面跳转、页面返回、页面替换、页面删除、参数获取、路由拦截等功能。
普通跳转
this.pageStack.pushPath({ name: "dialogPage", param: "aaa" }) this.pageStack.pushPathByName("dialogPage", "aaa")这两种方法都可以使用
带返回回调的跳转,跳转时添加onPop回调,能在页面出栈时获取返回信息
this.pageStack.pushPath({ name: "dialogPage", param: "aaa", onPop: (popInfo) => { Logger.log(popInfo.result) } })关闭退回时通过pop传回的数据
Button("×").onClick(() => { this.pageStack.pop("返回的内容") })页面返回
// 返回到上一页 this.pageStack.pop() // 返回到上一个PageOne页面 this.pageStack.popToName("PageOne") // 返回到索引为1的页面 this.pageStack.popToIndex(1) // 返回到根首页(清除栈中所有页面) this.pageStack.clear()页面替换
// 将栈顶页面替换为PageOne this.pageStack.replacePath({ name: "PageOne", param: "PageOne Param" }) this.pageStack.replacePathByName("PageOne", "PageOne Param")页面删除
// 删除栈中name为PageOne的所有页面 this.pageStack.removeByName("PageOne") // 删除指定索引的页面 this.pageStack.removeByIndexes([1,3,5])参数获取
// 获取栈中所有页面name集合 this.pageStack.getAllPathName() // 获取索引为1的页面参数 this.pageStack.getParamByIndex(1) // 获取PageOne页面的参数 this.pageStack.getParamByName("PageOne") // 获取PageOne页面的索引集合 this.pageStack.getIndexByName("PageOne")路由拦截
在页面的初始生命周期中设置
aboutToAppear(): void { this.pageStack.setInterception({ willShow: (from: NavDestinationContext | NavBar, to: NavDestinationContext | NavBar, operation: NavigationOperation, isAnimated: boolean) => { if (typeof to === "string") { return; } if (to.pathInfo.name === "dialogPage") { to.pathStack.pop(); to.pathStack.pushPath({ name: "page1", param: "bbbb" }) } } }) }5 系统路由表
在这里跨包的含义我们后面再讲
注意这部分只能使用虚拟机运行,预览器无法运行
先新建一个页面PageOne
//需要配置一个函数入口,用于routerMap中的引用 @Builder function PageOneBuilder() { PageOne(); } @Entry @Component struct PageOne { pathStack: NavPathStack = new NavPathStack() build() { NavDestination() { Column() { Text("实际的内容") } } .title('PageOne') .onReady((context: NavDestinationContext) => { // 跨包带来的环境中路由栈 this.pathStack = context.pathStack // 获取传到当前页面中的路由传参 // let param: IData = JSON.parse(this.pathStack.getParamByIndex(0) as string) as IData; let params: string[] = this.pathStack.getParamByName("PageOne") as string[] // 在routerMap中配置的静态数据 Logger.log(JSON.stringify(context.getConfigInRouteMap()?.data)) }) } }新建router_map.json
在main/resources/base/profile下面新建文件router_map.json文件
{ "routerMap": [ { "name": "PageOne", "pageSourceFile": "src/main/ets/pages/PageOne.ets", "buildFunction": "PageOneBuilder", "data": { "names": "xietian" } } ] }"name": "PageOne", 路由跳转的名字,用来在页面中通过NavPathStack对象调用pushPathByName方法找到这个路由名称
"pageSourceFile": "src/main/ets/pages/PageOne.ets", 路由跳转的实际ets文件地址
"buildFunction": "PageOneBuilder", 跳转进入的入口函数,这个函数在PageOne页面中通过@Builder定义的函数
"data": {
"names": "xietian"
}
路由传入带入的静态数据,可以当做Vue中Meta元数据
配置模块数据
在main下找到module.json5文件在module对象中添加"routerMap": "$profile:router_map",把routerMap指向创建的router_map.json
{ "module": { "name": "entry", "type": "entry", "description": "$string:module_desc", "mainElement": "EntryAbility", "deviceTypes": [ "phone", "tablet", "2in1" ], //这一句 "routerMap": "$profile:router_map", "deliveryWithInstall": true, "installationFree": false, "pages": "$profile:main_pages", "abilities": [ { "name": "EntryAbility", "srcEntry": "./ets/entryability/EntryAbility.ets", "description": "$string:EntryAbility_desc", "icon": "$media:layered_image", "label": "$string:EntryAbility_label", "startWindowIcon": "$media:startIcon", "startWindowBackground": "$color:start_window_background", "exported": true, "skills": [ { "entities": [ "entity.system.home" ], "actions": [ "action.system.home" ] } ] } ] } }主文件中做路由跳转
@Entry @Component struct Index { pageStack: NavPathStack = new NavPathStack(); build() { Column() { Navigation(this.pageStack) { Column() { Text("文本") .width("85%") .height(60) .backgroundColor("#ccc") .borderRadius(25) .margin({ top: 10 }) .textAlign(TextAlign.Center) .onClick(() => { // 跳转的路由名为配置的路由名 // this.pageStack.pushPathByName("PageOne", null, true) this.pageStack.pushPathByName("PageOne", JSON.stringify({ a: 1, b: 2 }), true) }) } } } } }欢迎加入课程班级,考取鸿蒙认证:
https://developer.huawei.com/consumer/cn/training/classDetail/d43582bb30b34f548c16c127cb3be104?type=1?ha_source=hmosclass&ha_sourceId=89000248