Router路由
页面路由指的是在应用程序中实现不同页面之间的跳转,以及数据传递。通过 Router 模块就可以实现这个功能.
1. 创建页面
之前是创建的文件,使用路由的时候需要创建页面,步骤略有不同
-
方法 1:直接右键新建Page(常用)
-
方法 2:单独添加页面并配置
1.1. 直接右键新建Page
创建的是页面,选项不要选错了
1.2. 单独添加页面并配置
也可以单独创建文件,然后设置为页面
- 新建页面
pages/DetailPage.ets
@Entry
@Component
struct DetailPage {
build() {
Column({ space: 15 }) {
Text('Detail Page')
.fontSize(40)
Button('Back')
}
.height('100%')
.width('100%')
.justifyContent(FlexAlign.Center)
}
}
-
调整配置
小技巧:
-
按两次 shift 在弹出的界面中输入文件名,可以快速定位
{
"src": [
"pages/Index",
+ "pages/DetailPage"
]
}
TIP
-
手动新建一个页面(ets)文件,需要在 main_pages.json 中手动配置
-
可以自动创建(会自动添加配置)
-
删除页面**不会自动删除配置,需要手动删除**
2. 页面跳转
接下来学习路由的跳转,页面跳转是开发过程中的一个重要组成部分。
在使用应用程序时,通常需要在不同的页面之间跳转,有时还需要将数据从一个页面传递到另一个页面。接下来咱们分场景来讲解这部分的内容:
-
页面跳转与后退
-
路由模式
-
参数传递
2.1. 页面跳转与后退
首先来看看看使用频率最高的 跳转和 后退,核心就是使用 router 的方法
// 1. 导入
import router from '@ohos.router';
// 2.调用方法-普通跳转(可以返回)
router.pushUrl({
url:'页面地址'
})
// 2.调用方法-替换跳转(无法返回)
router.replaceUrl({
url:'页面地址'
})
// 2.调用方法-返回()
router.back()
-
创建目录,管理页面:
- 在目录下添加:首页,详情页
-
页面 A 中分别使用pushUrl和replaceUrl跳转到页面 B
-
页面 B 测试 back 方法返回
import router from '@ohos.router'
@Entry
@Component
struct Index {
build() {
Column({ space: 15 }) {
Text('首页')
.fontSize(40)
// 通过 router 模块进行跳转
Button('去详情页-pushUrl')
.onClick(() => {
router.pushUrl({
url: 'pages/day11/knowledges/router01/DetailPage',
})
})
Button('去详情页-replaceUrl')
.onClick(() => {
router.replaceUrl({
url: 'pages/day11/knowledges/router01/DetailPage',
})
})
}
.height('100%')
.width('100%')
.justifyContent(FlexAlign.Center)
}
}
import router from '@ohos.router'
@Entry
@Component
struct DetailPage {
build() {
Row() {
Column() {
Text('详情页')
.fontSize(50)
.fontWeight(FontWeight.Bold)
Button('返回')
.onClick(() => {
router.back()
})
}
.width('100%')
}
.height('100%')
}
}
router.pushUrl() 和 router.replaceUrl()。都可以跳转页面,区别为是否会替换当前页。
-
router.pushUrl():目标页面不会替换当前页,而是压入页面栈。这样可以保留当前页的状态,并且可以通过返回键或者调用router.back()方法返回到当前页。
-
router.replaceUrl():目标页面会替换当前页,并销毁当前页。这样可以释放当前页的资源,并且无法返回到当前页。
划重点:pushUrl 可以返回 replaceUrl 无法返回
2.2. 页面栈
页面栈是用来存储程序运行时页面的一种数据结构,遵循先进后出的原则,咱们结合刚刚的代码来说明一下:
页面栈的最大容量为32个页面
2.2.1. pushUrl的情况
先来看看 pushUrl的情况
-
默认打开首页 → 首页入栈
-
pushUrl 去详情页 → 详情页入栈
-
back 返回上一页 → 详情页出栈
-
此时页面栈中应该只有一个页面
整一个过程中,都可以 router.getLength 进行查看
2.2.2. replaceUrl 的情况
再来看看replaceUrl的情况
-
默认打开首页 → 首页入栈
-
replaceUrl 去详情页 → 详情页替换首页,首页销毁
-
back 无法返回 → 没有上一页
2.2.3. 页面栈相关 api
为了让咱们更好的获取页面栈的信息,router 模块也提供了对应的 api 以供使用
// 获取页面栈长度
router.getLength()
// 获取页面状态
let page = router.getState();
console.log('current index = ' + page.index);
console.log('current name = ' + page.name);
console.log('current path = ' + page.path);
// 清空页面栈
router.clear()
3. 路由模式
路由提供了两种不同的跳转模式,不同模式的决定了页面是否会创建多个实例
-
standard(标准实例模式)是默认情况下的实例模式。在这种模式下,每次调用都会新建一个目标页,并压入页面栈顶。这意味着无论之前是否添加过相同的页面,每次跳转都会创建一个新的页面实例。这种模式的优点是可以确保每次跳转都是全新的页面状态,不会受到之前页面状态的影响。适用于那些需要每次跳转都呈现全新内容或状态的场景,或者需要避免复用已有页面实例导致的数据混淆问题。
-
Single(单实例模式)则会在页面栈中检查是否存在相同URL的页面。如果存在,离栈顶最近的同URL页面会被移动到栈顶,并重新加载;如果不存在,则按照标准模式跳转。这种模式的优势在于可以复用已有的页面实例,避免重复创建相同的页面,提高应用性能和资源利用率。它适用于那些需要保留页面状态或避免重复创建相同页面的场景,比如当用户在应用内多次访问同一页面时,使用Single模式可以确保页面的状态得到保持,并提供更加流畅的用户体验。
简而言之:
-
Standard:无论之前是否添加过,一直添加到页面栈【常用】
-
Single:如果之前加过页面,会使用之前添加的页面【看情况】
// 多实例模式下,router.RouterMode.Standard参数可以省略。
// pushUrl 和 replaceUrl 均可以在第二个参数设置 【路由模式】
router.pushUrl(options,mode)
router.replaceUrl(options,mode)
RouterMode参数说明
名称 | 说明 |
---|---|
Standard | 多实例模式,也是默认情况下的跳转模式。 目标页面会被添加到页面栈顶,无论栈中是否存在相同url的页面。 说明: 不使用路由跳转模式时,则按照默认的多实例模式进行跳转。 |
Single | 单实例模式。 如果目标页面的url已经存在于页面栈中,则该url页面移动到栈顶。 如果目标页面的url在页面栈中不存在同url页面,则按照默认的多实例模式进行跳转。 |
@Entry
@Component
struct HomePage {
build() {
Stack() {
Image($r("app.media.douban_home"))
.width('100%')
Button('电影详情')
.translate({ x: -120 })
.onClick(() => {
})
}
.height('100%')
}
}
@Entry
@Component
struct MoviePage {
@State message: string = 'Hello World';
build() {
Stack() {
Image($r("app.media.douban_movie"))
.width('100%')
Button('演员')
.translate({ x: -35, y: 180 })
.onClick(() => {
})
}
.height('100%')
}
}
@Entry
@Component
struct ActorPage {
build() {
Stack() {
Image($r("app.media.douban_actor"))
Button('电影')
.translate({ x: 43, y: 80 })
.onClick(() => {
})
}
.height('100%')
}
}
3.1. Strandard模式
使用 Strandard 模式,在电影和演员页面反复跳转时会持续往页面栈中添加新的页面,浪费内存
3.2. Single模式
使用 Single 模式,在页面和演员页面反复跳转时会将已有的页面移到栈顶,避免浪费内存
4. 参数
接下来看看另外一个挺常见的需求:传递参数
日常开发中有这样的场景:点击不同的电影,商品,标题。。。跳转到与之对应的详情页面。
详情页面的布局是类似的,但是内容被替换为与之对应的内容。这就是一个常见的需要传递参数的场景:
列表 → 详情
4.1. 参数传递及接收
首先来看看如何实现页面参数传递和获取
// -----------传递参数------------
// 普通跳转 并 传递参数
router.pushUrl({
url:'地址',
params:{
// 以对象的形式传递参数
}
})
// 覆盖跳转并传递参数
router.replaceUrl(
url:'地址',
params:{
// 以对象的形式传递参数
}
)
// 返回并传递参数
router.back(
url:'地址',
params:{
// 以对象的形式传递参数
}
)
// -------------页面 B接收并解析参数------------
// aboutToAppear一会展开 (生命周期函数)
aboutToAppear(): void {
// 1.确认内容
console.log(JSON.stringify(router.getParams()))
// 2.通过 as 类型断言 转为具体的类型
const params = router.getParams() as 类型
// 3. 通过点语法即可取值
params.xxx
}
options参数说明
名称 | 类型 | 必填 | 说明 |
---|---|---|---|
url | string | 是 | 表示目标页面的url,可以用以下两种格式: - 页面绝对路径,由配置文件中pages列表提供,例如: - pages/index/index - pages/detail/detail - 特殊值,如果url的值是"/",则跳转到首页。 |
params | object | 否 | 表示路由跳转时要同时传递到目标页面的数据,切换到其他页面时,当前接收的数据失效。跳转到目标页面后,使用router.getParams()获取传递的参数 |
-
创建目录管理页面
-
-
创建首页
-
创建详情页
-
-
【首页】携带数据去 【详情页】
-
【详情页】接收并解析数据
export class MovieInfo {
id: number = 0
}
@Entry
@Component
struct Home {
build() {
Stack() {
Image($r('app.media.douban_home'))
.width('100%')
Row({ space: 10 }) {
Button('热辣滚烫')
.onClick(() => {
// id: 36081094 图片 params_movie1
})
Button('第二十条')
.onClick(() => {
// id: 36208094 图片 params_movie2
})
Button('飞驰人生')
.onClick(() => {
// id: 36369452 图片 params_movie3
})
}
.translate({ x: -10 })
}
.height('100%')
}
}
@Entry
@Component
struct Params {
@State imgUrl: ResourceStr = $r('app.media.params_movie1');
aboutToAppear(): void {
// 根据 传递过来的 id 决定渲染不同的图片
// 热辣滚烫 // id == 36081094
// 第二十条 // id == 36208094
// 飞驰人生 2 // id == 36369452
}
build() {
Row() {
Image(this.imgUrl)
}
.height('100%')
}
}
5. 共享元素转场
当路由进行切换时,可以通过设置组件的 sharedTransition 属性将该元素标记为共享元素并设置对应的共享元素转场动效。
// 页面 A
组件(){
}
.sharedTransition('标记', { duration: 500 })
// 页面 B
组件(){
}
.sharedTransition('标记', { duration: 500 })
动画属性
名称 | 参数类型 | 是否必填 | 参数描述 |
---|---|---|---|
duration | number | 否 | 描述共享元素转场动效播放时长。 默认值:1000。 单位:毫秒。 |
curve | Curve | string | ICurve10+ | 否 | 描述共享元素转场动效播放时长。 默认值:1000。 单位:毫秒。 |
delay | number | 否 | 延迟播放时间。 默认值:0。 单位:毫秒。 |
motionPath | MotionPathOptions | 否 | 运动路径信息。 |
zIndex | number | 否 | 设置Z轴。 |
type | SharedTransitionEffectType | 否 | 动画类型。 默认值:SharedTransitionEffectType.Exchange。 |
效果
代码
Index.ets
import router from '@ohos.router'
export interface ProjectItem {
id:number
img: ResourceStr
title: string
desc: string
sub: string
}
@Entry
@Component
struct Index {
@State projectItem: ProjectItem[] = [
{
id:1,
img: $r('app.media.project02'),
title: '后台管理系统',
desc: '以Vue技理后台后台管理系统面试题面试整理',
sub: '后台系统'
},
{
id:2,
img: $r('app.media.project01'),
title: '在线问医生平台(医疗类)',
desc: '通过vue3+ts实现的在线医疗问诊相关技术整理',
sub: 'H5项目'
},
{
id:3,
img: $r('app.media.project03'),
title: '鸿蒙知识点',
desc: '以ArkTS与ArkUI为主的鸿蒙基础知识点讲解',
sub: '鸿蒙应用'
},
]
build() {
Column() {
Row() {
Text('项目').fontSize(20)
}
Column() {
ForEach(this.projectItem, (item: ProjectItem) => {
Row({ space: 10 }) {
Image(item.img).height('100%')
.sharedTransition(item.id.toString(),{duration:500
// ,type:SharedTransitionEffectType.Static
})
Column() {
Column({ space: 5 }) {
Text(item.title).fontSize(18)
Text(item.desc).fontSize(12).fontColor('#666')
}
.alignItems(HorizontalAlign.Start)
Text(item.sub).fontSize(10)
.padding(3).fontColor(Color.Green)
.backgroundColor('rgb(241, 244, 244)')
}.height('100%')
.alignItems(HorizontalAlign.Start)
.justifyContent(FlexAlign.SpaceBetween)
}.width('100%').height(110)
.padding({ top: 15, bottom: 15 })
.onClick(() => {
router.pushUrl({
url: "pages/DetailItem",
params: item
})
})
})
}
}.width('100%').height('100%').padding(15)
}
}
DetailItem.ets
import router from '@ohos.router'
import { ProjectItem } from '../pages/Index'
@Entry
@Component
struct DetailPage {
aboutToAppear(): void {
const data = router.getParams() as ProjectItem
this.data = data
}
@State data: ProjectItem = { id: 0 ,img: '', title: '', desc: '', sub: '' }
@State isShow: boolean = true
build() {
Column() {
Stack() {
Column({ space: 30 }) {
Row() {
Text('<').fontColor('#fff').fontSize(30)
}
Row({ space: 10 }) {
Image(this.data.img).height(150)
.sharedTransition(this.data.id.toString(),{ duration:500
// ,type:SharedTransitionEffectType.Static
})
Column() {
Text(this.data.title).fontSize(20).fontColor('#fff')
Text(this.data.desc).fontColor('#fff')
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.width('65%')
Text(`学习进度:10%`).fontColor('#999')
}
.alignItems(HorizontalAlign.Start)
.height(150).justifyContent(FlexAlign.SpaceAround)
}
}
.padding(20)
.alignItems(HorizontalAlign.Start)
Image($r('app.media.project_bg'))
.opacity(0.2).width('100%')
.onClick(() => {
router.back({
url: "pages/Index"
})
})
}
.height('100%')
.alignContent(Alignment.TopStart)
.backgroundColor('#683a18')
.bindSheet($$this.isShow, this.BindSheetContent(), {
height: 520, showClose: false
})
}
}
@Builder
BindSheetContent() {
Row() {
Text('问题列表').fontSize(25).fontWeight(500).margin(20)
}.justifyContent(FlexAlign.Start).width('100%')
}
}