Flutter鸿蒙原生开发-MVVM架构待办事项实战
随着鸿蒙系统的快速发展,越来越多的开发者开始关注鸿蒙原生应用开发。本文将带你深入理解如何在鸿蒙平台上使用ArkTS语言实现MVVM架构模式,构建一个功能完善的待办事项应用。通过本实战,你将掌握鸿蒙原生开发的核心概念和最佳实践。Model(模型层):负责数据结构和业务逻辑View(视图层):负责UI展示和用户交互ViewModel(视图模型层):连接Model和View的桥梁,处理数据转换和状态管理
Flutter 三方库 cached_network_image 的鸿蒙化适配与实战指南
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
前言
随着鸿蒙系统的快速发展,越来越多的开发者开始关注鸿蒙原生应用开发。本文将带你深入理解如何在鸿蒙平台上使用ArkTS语言实现MVVM架构模式,构建一个功能完善的待办事项应用。通过本实战,你将掌握鸿蒙原生开发的核心概念和最佳实践。
一、MVVM架构概述
1.1 什么是MVVM?
MVVM(Model-View-ViewModel)是一种软件架构模式,它将应用程序分为三个核心部分:
- Model(模型层):负责数据结构和业务逻辑
- View(视图层):负责UI展示和用户交互
- ViewModel(视图模型层):连接Model和View的桥梁,处理数据转换和状态管理
1.2 MVVM的优势
┌─────────────────────────────────────────────────────────┐
│ View Layer │
│ ┌─────────────────────────────────────────────────┐ │
│ │ TodoPage.ets - UI组件、用户交互、状态绑定 │ │
│ └─────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
│
│ 订阅/通知
▼
┌─────────────────────────────────────────────────────────┐
│ ViewModel Layer │
│ ┌─────────────────────────────────────────────────┐ │
│ │ TodoViewModel.ets - 状态管理、业务逻辑处理 │ │
│ └─────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
│
│ 数据操作
▼
┌─────────────────────────────────────────────────────────┐
│ Model Layer │
│ ┌─────────────────────────────────────────────────┐ │
│ │ TodoModel.ets - 数据模型、数据持久化 │ │
│ └─────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
二、项目结构设计
entry/src/main/ets/
├── models/
│ └── TodoModel.ets # 数据模型和仓库
├── viewmodels/
│ └── TodoViewModel.ets # 视图模型
├── pages/
│ ├── Index.ets # 启动页
│ └── TodoPage.ets # 待办事项主页面
└── entryability/
└── EntryAbility.ets # 应用入口
三、Model层实现
3.1 数据模型定义
export interface TodoModel {
id: string; // 唯一标识
title: string; // 标题
description: string; // 详细描述
isCompleted: boolean; // 完成状态
createdAt: number; // 创建时间戳
priority: TodoPriority; // 优先级
}
export enum TodoPriority {
low = 0, // 低优先级
medium = 1, // 中优先级
high = 2 // 高优先级
}
3.2 数据仓库实现
数据仓库负责数据的持久化存储,使用鸿蒙的Preferences API实现本地存储:
export class TodoRepository {
private static readonly TODOS_KEY = 'mvvm_todos_data';
private prefs: dataPreferences.Preferences | null = null;
async init(context: common.UIAbilityContext): Promise<void> {
this.prefs = await dataPreferences.getPreferences(context, 'mvvm_todo_store');
}
async getAllTodos(): Promise<TodoModel[]> {
if (!this.prefs) return [];
const todosJson = await this.prefs.get(TodoRepository.TODOS_KEY, '[]') as string;
return JSON.parse(todosJson) as TodoModel[];
}
async saveTodos(todos: TodoModel[]): Promise<void> {
if (!this.prefs) return;
await this.prefs.put(TodoRepository.TODOS_KEY, JSON.stringify(todos));
await this.prefs.flush();
}
async addTodo(todo: TodoModel): Promise<void> {
const todos = await this.getAllTodos();
todos.unshift(todo);
await this.saveTodos(todos);
}
async updateTodo(todo: TodoModel): Promise<void> {
const todos = await this.getAllTodos();
const index = todos.findIndex(t => t.id === todo.id);
if (index !== -1) {
todos[index] = todo;
await this.saveTodos(todos);
}
}
async deleteTodo(id: string): Promise<void> {
const todos = await this.getAllTodos();
const filtered = todos.filter(t => t.id !== id);
await this.saveTodos(filtered);
}
async clearCompleted(): Promise<void> {
const todos = await this.getAllTodos();
const filtered = todos.filter(t => !t.isCompleted);
await this.saveTodos(filtered);
}
}
四、ViewModel层实现
4.1 核心功能设计
ViewModel负责管理应用状态,处理业务逻辑,并通过观察者模式通知View更新:
export class TodoViewModel {
private _todos: TodoModel[] = [];
private _filter: TodoFilter = TodoFilter.all;
private _sortType: SortType = SortType.createdAt;
private _isLoading: boolean = true;
private _error: string = '';
private listeners: Set<() => void> = new Set();
// 计算属性:获取过滤和排序后的待办列表
get todos(): TodoModel[] {
return this.getFilteredAndSortedTodos();
}
// 统计数据
get totalCount(): number {
return this._todos.length;
}
get activeCount(): number {
return this._todos.filter((t: TodoModel) => !t.isCompleted).length;
}
get completedCount(): number {
return this._todos.filter((t: TodoModel) => t.isCompleted).length;
}
}
4.2 观察者模式实现
// 订阅状态变化
subscribe(listener: () => void): void {
this.listeners.add(listener);
}
// 取消订阅
unsubscribe(listener: () => void): void {
this.listeners.delete(listener);
}
// 通知所有监听者
private notifyListeners(): void {
this.listeners.forEach((listener: () => void) => listener());
}
4.3 过滤和排序逻辑
private getFilteredAndSortedTodos(): TodoModel[] {
let result: TodoModel[] = [];
for (let i = 0; i < this._todos.length; i++) {
result.push(this._todos[i]);
}
// 过滤逻辑
if (this._filter === TodoFilter.active) {
result = result.filter((t: TodoModel) => !t.isCompleted);
} else if (this._filter === TodoFilter.completed) {
result = result.filter((t: TodoModel) => t.isCompleted);
}
// 排序逻辑
if (this._sortType === SortType.createdAt) {
result.sort((a: TodoModel, b: TodoModel) => b.createdAt - a.createdAt);
} else if (this._sortType === SortType.priority) {
result.sort((a: TodoModel, b: TodoModel) => b.priority - a.priority);
} else if (this._sortType === SortType.title) {
result.sort((a: TodoModel, b: TodoModel) => a.title.localeCompare(b.title));
}
return result;
}
五、View层实现
5.1 页面状态管理
@Entry
@Component
struct TodoPage {
// 状态变量
@State todos: TodoModel[] = [];
@State filter: TodoFilter = TodoFilter.all;
@State sortType: SortType = SortType.createdAt;
@State isLoading: boolean = true;
@State totalCount: number = 0;
@State activeCount: number = 0;
@State completedCount: number = 0;
// 底部弹窗状态
@State showAddSheet: boolean = false;
@State showDeleteConfirm: boolean = false;
@State showClearConfirm: boolean = false;
// 输入状态
@State inputTitle: string = '';
@State inputDescription: string = '';
@State selectedPriority: TodoPriority = TodoPriority.medium;
private viewModel: TodoViewModel = todoViewModel;
}
5.2 生命周期管理
async aboutToAppear(): Promise<void> {
// 初始化数据仓库
const context = getContext(this) as common.UIAbilityContext;
await todoRepository.init(context);
// 订阅ViewModel状态变化
this.viewModel.subscribe((): void => this.updateState());
await this.viewModel.loadTodos();
}
aboutToDisappear(): void {
// 取消订阅,防止内存泄漏
this.viewModel.unsubscribe((): void => this.updateState());
}
updateState(): void {
this.todos = this.viewModel.todos;
this.filter = this.viewModel.filter;
this.sortType = this.viewModel.sortType;
this.isLoading = this.viewModel.isLoading;
this.totalCount = this.viewModel.totalCount;
this.activeCount = this.viewModel.activeCount;
this.completedCount = this.viewModel.completedCount;
}
5.3 UI组件构建
头部导航栏
@Builder
buildHeader(): void {
Row() {
Text('MVVM 待办事项')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor(Color.White)
Blank()
Row() {
Button() {
Text('排序')
.fontSize(14)
.fontColor(Color.White)
}
.backgroundColor(Color.Transparent)
.bindMenu(this.getSortMenuItems())
Button() {
Text('清除')
.fontSize(14)
.fontColor(Color.White)
}
.onClick((): void => {
if (this.completedCount === 0) {
promptAction.showToast({ message: '没有已完成的待办事项' });
} else {
this.showClearConfirm = true;
}
})
}
}
.width('100%')
.padding({ left: 16, right: 16, top: 12, bottom: 12 })
.backgroundColor('#007DFF')
}
过滤标签栏
@Builder
buildFilterTabs(): void {
Row() {
ForEach(this.filterItems, (item: FilterItem): void => {
Column() {
Text(item.label)
.fontSize(15)
.fontColor(this.filter === item.filter ? '#007DFF' : '#666666')
.fontWeight(this.filter === item.filter ? FontWeight.Bold : FontWeight.Normal)
if (this.filter === item.filter) {
Divider()
.width(40)
.height(3)
.color('#007DFF')
.margin({ top: 8 })
}
}
.layoutWeight(1)
.padding({ top: 12, bottom: 12 })
.onClick((): void => this.viewModel.setFilter(item.filter))
}, (item: FilterItem): string => item.label)
}
.width('100%')
.backgroundColor(Color.White)
}
待办事项列表项
@Builder
buildTodoItem(todo: TodoModel): void {
Row() {
Checkbox()
.select(todo.isCompleted)
.selectedColor('#007DFF')
.onChange((): void => { this.viewModel.toggleTodo(todo.id) })
Column() {
Text(todo.title)
.fontSize(16)
.fontWeight(FontWeight.Medium)
.fontColor(todo.isCompleted ? '#999999' : '#333333')
.decoration({
type: todo.isCompleted ? TextDecorationType.LineThrough : TextDecorationType.None
})
if (todo.description) {
Text(todo.description)
.fontSize(14)
.fontColor(todo.isCompleted ? '#CCCCCC' : '#999999')
.margin({ top: 4 })
}
}
.layoutWeight(1)
Text(getPriorityLabel(todo.priority))
.fontSize(12)
.fontColor(getPriorityColor(todo.priority))
.backgroundColor(getPriorityColor(todo.priority) + '20')
.borderRadius(4)
Button() {
Text('删除')
.fontSize(14)
.fontColor('#FF4444')
}
.backgroundColor(Color.Transparent)
.onClick((): void => {
this.deleteTodoId = todo.id;
this.showDeleteConfirm = true;
})
}
.width('100%')
.padding(16)
.backgroundColor(Color.White)
.borderRadius(12)
.margin({ top: 8 })
.shadow({ radius: 4, color: '#00000010', offsetX: 0, offsetY: 2 })
}
5.4 底部弹窗实现
使用 bindSheet 实现添加待办事项的底部弹窗:
build() {
Column() {
this.buildHeader()
this.buildFilterTabs()
this.buildStatsBar()
if (this.isLoading && this.todos.length === 0) {
this.buildLoadingState()
} else if (this.todos.length === 0) {
this.buildEmptyState()
} else {
this.buildTodoList()
}
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5')
.bindSheet($$this.showAddSheet, this.buildAddSheet(), {
height: 450,
backgroundColor: Color.White,
dragBar: true
})
.bindSheet($$this.showDeleteConfirm, this.buildDeleteConfirmSheet(), {
height: 200,
backgroundColor: Color.White,
dragBar: false
})
}
六、ArkTS编译注意事项
在鸿蒙ArkTS开发中,需要注意以下编译规则:
6.1 对象字面量类型声明
// ❌ 错误:对象字面量必须有明确的类型声明
.bindMenu([
{ value: '排序', action: () => {} }
])
// ✅ 正确:定义接口并使用
interface MenuItem {
value: string;
action: () => void;
}
private getSortMenuItems(): MenuItem[] {
const items: MenuItem[] = [
{ value: '按创建时间', action: (): void => { this.viewModel.setSortType(SortType.createdAt) } }
];
return items;
}
6.2 异步函数返回类型
// ❌ 错误:箭头函数隐式返回Promise<void>
.onChange((): void => this.viewModel.toggleTodo(todo.id))
// ✅ 正确:使用大括号避免隐式返回
.onChange((): void => { this.viewModel.toggleTodo(todo.id) })
6.3 ForEach key函数
// ✅ 正确:key函数必须返回string类型
ForEach(this.todos, (todo: TodoModel): void => {
ListItem() {
this.buildTodoItem(todo)
}
}, (todo: TodoModel): string => todo.id)
七、应用功能展示
7.1 核心功能
| 功能 | 描述 |
|---|---|
| 添加待办 | 支持标题、描述、优先级设置 |
| 完成状态 | 点击复选框切换完成状态 |
| 删除待办 | 滑动或点击删除按钮 |
| 过滤筛选 | 全部/进行中/已完成 |
| 排序功能 | 按时间/优先级/标题排序 |
| 批量清除 | 一键清除已完成事项 |
| 数据持久化 | 本地存储,应用重启数据不丢失 |
7.2 界面预览
┌────────────────────────────────────┐
│ MVVM 待办事项 [排序] [清除] │ ← 头部导航
├────────────────────────────────────┤
│ 全部 进行中 已完成 │ ← 过滤标签
├────────────────────────────────────┤
│ ● 总计: 5 ● 进行中: 3 ● 已完成: 2│ ← 统计栏
├────────────────────────────────────┤
│ ☐ 完成项目文档 [高] [删除]│
│ 详细描述内容... │ ← 待办列表
│ ☑ 提交代码审查 [中] [删除]│
│ ☐ 修复Bug [低] [删除]│
│ │
└────────────────────────────────────┘

八、总结
通过本实战项目,我们学习了:
- MVVM架构在鸿蒙原生开发中的应用
- ArkTS语言的特性和编译规则
- 数据持久化存储方案
- 组件化UI开发模式
- 状态管理和观察者模式
这个待办事项应用展示了鸿蒙原生开发的完整流程,从数据模型设计到UI实现,涵盖了移动应用开发的核心知识点。希望本文能帮助你更好地理解鸿蒙原生开发,为你的项目开发提供参考。
九、参考资料
作者简介:专注于鸿蒙原生开发和Flutter跨平台技术,致力于分享移动开发最佳实践。
版权声明:本文为原创文章,转载请注明出处。
更多推荐


所有评论(0)