Navigation路由导航
使用Navigation代替所有的路由跳转
理念是:一整个应用都只有一个page,剩余其它的部分都是组件【华为推荐使用Navigation路由导航】
第一种路由方式
- 根组件最外层必须被Navigation包裹
- 要被跳转的组件必须被NavDestination包裹
- 跳转对象是用NavPathStack对象
- NavPathStack必须绑定给Navigation
- 下层组件使用各种方式获取上层的NavPathStack进行跳转
- 跳转维护由.navDestination实现
- Navigation绑定NavPathStack,最好用Provide提供给子组件使用
- 路由管理-给Navigation绑定.navDestination(builder)- builder不能写小括号模式
import { Friend } from './Friend'
import { Live } from './Live'
import { Shake } from './Shake'
@Entry
@Component
struct NavigationCase {
@Provide
stackPath: NavPathStack = new NavPathStack()
@Builder
getRouterBuilder (name: string) {
// name是要跳转的标识
if(name === 'friend') {
Friend()
}
else if(name === "live") {
Live()
}
else if(name === "shake") {
Shake()
}
}
build() {
// 1. 使用Navigation包裹整个根组件
Navigation(this.stackPath) {
List() {
ListItem(){
Text("朋友圈")
.fontSize(20)
.width("100%")
}
.height(50)
.width("100%")
.padding({
left: 20
})
.onClick(() => {
this.stackPath.pushPath({
name: 'friend'
})
})
ListItem(){
Text("直播")
.fontSize(20)
.width("100%")
}
.height(50)
.width("100%")
.padding({
left: 20
})
.onClick(() => {
this.stackPath.pushPath({
name: 'live'
})
})
ListItem(){
Text("摇一摇")
.fontSize(20)
.width("100%")
}
.height(50)
.width("100%")
.padding({
left: 20
})
.onClick(() => {
this.stackPath.pushPath({
name: 'shake'
})
})
}
.divider({
strokeWidth: 2,
color: "#ccc"
})
}
.navDestination(this.getRouterBuilder)
.mode(NavigationMode.Auto)
}
}
import { IParams } from "./NavigationCase"
@Component
export struct NavigationCase_friend {
@Consume navStackPath: NavPathStack
@State age: number = 0
@State name: string = ''
build() {
NavDestination() {
Text("朋友圈")
Button("点我返回上一页")
.onClick(() => {
this.navStackPath.pop()
})
Text(this.name + this.age)
}.onWillAppear(() => {
const params = this.navStackPath.getParamByName('friend') as IParams[]
this.age = params[params.length - 1]?.age
this.name = params[params.length - 1]?.name
AlertDialog.show({ message: JSON.stringify(params, null, 2) })
})
}
}
第二种方式:WrapBuilder的方式
- wrapBuilder可以包裹一个全局的builder,不会立刻渲染,需要直接运行的builder方法
子组件需要单独到处全局builder
WrapBuilder
wrapBuilder就是一个全局函数
会返回一个类型为WrapBuilder<[]>, 空数组中是参数的类型
map.get(name).builder()
import { FriendPageBuilder } from "./NavigationCase_friend"
import { LivePageBuilder } from "./NavigationCase_live"
import { LookPageBuilder } from "./NavigationCase_look"
export const RouterMap: Map<string, WrappedBuilder<[]>> = new Map()
RouterMap.set("look", wrapBuilder(LookPageBuilder))
RouterMap.set("live", wrapBuilder(LivePageBuilder))
RouterMap.set("friend", wrapBuilder(FriendPageBuilder))
@Component
export struct NavigationCase_look {
@Consume navStackPath: NavPathStack
build() {
NavDestination() {
Text("看一看")
Button("点我去到live")
.onClick(() => {
this.navStackPath.pushPath({ name: 'live' })
})
Button("返回到根首页")
.onClick(() => this.navStackPath.clear())
}
}
}
@Builder
export function LookPageBuilder() {
NavigationCase_look()
}
import { RouterMap } from './NavigationCase_WrapBuilder'
export interface IParams {
name: string
age: number
}
@Entry
@Component
struct NavigationCase {
@Provide navStackPath: NavPathStack = new NavPathStack()
@Builder
getBuilder(name: string) {
RouterMap.get(name)?.builder()
}
build() {
Navigation(this.navStackPath) {
List({ space: 20 }) {
ListItem() {
Text("朋友圈")
.width("100%")
.height(60)
.backgroundColor(Color.Pink)
}.onClick(() => {
this.navStackPath.pushPath({ name: 'friend', param: { name: 'aaa', age: 18 } as IParams })
})
ListItem() {
Text("直播")
.width("100%")
.height(60)
.backgroundColor(Color.Brown)
}
.onClick(() => {
this.navStackPath.pushPath({ name: 'live' })
})
ListItem() {
Text("看一看")
.width("100%")
.height(60)
.backgroundColor(Color.Orange)
}
.onClick(() => {
this.navStackPath.pushPathByName('look', null)
})
}
}
.mode(NavigationMode.Auto)
.navDestination(this.getBuilder)
}
}
第三种方式:配置型路由
- 根组件必须被Navigation包裹
- 必须绑定NavStackPath
- 子组件必须被NavDestination包裹
- 必须有一个全局的builder来渲染子组件
- 不需要再配置.navDestination
- 需要在项目的module.json5中配置一个routerMap的json文件【"routerMap": "$profile:router_map"】
- routerMap文件中配置三个内容-name-builder-文件路径
- 路由map模式只能模拟器才可以测试
{
"routerMap": [
{
"name": "main",
"buildFunction": "MainPageBuilder",
"pageSourceFile": "src/main/ets/pages/AgainNavigation/MainPage.ets"
},
{
"name": "detail",
"buildFunction": "PageDetailBuilder",
"pageSourceFile": "src/main/ets/pages/AgainNavigation/PageDetail.ets"
}
]
}
Navigation取参数 - 返回 - 回到第一页
- this.stackPath.getParamByName("当前页的name")
.onWillAppear(() => { const params = this.stackPath.getParamByName("detail") as Params[] this.age = params[params.length - 1]?.age console.log("onWillAppear") })- 如果没传参数,数组依然有长度,但是里面的内容为undefined
- 返回主页:this.stackPath.popToIndex(-1)
- 返回上一页:this.stackPath.pop()
- // 返回到上一个PageOne页面 // this.pageStack.popToName("PageOne")
- // 返回到根首页(清除栈中所有页面) // this.pageStack.clear()
- pushPath(追加)/replacePath(替换), 层级无限制
- router-32层
- pushPath({ name: '', param: 123 }), 假设要传对象要明确类型,比如 interface/class/Record/Map
- 接收参数 this.stackPath.getParamByName("xx") - xx是当前组件的name值
- 得到的参数类型是一个unknown[],强制要求开发者进行 as 具体类型[]
- 要取最后一个参数-如果不传参数,数组依然存在,只是内容是undfined
Navigation组件的一些常用属性
.title() // 标题,可以设置自定义builder
.titleMode() // 设置页面标题栏显示模式,常用属性:NavigationTitleMode.Mini
.hideTitleBar(true) // 隐藏标题栏
.mode() // 设置导航栏的显示模式,常用Stack,不分栏显示
.navDestination() // 创建NavDestination组件。使用builder函数,基于name和param构造NavDestination组件。builder下只能有一个根节点。builder中允许在NavDestination组件外包含一层自定义组件, 但自定义组件不允许设置属性和事件,否则仅显示空白。
NavDestination组件的建议
不推荐设置位置、大小等布局相关属性,可能会造成页面显示异常。
router路由跳转
路由模式
路由提供了两种不同的跳转模式,不同模式决定了页面是否会创建多个实例
Standard:多实例模式,也是默认情况下的跳转模式。目标页面被添加到页面栈顶,无论是否存在相同url的页面
Single:单实例模式,如果目标页面的url已经存在于页面栈中,则会将离栈顶最近的同url页面移动到栈顶,该页面成为新建页。如果目标页面的url在页面栈中不存在同url页面,则按照默认的多实例模式进行跳转
简而言之:
Standard:无论之前是否添加过,一直添加到页面栈【常用】【默认】
Single:如果之前加过页面,会使用之前添加的页面【看情况使用】
router.pushUrl(options, mode?) // router.pushUrl(options, router.RouterMode.Standard) router.replaceUrl(option, mode?)
页面跳转与后退
普通跳转(可返回)
router.pushUrl({ url: '页面地址' // page/Index })替换跳转(无法返回)
router.replaceUrl({ url: "页面地址" })返回
router.back()router.pushUrl()和router.replaceUrl都可以跳转页面,区别:是否会替换当前页
- router.pushUrl():目标页面不会替换当前页,而是压入页面栈。这样可以保留当前页的状态,并且可以通过返回键或者调用router.back()方法返回到当前页
- router.replaceUrl():目标页面会替换当前页,并销毁当前页。这样就可以释放当前页面的资源,并且无法返回到当前页
页面栈
页面栈是用来存储程序运行时页面的一种数据结构,遵循先进后出的原则
router方式跳转路由,页面栈的最大容量为32个页面
router.clear() // 清空页面栈 router.getLength() // 范湖页面栈当前的长度 router.getState() // 获取页面状态 let page = router.getState() console.log("current Index = " + page.index) console.log("current name = " + page.name) console.log("current path = " + page.path)
参数
pushUrl
router.pushUrl({ url: "地址", params: { // 以对象的形式传递参数 } }) router.getParams() as object // 接收路由参数replaceUrl
router.replaceUrl({ url: "地址", params: { // 以对象的形式传递参数 } })back
router.back({ url: '地址', params: { // 以对象的形式传递参数 } // back返回时是可以传递参数的,但一般不需要为back指定参数 })
router模式的跨包跳转
Navigation的路由跳转 - 跨包:hap -> hsp / hsp -> hsp
Navigation天然支持跨包跳转
通过路径方式
- 规则:@bundle:包名/模块名/ets/pages/xx页面 // 可以把前缀封装成一个常量:@bundle:包名
router.pushUrl({ url: '@bundle:com.itheima.sz.new.content/home/ets/pages/Index' })
通过name的方式(对比路径方式显得麻烦)
- 要跳转的页面修改@Entry ==> @Entry({routerName: 'xxx' })
- 添加依赖,oh-package.json里面添加要跳转的hsp的模块
- 在你要点击跳转的页面中进行import("模块名/路径文件")
- router.pushNamedRoute
import { router } from '@kit.ArkUI';
import("home/src/main/ets/pages/Index")
@Entry
@Component
struct IndexPage {
build() {
RelativeContainer() {
Button("跨包跳转1-路径方式")
.alignRules({
center: { anchor: '__container__', align: VerticalAlign.Center },
middle: { anchor: '__container__', align: HorizontalAlign.Center }
})
.onClick(() => {
router.pushUrl({
url: `@bundle:com.itheima.sz.new.content/home/ets/pages/Index`
})
// 规则- @bundle:包名/模块名/ets/pages/xxx页面
})
.id("btn1")
Button("跨包跳转2-name方式")
.alignRules({
top: { anchor: 'btn1', align: VerticalAlign.Bottom },
middle: { anchor: '__container__', align: HorizontalAlign.Center }
})
.margin({
top: 20
})
.onClick(() => {
// router.pushUrl({
// url: `@bundle:com.itheima.sz.new.content/home/ets/pages/Index`
// })
// 规则- @bundle:包名/模块名/ets/pages/xxx页面
router.pushNamedRoute({
name: 'homePage'
})
})
}
.height('100%')
.width('100%')
}
}