鸿蒙开发5--鸿蒙页面导航(声明式导航Navigation组件)
上文中我们用@ohos.router做了一个商品信息的列表和详情间的跳转页面。
但是我们发现@ohos.router是一种编程式导航。你必须在代码的某个地方调用函数router.pushUrl()来命令应用跳转到下一个页面。
而声明式导航就不一样了,核心思想是导航本身也是UI状态的一部分。我们不再发出“跳转”的命令,只需要通过改变一个状态变量来声明我们希望看到哪个页面。
一、Navigation框架的核心三要素
声明式导航主要由三个部分协同工作:
-
<Navigation>容器:
-
这是一个特殊的根组件,包裹了所有参与导航的页面视图。
-
定义了一个导航的上下文或作用域。
-
-
NavPathStack栈:
-
这是一个我们需要自己定义和维护的状态变量,通常是一个数组。
-
这个数组的内容直接决定了当前的导航路径。
-
数组为空,显示根页面(首页)。
-
数组中有一个元素,显示栈顶元素对应的页面。
-
数组中有多个元素,形成一个导航层级。
-
-
通过对这个数组进行.push()、.pop()等操作,来驱动UI进行页面的推入和弹出。
-
-
<NavDestination>目标页:
-
这是一个用来“注册”或“定义”可跳转目标页面的组件。
-
我们给每个目标页面创建一个NavDestination,通过他的builder来指定该页面具体长什么样。
-
二、重构商品应用页面
我们把之前的商品应用页面重构一下。
2.1. 创建模块
同样的,我们先创建一个模块day5_navigation。

在src/main/ets/pages目录下,创建两个UI Page文件:ProductListPage.ets、ProductDetailPage.ets(Index.ets可以删除)

记得把Day5_navigationAbility.ets中UIAbility的入口页面修改了。

2.2. 定义数据模型和导航栈
为了在页面间传递数据,并让代码更规范,我们先定义好数据类型。
创建一个新文件src/main/ets/models/ProductModels.ets
// 商品的数据结构
export interface Product {
id: string;
name: string;
description: string;
}
2.3. 改造ProductListPage.ets(列表页)
把列表页改造成我们的导航主容器。
import { Product } from '../models/ProductModels';
import { ProductDetailPage } from './ProductDetailPage';
const products: Product[] = [
{ id: 'p001', name: '鸿蒙智能手表', description: '最新款,搭载HarmonyOS NEXT。' },
{ id: 'p002', name: 'ArkUI T恤', description: '开发者信仰充值,100%纯棉。' },
{ id: 'p003', name: 'DevEco Studio贴纸', description: '让你的笔记本充满极客范。' },
{ id: 'p004', name: '手机', description: '拍个月球给你看。' }
];
@Entry
@Component
struct ProductListPage {
@State path: NavPathStack = new NavPathStack();
@Builder
destinationBuilder(name: string, param: object) {
if (name === 'ProductDetail') {
NavDestination() {
ProductDetailPage({ product: param as Product })
}
.title('产品详情')
} else {
NavDestination() {
Text('Invalid destination')
.fontSize(16)
.fontColor(Color.Red)
}
.title('Error')
}
}
build() {
Navigation(this.path) {
Column() {
List({ space: 12 }) {
ForEach(products, (item: Product) => {
ListItem() {
Text(item.name)
.width('100%').fontSize(20).padding(20)
.backgroundColor(Color.White).borderRadius(10)
}
.onClick(() => {
this.path.pushPathByName('ProductDetail', item);
})
})
}
.width('95%').layoutWeight(1)
}
.width('100%').height('100%').backgroundColor('#f1f3f5')
.padding({ top: 10 })
}
.title('商品列表')
.navDestination(this.destinationBuilder)
}
}
使用import导入了Product数据模型和ProductDetailPage组件。
@State path: NavPathStack是整个声明式导航的核心。
定义了一个名叫path的变量,类型是NavPathStack,这是一个专门用来管理导航路径的特殊对象。
@State装饰器就是告诉ArkUI框架,要关注path这个变量,如果发生了变化,就要自动刷新所有依赖他的UI。
@Builder也是一个装饰器,表示destinationBuilder方法不是一个普通的逻辑函数,是一个专门用来构建UI片段的模版。
destinationBuilder(name: string, param: object)构造器接收两个参数,这两个参数都是path.pushPathByName方法在跳转的时候提供的。
name是路由目标的名称,用他来区分跳转到哪个页面。
param是携带的参数,是一个通用的object。
NavDestination()是真正定义目标导航页的容器。所有可以被导航到的页面,都必须被他包裹。
在NavDestination内部,我们创建了ProductDetailPage组件的实例。param as Product是一个类型断言,明确告诉编译器:虽然param是object类型,但确定他实际上是一个Product对象,然后把他传递给详情页的product属性。
.title('产品详情')就是给这个目标页设置导航栏的标题。
Navigation(this.path)是导航的根容器,我们把@State变量this.path传给他,建立双向绑定。当path改变时,Navigation组件会自动更新视图;当用户在UI上进行返回操作时,Navigation组件也会自动更新path变量。
这里的.title('商品列表')是根页面(也就是列表页自身)设置导航栏标题。
.navDestination(this.destinationBuilder)是关键的一步。我们把刚才用@Builder定义的destinationBuilder方法,注册给了Navigation容器。这样Navigation就知道当path发生变化时,应该调用哪个模版去构建目标页面。
当用户点击列表项时,我们不再调用router,而是直接修改状态。
this.path.pushPathByName('ProductDetail', item)调用path对象的pushPathByName方法。
ProductDetail这个字符串会作为name参数传递给我们的destinationBuilder。
item是当前点击的商品对象(Product类型),会作为param参数传递给destinationBuilder。
一旦这行代码执行,@State变量path的内容就变了,ArkUI框架侦测到这个变化,就会自动使用destinationBuilder去构建ProductDetailPage并显示出来。
2.4. 改造ProductDetailPage.ets(详情页)
这个页面比较简单,只负责一件事,就是接收一个Product对象,显示出来。
import { Product } from '../models/ProductModels';
@Entry
@Component
export struct ProductDetailPage {
@Prop product: Product;
build() {
Column() {
Text(this.product.name)
.fontSize(28).fontWeight(FontWeight.Bold).margin(20)
Text(this.product.description)
.fontSize(18).fontColor(Color.Gray).margin({ left: 20, right: 20 })
}
.width('100%').height('100%')
.justifyContent(FlexAlign.Start).padding(15)
}
}
@Prop product: Product;是子组件接收父组件传递数据的标准方式。
@Prop装饰器声明product是一个外部属性,他的值由创建ProductDetailPage的地方(也就是destinationBuilder)提供。
这样就建立了一个从父到子的单向数据流。
在build函数中,我们可以像使用普通成员变量一样,直接通过this.product.name和this.product.description来访问由@Prop传入的数据,并把他们渲染成UI。
2.5. 改造后界面
列表

详情

更多推荐



所有评论(0)