心情日记小应用 - HarmonyOS ArkUI 开发实战-Checkbox与Flex布局-PC版本

一、应用概述与设计理念
心情日记小应用是一款记录每日心情的温馨工具,灵感来源于人们记录生活点滴的习惯。在快节奏的现代生活中,我们经常会有各种情绪体验,开心、难过、平静、激动等。记录这些心情不仅可以帮助我们回顾生活,还能帮助我们更好地理解自己的情绪变化规律。
这款应用基于HarmonyOS ArkUI框架开发,采用了声明式UI开发范式。用户可以选择表情符号表达心情,并写下当天的感受。应用会自动记录日期,并将日记条目按时间顺序展示。界面温馨简洁,操作简单直观,适合各年龄段用户使用。
从技术角度来看,这个应用涵盖了HarmonyOS开发中的多个核心知识点:自定义数据模型、表情选择器实现、日期格式化、List列表渲染、Flex布局应用、数据管理等。通过学习这个应用的开发过程,开发者可以深入理解ArkTS语言的日期处理能力和列表渲染机制。
二、功能特性详解
2.1 核心功能列表
心情日记小应用提供了以下核心功能:
| 功能模块 | 功能描述 | 技术实现 |
|---|---|---|
| 心情选择 | 8种表情符号可选 | Button + ForEach |
| 日记输入 | TextInput记录心情文字 | TextInput + onChange |
| 日期记录 | 自动记录当前日期 | Date对象 + 格式化 |
| 日记保存 | 创建并保存日记条目 | Button + onClick |
| 历史展示 | List列表展示历史日记 | List + ForEach |
| 数据管理 | 日记数据的增删操作 | 数组操作方法 |
2.2 用户交互流程
2.3 界面设计说明
应用的界面设计遵循温馨简洁的原则,主要分为以下几个区域:
- 顶部标题栏:包含返回按钮和应用标题,采用灰色背景
- 心情选择区:8种表情符号,圆形按钮设计,选中时背景变色
- 输入区域:TextInput组件用于输入日记内容,高度设置为80
- 保存按钮:蓝色按钮,宽度60%,居中显示
- 历史记录区:List列表展示历史日记,卡片式设计
三、数据模型设计
3.1 日记数据模型
在ArkTS中,使用class定义日记数据模型:
class DiaryEntry_1 {
date_1: string = '';
mood_1: string = '';
text_1: string = '';
constructor(date_1: string, mood_1: string, text_1: string) {
this.date_1 = date_1;
this.mood_1 = mood_1;
this.text_1 = text_1;
}
}
数据模型分析:
| 属性 | 类型 | 说明 | 示例值 |
|---|---|---|---|
| date_1 | string | 日记日期 | ‘2024-06-15’ |
| mood_1 | string | 心情表情 | ‘😊’ |
| text_1 | string | 日记内容 | ‘今天心情很好’ |
构造函数说明:
- 构造函数接收三个参数:日期、表情、内容
- 使用构造函数可以方便地创建新的日记实例
- 初始值设置为空字符串,确保属性初始化
3.2 数据模型的优势
使用class定义数据模型有以下优势:
| 优势 | 说明 | 应用场景 |
|---|---|---|
| 类型安全 | 明确定义属性类型 | 避免类型错误 |
| 结构清晰 | 数据结构一目了然 | 代码可读性强 |
| 易于扩展 | 可以方便添加新属性 | 功能扩展方便 |
| 语义明确 | DiaryEntry名称直观 | 理解数据含义 |
3.3 数据模型扩展方向
如果需要扩展日记功能,可以在数据模型中添加更多属性:
class EnhancedDiaryEntry {
id: string = ''; // 日记唯一标识
date: string = ''; // 日记日期
mood: string = ''; // 心情表情
text: string = ''; // 日记内容
weather: string = ''; // 天气信息
location: string = ''; // 地理位置
tags: string[] = []; // 标签列表
images: string[] = []; // 图片列表
createTime: Date = new Date(); // 创建时间
}
四、状态管理实现
4.1 状态变量声明
应用使用了多个状态变量来管理数据:
@State selectedMood_1: string = '😊';
@State diaryText_1: string = '';
@State entries_1: DiaryEntry_1[] = [
new DiaryEntry_1('2024-06-15', '😊', '今天心情很好,天气也不错'),
new DiaryEntry_1('2024-06-14', '😐', '平平淡淡的一天'),
new DiaryEntry_1('2024-06-13', '😢', '有点难过,工作不太顺利')
];
状态变量说明表:
| 状态变量 | 类型 | 初始值 | 作用 |
|---|---|---|---|
| selectedMood_1 | string | ‘😊’ | 当前选中的心情表情 |
| diaryText_1 | string | ‘’ | 用户输入的日记内容 |
| entries_1 | DiaryEntry_1[] | […] | 存储所有日记条目 |
4.2 表情配置
应用提供了8种心情表情:
moods_1: string[] = ['😊', '😄', '😍', '😐', '😔', '😢', '😤', '😴'];
表情方案表:
| 表情 | 名称 | 心情含义 | 建议场景 |
|---|---|---|---|
| 😊 | 微笑 | 开心愉快 | 好心情、小确幸 |
| 😄 | 大笑 | 非常开心 | 特别开心的事 |
| 😍 | 爱慕 | 喜欢喜爱 | 恋爱、喜爱的事物 |
| 😐 | 平淡 | 平静普通 | 日常平淡 |
| 😔 | 失落 | 失望沮丧 | 不如意的事 |
| 😢 | 难过 | 悲伤难过 | 遇到困难 |
| 😤 | 生气 | 愤怒不满 | 不开心的事 |
| 😴 | 困倦 | 疲惫困乏 | 累了、想休息 |
4.3 状态变量响应机制
当状态变量变化时,UI会自动更新:
五、表情选择器实现
5.1 表情选择器UI结构
表情选择器使用Flex布局,支持自动换行:
Flex({ wrap: FlexWrap.Wrap }) {
ForEach(this.moods_1, (mood_1: string) => {
Button(mood_1)
.width(56)
.height(56)
.fontSize(28)
.backgroundColor(this.selectedMood_1 === mood_1 ? '#E8F0FE' : '#F1F3F5')
.borderRadius(28)
.onClick(() => {
this.selectedMood_1 = mood_1;
})
.margin({ right: 8 })
})
}
.margin({ top: 12 })
5.2 Flex布局详解
Flex是弹性布局容器,支持灵活的子组件排列:
Flex属性说明:
| 属性 | 值 | 说明 |
|---|---|---|
| wrap | FlexWrap.Wrap | 支持自动换行 |
| direction | FlexDirection.Row | 横向排列(默认) |
| justifyContent | FlexAlign.Start | 从左开始排列 |
| alignItems | ItemAlign.Center | 垂直居中 |
5.3 Button表情按钮详解
表情按钮采用圆形设计:
Button(mood_1)
.width(56)
.height(56)
.fontSize(28)
.backgroundColor(this.selectedMood_1 === mood_1 ? '#E8F0FE' : '#F1F3F5')
.borderRadius(28)
按钮属性说明:
| 属性 | 值 | 说明 |
|---|---|---|
| text | mood_1 | 表情符号作为按钮文本 |
| width | 56 | 宽度56px |
| height | 56 | 高度56px |
| fontSize | 28 | 字体大小28px |
| borderRadius | 28 | 圆角半径28px(圆形) |
| backgroundColor | 动态颜色 | 选中时蓝色背景 |
5.4 动态背景颜色
表情按钮的背景颜色根据选中状态动态变化:
.backgroundColor(this.selectedMood_1 === mood_1 ? '#E8F0FE' : '#F1F3F5')
颜色逻辑:
- 选中时:#E8F0FE(淡蓝色)
- 未选中:#F1F3F5(灰色)
- 提供直观的选中反馈
- 圆形设计更加美观
5.5 ForEach循环渲染
ForEach用于循环渲染表情按钮:
ForEach(this.moods_1, (mood_1: string) => {
Button(mood_1)
// ...
})
ForEach特点:
- 遍历moods数组
- 为每个表情创建按钮
- 自动处理数组变化
- 提高渲染效率
六、日记输入处理
6.1 TextInput组件实现
日记输入使用TextInput组件:
TextInput({ placeholder: '写下今天的心情...' })
.width('90%')
.height(80)
.margin({ top: 20 })
.onChange((value_1: string) => {
this.diaryText_1 = value_1;
})
6.2 TextInput属性说明
| 属性 | 值 | 说明 |
|---|---|---|
| placeholder | ‘写下今天的心情…’ | 占位提示文本 |
| width | ‘90%’ | 宽度为父容器90% |
| height | 80 | 高度80px,舒适输入 |
| onChange | 回调函数 | 输入变化时更新状态 |
6.3 onChange回调详解
onChange回调实时更新输入内容:
.onChange((value_1: string) => {
this.diaryText_1 = value_1;
})
回调机制说明:
- 参数value_1:当前输入的文本
- 每次输入变化都会触发
- 实时更新状态变量
- 为保存提供输入数据
6.4 输入高度设计
日记输入框高度设置为80px,提供舒适的输入体验:
| 高度 | 适用场景 | 优势 |
|---|---|---|
| 40px | 简短输入 | 紧凑布局 |
| 60px | 中等输入 | 平衡设计 |
| 80px | 详细日记 | 舒适体验 |
七、日记保存功能
7.1 保存按钮实现
保存按钮用于创建日记条目:
Button('保存日记')
.width('60%')
.height(40)
.margin({ top: 16 })
.backgroundColor('#0A59F7')
.onClick(() => {
let now_1: Date = new Date();
let date_1: string = now_1.getFullYear() + '-' +
String(now_1.getMonth() + 1).padStart(2, '0') + '-' +
String(now_1.getDate()).padStart(2, '0');
let newEntry_1 = new DiaryEntry_1(date_1, this.selectedMood_1, this.diaryText_1);
this.entries_1 = [newEntry_1].concat(this.entries_1);
this.diaryText_1 = '';
})
7.2 保存流程分析
7.3 日期获取与格式化
保存日记时需要获取并格式化当前日期:
let now_1: Date = new Date();
let date_1: string = now_1.getFullYear() + '-' +
String(now_1.getMonth() + 1).padStart(2, '0') + '-' +
String(now_1.getDate()).padStart(2, '0');
日期格式化说明:
| 步骤 | 方法 | 说明 |
|---|---|---|
| 获取年份 | getFullYear() | 四位数年份 |
| 获取月份 | getMonth() + 1 | 月份从0开始,需加1 |
| 获取日期 | getDate() | 当前日期 |
| 格式化 | padStart(2, ‘0’) | 补零为两位数 |
7.4 padStart方法详解
padStart用于字符串补零:
String(now_1.getMonth() + 1).padStart(2, '0')
padStart说明:
| 参数 | 说明 | 示例 |
|---|---|---|
| targetLength | 目标长度 | 2 |
| padString | 补充字符 | ‘0’ |
| 原字符串 | ‘6’ | 月份 |
| 结果 | ‘06’ | 补零后 |
7.5 日记添加到列表
新日记添加到列表开头,最新的在最前面:
this.entries_1 = [newEntry_1].concat(this.entries_1);
添加方式说明:
| 方法 | 效果 | 说明 |
|---|---|---|
| concat到开头 | 新日记在最前 | 最新日记优先显示 |
| concat到末尾 | 新日记在最后 | 按时间顺序排列 |
为什么添加到开头:
- 用户习惯查看最新日记
- 最新日记优先展示
- 符合时间倒序排列习惯
- 提升用户体验
7.6 输入框清空
保存成功后清空输入框:
this.diaryText_1 = '';
清空说明:
- 更新状态变量为空字符串
- TextInput自动清空
- 方便用户继续写日记
- 提升交互体验
八、日期处理技术
8.1 Date对象常用方法
JavaScript的Date对象提供了丰富的方法:
| 方法 | 返回值 | 说明 |
|---|---|---|
| getFullYear() | 年份 | 四位数年份 |
| getMonth() | 0-11 | 月份(需要+1) |
| getDate() | 1-31 | 日期 |
| getHours() | 0-23 | 小时 |
| getMinutes() | 0-59 | 分钟 |
| getSeconds() | 0-59 | 秒 |
| getTime() | 时间戳 | 毫秒数 |
8.2 日期格式化技巧
将Date对象格式化为字符串是常见需求:
// 格式化为 YYYY-MM-DD
let date_1: string = year + '-' +
String(month).padStart(2, '0') + '-' +
String(day).padStart(2, '0');
// 使用模板字符串
let formattedDate_1: string = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
8.3 日期计算注意事项
在进行日期计算时,需要注意以下几点:
| 注意事项 | 说明 | 影响 |
|---|---|---|
| 时区问题 | Date对象使用本地时区 | 时间显示可能不同 |
| 月份偏移 | getMonth()返回0-11 | 需要加1 |
| 闰年处理 | 系统自动处理闰年 | 2月天数正确 |
| 边界情况 | 月末、年末的日期 | 需要特殊处理 |
8.4 日期格式扩展
如果需要更详细的日期格式,可以扩展格式化:
// 格式化为 YYYY-MM-DD HH:mm:ss
let fullDate_1: string = now_1.getFullYear() + '-' +
String(now_1.getMonth() + 1).padStart(2, '0') + '-' +
String(now_1.getDate()).padStart(2, '0') + ' ' +
String(now_1.getHours()).padStart(2, '0') + ':' +
String(now_1.getMinutes()).padStart(2, '0') + ':' +
String(now_1.getSeconds()).padStart(2, '0');
九、历史记录展示
9.1 List列表实现
历史记录使用List列表展示:
List() {
ForEach(this.entries_1, (entry_1: DiaryEntry_1) => {
ListItem() {
Row() {
Column() {
Text(entry_1.mood_1)
.fontSize(32)
Text(entry_1.date_1)
.fontSize(12)
.fontColor('#999999')
.margin({ top: 4 })
}
.width(80)
Text(entry_1.text_1)
.fontSize(14)
.layoutWeight(1)
.padding({ left: 12 })
}
.padding(12)
.width('100%')
.backgroundColor('#F8F8F8')
.borderRadius(8)
.margin({ bottom: 8 })
}
})
}
.width('90%')
.margin({ top: 8 })
9.2 List组件详解
List是列表容器组件,用于展示列表数据:
List属性说明:
| 属性 | 值 | 说明 |
|---|---|---|
| width | ‘90%’ | 宽度为父容器90% |
| margin | { top: 8 } | 上边距8px |
9.3 ListItem组件详解
ListItem是List的子组件,代表列表中的一个项:
ListItem() {
Row() {
// 日记内容
}
.padding(12)
.width('100%')
.backgroundColor('#F8F8F8')
.borderRadius(8)
.margin({ bottom: 8 })
}
ListItem特点:
- 代表列表中的一个项
- 可以包含任意组件
- 支持滚动和懒加载
- 性能优化友好
9.4 日记卡片设计
日记卡片采用Row横向布局:
Row() {
Column() {
Text(entry_1.mood_1)
.fontSize(32)
Text(entry_1.date_1)
.fontSize(12)
.fontColor('#999999')
.margin({ top: 4 })
}
.width(80)
Text(entry_1.text_1)
.fontSize(14)
.layoutWeight(1)
.padding({ left: 12 })
}
卡片布局说明:
| 区域 | 内容 | 样式 |
|---|---|---|
| 左侧 | 表情 + 日期 | 固定宽度80px |
| 右侧 | 日记内容 | layoutWeight自适应 |
| 背景 | 灰色背景 | #F8F8F8 |
| 圆角 | 8px圆角 | borderRadius(8) |
9.5 layoutWeight详解
layoutWeight用于自适应布局:
Text(entry_1.text_1)
.layoutWeight(1)
layoutWeight说明:
| 属性 | 值 | 说明 |
|---|---|---|
| layoutWeight | 1 | 占据剩余空间 |
| 固定宽度 | 80 | 左侧固定宽度 |
| 自适应 | 剩余宽度 | 右侧自适应 |
9.6 卡片样式设计
日记卡片采用灰色背景和圆角设计:
| 样式 | 值 | 设计意图 |
|---|---|---|
| backgroundColor | #F8F8F8 | 浅灰色背景,卡片感 |
| borderRadius | 8 | 圆角设计,柔和视觉 |
| padding | 12 | 内边距,内容不贴边 |
| margin | { bottom: 8 } | 下边距,卡片间距 |
十、UI布局结构
10.1 整体布局结构
应用采用Column垂直布局作为根容器:
10.2 标题栏实现
标题栏采用Row横向布局:
Row() {
Button('返回')
.onClick(() => router.back())
Text('心情日记小应用')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.layoutWeight(1)
.textAlign(TextAlign.Center)
}
.width('100%')
.padding(12)
.backgroundColor('#F1F3F5')
10.3 心情提示实现
心情提示使用Text组件:
Text('今天心情怎么样?')
.fontSize(16)
.fontWeight(FontWeight.Medium)
.margin({ top: 24 })
10.4 响应式布局考虑
布局设计考虑了响应式特性:
| 特性 | 实现方式 | 说明 |
|---|---|---|
| 百分比宽度 | width(‘90%’) | 自适应屏幕宽度 |
| 布局权重 | layoutWeight(1) | 自适应剩余空间 |
| 弹性间距 | margin | 相对间距 |
| 自动换行 | FlexWrap.Wrap | 表情自动换行 |
十一、Flex布局详解
11.1 Flex基本概念
Flex是弹性布局容器,提供灵活的子组件排列方式:
Flex({ wrap: FlexWrap.Wrap }) {
// 子组件
}
11.2 Flex属性说明
| 属性 | 可选值 | 说明 |
|---|---|---|
| direction | Row, Column, RowReverse, ColumnReverse | 主轴方向 |
| wrap | NoWrap, Wrap, WrapReverse | 是否换行 |
| justifyContent | Start, Center, End, SpaceBetween, SpaceAround, SpaceEvenly | 主轴对齐 |
| alignItems | Start, Center, End, Stretch | 交叉轴对齐 |
11.3 FlexWrap.Wrap详解
FlexWrap.Wrap支持自动换行:
Flex({ wrap: FlexWrap.Wrap }) {
ForEach(this.moods_1, (mood_1: string) => {
Button(mood_1)
// ...
})
}
换行效果:
- 子组件超出容器宽度时自动换行
- 适合不确定数量的子组件
- 表情按钮可以自动排列
- 适应不同屏幕宽度
11.4 Flex与其他布局对比
| 布局 | 特点 | 适用场景 |
|---|---|---|
| Row | 横向排列,不换行 | 固定数量的子组件 |
| Column | 纵向排列 | 垂直布局 |
| Flex | 弹性排列,可换行 | 不确定数量的子组件 |
| Stack | 层叠排列 | 重叠显示 |
十二、数组操作详解
12.1 数组常用方法
本应用使用了多种数组操作方法:
| 方法 | 用途 | 示例 |
|---|---|---|
| concat | 添加元素 | [newEntry].concat(entries) |
| forEach | 遍历元素 | entries.forEach(entry => …) |
| filter | 过滤元素 | entries.filter(entry => …) |
12.2 concat方法详解
concat用于合并数组,创建新数组:
this.entries_1 = [newEntry_1].concat(this.entries_1);
concat特点:
| 特点 | 说明 | 优势 |
|---|---|---|
| 不修改原数组 | 创建新数组 | 状态变量更新正确 |
| 可以添加到开头 | [new].concat(old) | 新元素在最前 |
| 可以添加到末尾 | old.concat([new]) | 新元素在最后 |
12.3 添加到开头 vs 添加到末尾
| 方式 | 代码 | 效果 |
|---|---|---|
| 添加到开头 | [new].concat(old) | 新日记在最前 |
| 添加到末尾 | old.concat([new]) | 新日记在最后 |
为什么添加到开头:
- 用户习惯查看最新日记
- 最新日记优先展示
- 符合时间倒序排列
- 提升用户体验
12.4 数组操作最佳实践
在ArkUI中使用数组操作的最佳实践:
// 添加元素:使用concat
this.entries_1 = [newEntry_1].concat(this.entries_1);
// 删除元素:使用filter
this.entries_1 = this.entries_1.filter((_, i_1: number) => i_1 !== index_1);
// 修改元素:使用map
this.entries_1 = this.entries_1.map((entry_1, i_1: number) => {
if (i_1 === index_1) {
return new DiaryEntry_1(newDate_1, newMood_1, newText_1);
}
return entry_1;
});
十三、应用扩展方向
13.1 心情统计功能
添加心情统计,分析心情变化:
// 统计各心情出现次数
getMoodStatistics(): Record<string, number> {
let stats: Record<string, number> = {};
this.entries_1.forEach(entry_1 => {
if (stats[entry_1.mood_1] === undefined) {
stats[entry_1.mood_1] = 0;
}
stats[entry_1.mood_1]++;
});
return stats;
}
// 显示统计
ForEach(Object.keys(stats), (mood_1: string) => {
Row() {
Text(mood_1)
Text(String(stats[mood_1]))
}
})
13.2 心情日历功能
在日历上显示每天心情:
// 获取某天的心情
getMoodByDate(date: string): string {
let entry_1 = this.entries_1.find(entry => entry.date_1 === date);
return entry_1 ? entry_1.mood_1 : '';
}
// 日历展示
Calendar()
.onDateSelect((date: Date) => {
let mood_1 = this.getMoodByDate(formatDate(date));
// 显示心情
})
13.3 图片记录功能
支持添加图片到日记:
class EnhancedDiaryEntry {
date: string = '';
mood: string = '';
text: string = '';
images: string[] = []; // 图片列表
}
// 添加图片按钮
Button('添加图片')
.onClick(() => {
// 选择图片
// 添加到日记
})
// 显示图片
ForEach(entry.images, (image: string) => {
Image(image)
.width(100)
.height(100)
})
13.4 数据持久化
使用Preferences存储日记数据:
import preferences from '@ohos.data.preferences';
// 保存日记
async saveDiaries() {
let prefs = await preferences.getPreferences(context, 'mood_diary');
let diariesJson: string = JSON.stringify(this.entries_1);
await prefs.put('diaries', diariesJson);
await prefs.flush();
}
// 加载日记
async loadDiaries() {
let prefs = await preferences.getPreferences(context, 'mood_diary');
let diariesJson: string = await prefs.get('diaries', '[]') as string;
this.entries_1 = JSON.parse(diariesJson);
}
13.5 日记编辑功能
添加编辑已有日记的功能:
@State editingIndex_1: number = -1;
@State editingText_1: string = '';
// 编辑按钮
Button('编辑')
.onClick(() => {
this.editingIndex_1 = index_1;
this.editingText_1 = entry_1.text_1;
})
// 保存编辑
Button('保存')
.onClick(() => {
this.entries_1 = this.entries_1.map((entry_1, i_1: number) => {
if (i_1 === this.editingIndex_1) {
return new DiaryEntry_1(entry_1.date_1, entry_1.mood_1, this.editingText_1);
}
return entry_1;
});
this.editingIndex_1 = -1;
})
13.6 日记删除功能
添加删除日记的功能:
// 删除按钮
Button('删除')
.onClick(() => {
this.entries_1 = this.entries_1.filter((_, i_1: number) => i_1 !== index_1);
})
13.7 日记搜索功能
添加搜索日记的功能:
@State searchText_1: string = '';
// 搜索输入框
TextInput({ placeholder: '搜索日记...' })
.onChange((value_1: string) => {
this.searchText_1 = value_1;
})
// 搜索过滤
getFilteredEntries(): DiaryEntry_1[] {
if (this.searchText_1 === '') {
return this.entries_1;
}
return this.entries_1.filter(entry_1 =>
entry_1.text_1.includes(this.searchText_1)
);
}
十四、性能优化建议
14.1 渲染优化
- 使用唯一key:ForEach中提供唯一key,提高渲染效率
ForEach(this.entries_1, (entry_1: DiaryEntry_1, index_1: number) => {
ListItem() {
// ...
}
}, (entry_1: DiaryEntry_1, index_1: number) => entry_1.date_1)
- 条件渲染:避免渲染不必要的组件
if (this.entries_1.length > 0) {
List() {
// 日记列表
}
} else {
Text('暂无日记记录')
}
14.2 数据优化
- 限制日记数量:避免数据过多影响性能
if (this.entries_1.length < 100) {
this.entries_1 = [newEntry_1].concat(this.entries_1);
} else {
// 提示用户删除部分日记
}
- 分页加载:大数据量时分页展示
@State currentPage: number = 1;
pageSize: number = 20;
getCurrentPageEntries(): DiaryEntry_1[] {
let start: number = (this.currentPage - 1) * this.pageSize;
let end: number = start + this.pageSize;
return this.entries_1.slice(start, end);
}
14.3 内存管理
- 及时清理:删除日记时释放内存
- 避免重复创建:复用已有组件实例
- 合理使用状态变量:减少不必要的状态变量
14.4 用户体验优化
- 加载状态:显示加载动画
- 错误处理:友好的错误提示
- 操作反馈:保存成功后的视觉反馈
- 空状态提示:无日记时的友好提示
十五、完整代码清单
15.1 主组件完整代码
import { router } from '@kit.ArkUI';
class DiaryEntry_1 {
date_1: string = '';
mood_1: string = '';
text_1: string = '';
constructor(date_1: string, mood_1: string, text_1: string) {
this.date_1 = date_1;
this.mood_1 = mood_1;
this.text_1 = text_1;
}
}
@Entry
@Component
struct MoodDiaryApp {
@State selectedMood_1: string = '😊';
@State diaryText_1: string = '';
@State entries_1: DiaryEntry_1[] = [
new DiaryEntry_1('2024-06-15', '😊', '今天心情很好,天气也不错'),
new DiaryEntry_1('2024-06-14', '😐', '平平淡淡的一天'),
new DiaryEntry_1('2024-06-13', '😢', '有点难过,工作不太顺利')
];
moods_1: string[] = ['😊', '😄', '😍', '😐', '😔', '😢', '😤', '😴'];
build() {
Column() {
Row() {
Button('返回')
.onClick(() => router.back())
Text('心情日记小应用')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.layoutWeight(1)
.textAlign(TextAlign.Center)
}
.width('100%')
.padding(12)
.backgroundColor('#F1F3F5')
Column() {
Text('今天心情怎么样?')
.fontSize(16)
.fontWeight(FontWeight.Medium)
.margin({ top: 24 })
Flex({ wrap: FlexWrap.Wrap }) {
ForEach(this.moods_1, (mood_1: string) => {
Button(mood_1)
.width(56)
.height(56)
.fontSize(28)
.backgroundColor(this.selectedMood_1 === mood_1 ? '#E8F0FE' : '#F1F3F5')
.borderRadius(28)
.onClick(() => {
this.selectedMood_1 = mood_1;
})
.margin({ right: 8 })
})
}
.margin({ top: 12 })
TextInput({ placeholder: '写下今天的心情...' })
.width('90%')
.height(80)
.margin({ top: 20 })
.onChange((value_1: string) => {
this.diaryText_1 = value_1;
})
Button('保存日记')
.width('60%')
.height(40)
.margin({ top: 16 })
.backgroundColor('#0A59F7')
.onClick(() => {
let now_1: Date = new Date();
let date_1: string = now_1.getFullYear() + '-' +
String(now_1.getMonth() + 1).padStart(2, '0') + '-' +
String(now_1.getDate()).padStart(2, '0');
let newEntry_1 = new DiaryEntry_1(date_1, this.selectedMood_1, this.diaryText_1);
this.entries_1 = [newEntry_1].concat(this.entries_1);
this.diaryText_1 = '';
})
Text('历史记录')
.fontSize(16)
.fontWeight(FontWeight.Medium)
.margin({ top: 24, left: 20 })
.width('90%')
List() {
ForEach(this.entries_1, (entry_1: DiaryEntry_1) => {
ListItem() {
Row() {
Column() {
Text(entry_1.mood_1)
.fontSize(32)
Text(entry_1.date_1)
.fontSize(12)
.fontColor('#999999')
.margin({ top: 4 })
}
.width(80)
Text(entry_1.text_1)
.fontSize(14)
.layoutWeight(1)
.padding({ left: 12 })
}
.padding(12)
.width('100%')
.backgroundColor('#F8F8F8')
.borderRadius(8)
.margin({ bottom: 8 })
}
})
}
.width('90%')
.margin({ top: 8 })
}
.width('100%')
.layoutWeight(1)
}
.width('100%')
.height('100%')
.backgroundColor('#FFFFFF')
}
}
15.2 代码结构说明
| 代码区域 | 行数 | 功能说明 |
|---|---|---|
| 导入声明 | 1 | 导入router模块 |
| 数据模型 | 3-13 | 定义DiaryEntry类 |
| 状态声明 | 18-27 | 声明状态变量和表情数组 |
| build方法 | 29-128 | UI构建方法 |
十六、开发经验总结
16.1 技术要点回顾
通过开发这个心情日记应用,我们掌握了以下技术要点:
- 数据模型设计:使用class定义日记数据结构
- 状态管理:使用@State装饰器管理响应式数据
- 表情选择器:Flex布局 + ForEach循环渲染
- 日期处理:Date对象 + padStart格式化
- List列表:List + ListItem展示历史记录
- 数组操作:concat添加元素到开头
16.2 常见问题解决
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 日期格式错误 | getMonth()返回0-11 | 使用时加1 |
| 月份显示单数 | 未补零 | 使用padStart(2, ‘0’) |
| UI不更新 | 直接修改数组 | 使用concat创建新数组 |
| 表情不换行 | 使用Row布局 | 使用Flex + FlexWrap.Wrap |
16.3 最佳实践建议
- 数据管理:使用class定义数据模型,提高代码可维护性
- 状态更新:使用数组方法创建新数组,确保UI更新
- 日期处理:注意月份偏移和格式化补零
- 布局设计:合理使用Flex布局,支持自动换行
- 用户体验:提供温馨的界面设计,提升使用体验
十七、总结
心情日记小应用是一款温馨的记录工具,展示了HarmonyOS ArkUI框架在数据管理和UI布局方面的强大能力。应用采用了表情符号选择的设计理念,界面温馨简洁,操作便捷高效。
从技术实现角度来看,应用涵盖了自定义数据模型、状态管理、Flex布局、日期处理、List列表渲染、数组操作等核心技术点。通过学习这个应用的开发过程,开发者可以深入理解ArkTS语言的日期处理能力和列表渲染机制。
应用的核心价值在于帮助用户记录每日心情,回顾生活点滴。8种表情符号让心情表达更加直观,List列表让日记展示更加清晰。未来可以进一步扩展应用的功能,如添加心情统计、心情日历、图片记录、数据持久化、日记编辑、日记删除、日记搜索等特性,使其成为一个更加完善的心情记录工具。
这个应用也可以作为学习HarmonyOS开发的入门项目,帮助开发者快速掌握ArkTS和ArkUI的基础知识,理解日期处理和列表渲染机制。
更多推荐


所有评论(0)