今天给大家分享一个基于HarmonyOS 6和ArkTS开发的待办清单应用案例。这个案例虽然看起来简单,但包含了HarmonyOS开发中的几个核心知识点:状态管理、条件渲染、样式动态绑定以及组件封装。我自己在实际开发过程中踩过不少坑,这次就把完整的实现过程和经验总结分享给大家。
这个待办清单的主要功能包括:
首先我们需要设计待办事项的数据结构。在ArkTS中,我们使用class来定义数据模型:
typescript复制class ToDoModel {
index: number;
content: string;
constructor(index: number, content: string) {
this.index = index;
this.content = content;
}
}
这里我特意添加了index字段而不是直接用content作为唯一标识,因为在真实应用中,可能会有相同内容的待办事项,用自增ID作为唯一标识更合理。
主页面组件负责管理整个待办事项列表的数据和状态:
typescript复制@Entry
@Component
struct ToDoListPage {
@State message: string = '待办列表';
//待办事项数据集合
@State dataList: Array<ToDoModel> = [
new ToDoModel(1, '取快递'),
new ToDoModel(2, '健身'),
new ToDoModel(3, '图书馆还书'),
new ToDoModel(4, '小米4S店提车'),
new ToDoModel(5, '办理交房手续'),
]
build() {
Column() {
Text(this.message)
.fontSize(50)
.fontWeight(FontWeight.Bold)
ForEach(this.dataList, (item: ToDoModel) => {
ToDoItem({ content: item.content })
}, (item: ToDoModel) => JSON.stringify(item))
}
.width('100%')
.height('100%')
.backgroundColor('#ffffff') // 页面背景颜色
}
}
几个关键点:
@State装饰器声明响应式数据,当数据变化时会触发UI更新ForEach用于遍历数据列表并渲染子组件提示:在实际项目中,建议将初始数据放在单独的文件中管理,或者从网络/本地存储加载。
待办事项子组件负责单个事项的展示和交互:
typescript复制@Component
struct ToDoItem {
private content?: string; //具体的待办项的内容
@State isComplete: boolean = false; //是否完成
build() {
Row() {
//if条件渲染,替换图标效果
if (this.isComplete) {
this.labelIcon($r('app.media.ic_ok'));
} else {
this.labelIcon($r('app.media.ic_default'));
}
//每一条待办事项,通过opacity属性设置虚化效果,decoration属性设置删除线效果
Text(this.content)
.fontSize('20fp')
.fontWeight(500)
.opacity(this.isComplete ? 0.4 : 1)
.decoration({ type: this.isComplete ? TextDecorationType.LineThrough : TextDecorationType.None })
}
.borderRadius(24)
.backgroundColor('#ffffff')
.width('95%')
.height('64vp')
.onClick(() => {
this.isComplete = !this.isComplete;
})
}
//抽离"是否完成按钮"的子组件,方便复用
@Builder
labelIcon(icon: Resource) {
Image(icon)
.width('28vp')
.height('28vp')
.margin('20vp')
}
}
这个组件有几个值得注意的技术点:
基础版本只支持查看和切换状态,实际应用中我们还需要添加新事项的功能。在主页面添加一个输入框和按钮:
typescript复制@Entry
@Component
struct ToDoListPage {
@State message: string = '待办列表';
@State dataList: Array<ToDoModel> = [...];
@State newItemContent: string = ''; // 新增:输入框内容
build() {
Column() {
Text(this.message)
.fontSize(50)
.fontWeight(FontWeight.Bold)
// 新增:输入框和添加按钮
Row() {
TextInput({ placeholder: '输入新待办事项' })
.width('70%')
.onChange((value: string) => {
this.newItemContent = value;
})
Button('添加')
.width('25%')
.onClick(() => {
if (this.newItemContent.trim()) {
this.dataList.push(new ToDoModel(this.dataList.length + 1, this.newItemContent));
this.newItemContent = '';
}
})
}
.width('95%')
.margin({ top: 20 })
ForEach(this.dataList, (item: ToDoModel) => {
ToDoItem({ content: item.content })
}, (item: ToDoModel) => JSON.stringify(item))
}
.width('100%')
.height('100%')
.backgroundColor('#ffffff')
}
}
目前数据只在内存中,应用关闭后会丢失。我们可以使用HarmonyOS的Preferences工具实现本地存储:
typescript复制import preferences from '@ohos.data.preferences';
typescript复制// 存储数据
async function saveToDoList(context: any, list: Array<ToDoModel>) {
try {
const pref = await preferences.getPreferences(context, 'todo_pref');
await pref.put('todo_list', JSON.stringify(list));
await pref.flush();
} catch (e) {
console.error(`保存失败: ${e}`);
}
}
// 加载数据
async function loadToDoList(context: any): Promise<Array<ToDoModel>> {
try {
const pref = await preferences.getPreferences(context, 'todo_pref');
const listStr = await pref.get('todo_list', '[]');
return JSON.parse(listStr);
} catch (e) {
console.error(`加载失败: ${e}`);
return [];
}
}
typescript复制@Entry
@Component
struct ToDoListPage {
// ...
aboutToAppear() {
loadToDoList(getContext(this)).then(list => {
if (list.length > 0) {
this.dataList = list;
}
});
}
// 在修改数据的地方调用saveToDoList
// 例如添加新事项后:
// this.dataList.push(...);
// saveToDoList(getContext(this), this.dataList);
}
当列表项很多时,需要注意渲染性能:
typescript复制ForEach(this.dataList, (item: ToDoModel) => {
ToDoItem({ content: item.content })
}, (item: ToDoModel) => item.index.toString()) // 使用index作为key
typescript复制// 在ToDoItem组件中添加以下装饰器
@Reusable
@Component
struct ToDoItem {
// ...
}
typescript复制// 需要实现数据源接口
class ToDoDataSource implements IDataSource {
// 实现必要的方法
}
// 使用LazyForEach
LazyForEach(new ToDoDataSource(), (item: ToDoModel) => {
ToDoItem({ content: item.content })
}, (item: ToDoModel) => item.index.toString())
如果遇到图标不显示的情况,检查以下几点:
resources/base/media/目录下$r('app.media.ic_ok')中的路径是否正确如果点击后状态没有变化,检查:
@State装饰器声明状态变量默认情况下Column不会滚动,如果需要滚动列表:
typescript复制Column() {
// ...列表内容
}
.scrollable(ScrollDirection.Vertical)
.scrollBar(BarState.On)
鸿蒙应用需要适配不同设备,建议:
vp作为尺寸单位而不是固定像素fp单位随着项目复杂度增加,良好的代码组织非常重要:
code复制src/main/ets/
├── components/ // 公共组件
│ └── ToDoItem.ets // 待办事项组件
├── model/ // 数据模型
│ └── ToDoModel.ets
├── utils/ // 工具函数
│ └── storage.ets // 存储相关工具
└── pages/ // 页面
└── ToDoList.ets // 主页面
在ToDoModel.ets中定义数据模型:
typescript复制export class ToDoModel {
index: number;
content: string;
completed: boolean; // 可以添加更多字段
constructor(index: number, content: string, completed: boolean = false) {
this.index = index;
this.content = content;
this.completed = completed;
}
}
在storage.ets中封装存储逻辑:
typescript复制import preferences from '@ohos.data.preferences';
export async function saveToDoList(context: any, list: Array<ToDoModel>): Promise<void> {
// ...实现存储逻辑
}
export async function loadToDoList(context: any): Promise<Array<ToDoModel>> {
// ...实现加载逻辑
}
为关键功能添加单元测试:
typescript复制// 测试ToDoModel
describe('ToDoModel', () => {
it('should create instance correctly', () => {
const item = new ToDoModel(1, '测试事项');
expect(item.index).assertEqual(1);
expect(item.content).assertEqual('测试事项');
});
});
使用DevEco Studio的预览器快速验证UI变化:
在真机上测试时注意:
如果想深入学习HarmonyOS开发,建议:
这个待办清单虽然简单,但涵盖了HarmonyOS开发的许多核心概念。我在实际开发中发现,良好的状态管理和组件设计是构建复杂应用的基础。建议从这个小项目开始,逐步扩展功能,比如添加分类、提醒、同步等功能,这样可以循序渐进地掌握HarmonyOS开发的各个方面。