《鸿蒙纪元》 是 张风捷特烈 计划打造的一套 HarmonyOS 开发系列教程合集。致力于创作优质的鸿蒙原生学习资源,帮助开发者进入纯血鸿蒙的开发之中。本系列的所有代码将开源在 HarmonyUnit 项目中:
github
: github.com/toly1994328…
gitee
: gitee.com/toly1994328…
鸿蒙纪元 系列文章列表可在《文章总集》 或 【github 项目首页】 查看。
上一篇,我们简单了解通过 bindSheet
弹出底部抽屉框,实现了 选择音效 和 选择图片 基本功能,丰富了电子木鱼的玩法。本文将进一步完善功能,实现 记录功德列表
的功能:
1. 功德记录的需求
我们在上一篇中,为木鱼增加了一个属性点: 每次功德增加的取值范围 。现在想要记录每次点击时的功德数据,以便让用户了解功德增加的历史。对于一个需求而言,最重要的是分析其中需要的数据,以及如何维护这些数据。
效果如下,点击左上角按钮,跳转到功德记录的面板。功德记录界面是一个可以滑动的列表,每个条目展示当前功德的 木鱼图片
、音效名称
、时间
、功德数
信息。在这个需求中,我们还能了解鸿蒙开发中到如何跳转到新界面:
木鱼主页 | 功德记录界面 |
---|---|
2. 功德数据模型
根据界面中的数据,可以封装一个类来维护,如下 MeritRecord 类。其中:
interface
关键字表示定义数据字段接口,便于作为入参构造 MeritRecord 对象;readonly
关键字表示字段只读,对象无法修改改字段。一条木鱼的历史记录记录着真实可信的数据,是不会也不应被篡改的,所以 readonly 在这里很符合场景:
interface MeritRecordParams {
timestamp: number; // 记录的时间戳
value: number; // 功德数
image: ResourceStr; // 图片资源
audio: string; // 音效名称
}
export class MeritRecord {
readonly timestamp: number;
readonly value: number;
readonly image: ResourceStr;
readonly audio: string;
constructor(data: MeritRecordParams) {
this.timestamp = data.timestamp;
this.value = data.value;
this.image = data.image;
this.audio = data.audio;
}
}
对于功德记录的需求而言,需要在 MuyuBloc
中新加的数据也很明确: 维护 MeritRecord
数组。如下所示,records
列表需要在每次点击时记录信息,另外 lastRecord
用于记录最后一次的记录,用于展示每次动画文字的信息:
---->[muyu/bloc/MuyuBloc.ets]----
@Track records: MeritRecord[] = [];
@Track lastRecord?: MeritRecord | null;
tick(): void {
if (!this.ready) {
return;
}
this.player?.play(this.soundIds[this.activeAudioIndex]);
let image: ImageOption = this.imageOptions[this.activeImageIndex];
let audio: AudioOption = this.audioOptions[this.activeAudioIndex];
let value = Math.floor(Math.random() * (image.max - image.min + 1)) + image.min;
this.lastRecord = new MeritRecord({
timestamp: Date.now(),
value: value,
image: image.src,
audio: audio.name,
});
this.records.push(this.lastRecord!);
this.counter += value;
}
这样数据层就完成了,接下来看一下如何处理新界面的跳转。
3. 使用 Navigation 组件进行界面跳转
界面的跳转很好理解,就是将一个新界面推 (push) 到当前界面之上,从而展示新界面。一般推入的界面可以弹出(pop) 展示下层界面:
官方的文档目前已经不推荐使用 router
体系的路由跳转,推荐使用 Navigation 组件。
首先需要配置路由表,在 module.json5
中增加 routerMap 字段,指向 profile
里的 router_map.json,文件名可以任意,只要两处一致即可:
在路由配置表中,routerMap 节点放置路由列表,配置名称
、文件路径
和构建函数
:
{
"routerMap": [
{
"name": "muyu",
"pageSourceFile": "src/main/ets/pages/muyu/view/MuyuPage.ets",
"buildFunction": "pageBuilder"
},
{
"name": "merit_record",
"pageSourceFile": "src/main/ets/pages/muyu/view/MeritRecordPage.ets",
"buildFunction": "pageBuilder"
}
]
}
界面构造器就是一个通过 @Builder
注解、返回界面对象的函数,如下所示:
--->[/muyu/view/MuyuPage.ets]----
@Builder
export function pageBuilder() {
MeritRecordPage()
}
另外推入的界面一般通过 NavDestination 组件包裹,可以感知一些路由的生命周期,以及操作方法:
此时 Index 入口中,通过 Navigation 组件提供路由功能。需要在构造时传入 NavPathStack 对象,该对象可以操作路由栈。这里通过 @Provide 注解可以向下层的组件传递状态数据,便于子组件访问 pageStack 对象。在 onAppear
回调中,可以通过 pushPathByName
跳转到木鱼主界面:
---->[Index.ets]----
@Entry
@Component
struct Index {
@StorageProp('bottomRectHeight')
bottomRectHeight: number = 0;
@Provide('NavPathStack') pageStack: NavPathStack = new NavPathStack()
build() {
Navigation(this.pageStack){}
.padding({ bottom: px2vp(this.bottomRectHeight) })
.onAppear(() => this.pushHome())
}
pushHome(): void {
this.pageStack.pushPathByName("muyu", null, false);
}
}
下层的 MuYuPage 组件通过 @Consume 注解可以访问到上层注入的对象,现在只需要点击历史记录按钮,通过 pageStack
进行跳转即可:
pushPathByName 的第二参是推入路由 传递的参数,这里新界面需要功德记录的数据
--->[/muyu/view/MuyuPage.ets]----
@Component
export struct MuYuPage {
@Consume('NavPathStack') pageStack: NavPathStack;
toHistory() {
this.pageStack.pushPathByName('merit_record', this.model.records.reverse());
}
...
}
4. 功德记录界面
在 view 文件夹中创建 MeritRecordPage.ets
文件,使用 NavDestination 包裹整个界面内容,在 onReady
回调中可以感知导航的上下文,从中可以获取上级界面传递的参数,以此初始化 MeritRecord
数组:
@Builder
export function pageBuilder() {
MeritRecordPage()
}
@Component
export struct MeritRecordPage {
pathStack?: NavPathStack;
@State records: MeritRecord[] = []
findParam(context: NavDestinationContext): void {
this.records = context.pathInfo.param as MeritRecord[];
}
build() {
NavDestination() {
/// 构建界面具体内容
}
.onReady((ctx) => this.findParam(ctx)).hideTitleBar(true)
}
}
接下来就是列表界面的构建代码,建议大家在写界面时,尽可能细分一下。比如这个组件可以封装一下来单独维护,以后想修改就很方便定位到代码位置。
另外,不要写一点代码运行一下查看效果,毕竟目前鸿蒙开发的热重载还没有很完善,对于跳转进入的二级界面,运行查看效果比较费劲。拆分成新组件的好处还有:你可以通过 @Preview
注解,通过 Previewer
面板在开发过程中,实时查看效果,有助于效率提升:
列表内容是可滑动的,可以使用 List 组件进行构建,通过 ForEach 遍历构建 MeritRecordItem 子条目即可。条目的独立封装,也可以让构建的逻辑更加清晰。想一想,如果 MeritRecordItem 的构建逻辑全部塞在 ForEach 里面,代码回是多么混乱。
List() {
ForEach(this.records, (record: MeritRecord) => {
ListItem() { MeritRecordItem({ record }) }
}, (item: MeritRecord) => `${item.timestamp}`)
}
尾声
到这里,电子木鱼的基本功能完成了,这里提交一个小里程碑 v16-电子木鱼-功能完成 。目前我们只是简单地了解一下 Navigation 导航,以后还会有机会详细地介绍它。
另外,现在的数据都是存储在内存中的,应用退出之后无论是选项,还是功德记录都会重置。想要数据持久化存储,在后面的 数据的持久化存储 章节会再继续完善,木鱼项目先告一段落。
更多文章和视频知识资讯,大家可以关注我的公众号、掘金和 B 站 。关注 公众号
并回复 鸿蒙纪元 可领取最新的 xmind 脑图电子版,让我们一起成长,变得更强。我们下次再见~