HarmonyOs应用《日记本》开发第16篇 - 日记列表页面 Index 详解
·
本篇深入剖析鸿蒙日记应用的核心入口页面——Index 日记列表页的完整实现,涵盖页面结构、状态管理、数据加载与交互逻辑。

一、页面概述
Index 页面是整个日记应用的首页,用户打开应用后首先看到的就是日记列表。该页面负责:
- 展示所有已创建的日记条目
- 提供新建日记的入口(FAB 浮动按钮)
- 支持点击进入日记详情
- 支持长按删除日记
- 空列表状态展示
二、页面结构设计
2.1 整体布局
┌─────────────────────────┐
│ 标题栏 (日记) │
├─────────────────────────┤
│ │
│ 日记列表 (List) │
│ ┌─────────────────┐ │
│ │ 日期 | 天气心情 │ │
│ │ 内容预览... │ │
│ └─────────────────┘ │
│ ┌─────────────────┐ │
│ │ 日期 | 天气心情 │ │
│ │ 内容预览... │ │
│ └─────────────────┘ │
│ │
│ ┌───┐ │
│ │ + │ FAB │
│ └───┘ │
└─────────────────────────┘
2.2 状态变量定义
@Entry
@Component
struct Index {
@State diaries: DiaryInfo[] = []
@State isLoading: boolean = false
private diaryStore: DiaryStore = DiaryStore.getInstance()
aboutToAppear() {
this.loadDiaries()
}
}
关键点解析:
@State diaries:日记数组,是页面的核心状态数据,变化时自动触发 UI 刷新@State isLoading:加载状态标志,控制加载动画的显示diaryStore:通过单例模式获取数据存储实例,避免重复创建
三、数据加载逻辑
3.1 aboutToAppear 生命周期
aboutToAppear() {
this.loadDiaries()
}
private async loadDiaries() {
this.isLoading = true
try {
this.diaries = await this.diaryStore.getAllDiaries()
// 按日期降序排序
this.diaries.sort((a, b) => {
return new Date(b.date).getTime() - new Date(a.date).getTime()
})
} catch (error) {
console.error('加载日记失败: ' + error)
} finally {
this.isLoading = false
}
}
执行流程:
- 页面即将显示时自动触发
aboutToAppear - 调用
loadDiaries()从 Preferences 异步读取数据 - 按日期降序排序,最新的日记显示在最前面
- 异常处理确保网络或存储错误不会导致页面崩溃
3.2 页面刷新机制
当用户从编辑页面返回时,需要刷新列表数据:
onPageShow() {
this.loadDiaries()
}
这确保了新建或编辑日记后,列表能及时反映最新数据。
四、列表渲染
4.1 List 与 ForEach
List({ space: 12 }) {
ForEach(this.diaries, (diary: DiaryInfo, index: number) => {
ListItem() {
this.DiaryCard(diary)
}
.onClick(() => {
this.goToDetail(diary.id)
})
}, (diary: DiaryInfo) => diary.id)
}
.width('100%')
.layoutWeight(1)
ForEach 参数说明:
| 参数 | 说明 |
|---|---|
| 第一个参数 | 数据源数组 this.diaries |
| 第二个参数 | 子项生成函数,接收 item 和 index |
| 第三个参数 | 键值生成函数,用于 Diff 算法的唯一标识 |
关键提示: 键值生成函数返回
diary.id确保列表项的唯一性,在数据更新时能精确匹配并高效更新 DOM。
4.2 日记卡片组件
@Builder
DiaryCard(diary: DiaryInfo) {
Row({ space: 12 }) {
// 左侧日期
Column() {
Text(diary.day)
.fontSize(28)
.fontWeight(FontWeight.Bold)
Text(diary.monthYear)
.fontSize(12)
.fontColor('#999')
}
.width(60)
.alignItems(HorizontalAlign.Center)
// 右侧内容
Column({ space: 6 }) {
Row({ space: 8 }) {
Text(diary.weather)
Text(diary.mood)
}
Text(diary.content)
.fontSize(14)
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Start)
}
.width('100%')
.padding(16)
.backgroundColor('#FFFFFF')
.borderRadius(12)
.shadow({ radius: 4, color: 'rgba(0,0,0,0.08)' })
}
卡片设计要点:
- 左右布局:日期信息在左,内容预览在右,层次分明
- 阴影效果:轻微阴影提升卡片立体感
- 圆角设计:12px 圆角符合现代 UI 风格
- 文本截断:
maxLines(2)+Ellipsis确保内容过长时优雅截断
五、空状态处理
当没有日记时,展示友好的空状态提示:
if (this.diaries.length === 0) {
Column({ space: 16 }) {
Image($r('app.media.empty'))
.width(120)
.height(120)
Text('还没有日记,点击 + 开始记录吧')
.fontSize(16)
.fontColor('#999')
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
} else {
// 正常列表展示
}
空状态设计原则:
- 提供视觉引导(插图或图标)
- 文案友好,引导用户操作
- 居中布局,视觉平衡
六、导航跳转
6.1 跳转到详情页
private goToDetail(id: string) {
router.pushUrl({
url: 'pages/DiaryDetail',
params: { id: id }
})
}
6.2 跳转到编辑页
private goToEdit() {
router.pushUrl({
url: 'pages/DiaryEdit',
params: { mode: 'create' }
})
}
路由参数传递:
- 通过
params对象传递参数 - 目标页面通过
router.getParams()获取 - 支持字符串、数字、对象等多种类型
七、浮动按钮 FAB
Button() {
Image($r('app.media.add'))
.width(28)
.height(28)
}
.width(56)
.height(56)
.backgroundColor('#007DFF')
.borderRadius(28)
.position({ right: 20, bottom: 20 })
.shadow({ radius: 8, color: 'rgba(0,125,255,0.3)' })
.onClick(() => {
this.goToEdit()
})
FAB 实现要点:
- 使用
position绝对定位,固定在右下角 - 圆形按钮(
borderRadius= 宽度的一半) - 蓝色阴影增强浮动感
- 醒目的图标引导用户操作
八、下拉刷新
Refresh({ refreshing: $$this.isRefreshing }) {
List({ space: 12 }) {
ForEach(this.diaries, (diary: DiaryInfo) => {
ListItem() {
this.DiaryCard(diary)
}
}, (diary: DiaryInfo) => diary.id)
}
}
.onRefreshing(() => {
this.loadDiaries()
})
九、性能优化
9.1 懒加载
对于大量日记数据,可以使用 LazyForEach 替代 ForEach:
LazyForEach(this.diaryDataSource, (diary: DiaryInfo) => {
ListItem() {
this.DiaryCard(diary)
}
}, (diary: DiaryInfo) => diary.id)
9.2 数据分页
private async loadDiaries(page: number = 0, pageSize: number = 20) {
const allDiaries = await this.diaryStore.getAllDiaries()
const start = page * pageSize
const end = start + pageSize
const pageData = allDiaries.slice(start, end)
if (page === 0) {
this.diaries = pageData
} else {
this.diaries = this.diaries.concat(pageData)
}
}
十、总结
Index 页面作为应用的首页,承担了数据展示和导航枢纽的双重职责。其核心设计要点包括:
- 声明式 UI:通过
@State驱动界面自动更新 - Builder 复用:日记卡片通过
@Builder实现代码复用 - 生命周期管理:在
aboutToAppear和onPageShow中处理数据加载 - 用户体验:空状态、加载状态、下拉刷新等细节完善
- 性能考量:ForEach 键值、懒加载等优化手段
这些模式在鸿蒙 ArkUI 开发中非常通用,掌握它们可以快速构建高质量的列表页面。
更多推荐


所有评论(0)