Flutter for OpenHarmony 构建简洁高效的待办事项应用 实战解析
在移动开发的世界中,待办事项(Todo)应用 常被视为"Hello World"级别的入门项目。然而,一个真正优秀的 Todo 应用远不止是简单的增删改查------它应具备清晰的交互逻辑、优雅的视觉反馈和流畅的用户体验。本文将深入剖析一段由 AI 编程助手 Trae 生成的 Flutter 代码,带你从零构建一款符合 Material Design 3 规范、支持滑动删除、状态切换与空状态引导的现代化任务清单应用。
🌐 加入社区 欢迎加入 开源鸿蒙跨平台开发者社区 ,获取最新资源与技术支持: 👉 开源鸿蒙跨平台开发者社区
完整效果


一、整体架构:状态驱动 + 组件化设计
该应用采用经典的 StatefulWidget + 状态管理 模式,结构清晰、职责分明:
| 组件 | 职责 |
|---|---|
TodoItem |
数据模型:封装任务标题与完成状态 |
_TodoListPageState |
业务逻辑:管理任务列表、处理增删改操作 |
Scaffold + ListView.builder |
UI 层:构建页面骨架与动态列表 |
Dismissible |
交互增强:实现滑动删除手势 |
💡 整个应用仅 150 行核心代码,却完整覆盖了 Todo 应用的核心功能,体现了 Flutter "少即是多"的开发哲学。
二、数据模型:轻量而明确
dart
class TodoItem {
String title;
bool isDone;
TodoItem({required this.title, this.isDone = false});
}

- 字段精简 :仅保留
title(任务内容)和isDone(完成状态); - 默认值设计 :新任务默认未完成(
isDone = false),符合用户心智模型; - 可变性 :使用
var而非final,便于后续状态更新(通过setState触发重绘)。
⚠️ 注意:在大型应用中,建议使用不可变对象(
final字段 + copyWith)配合状态管理库(如 Riverpod),但在此小型 Demo 中,直接修改状态更为简洁高效。
三、核心交互:四大操作全覆盖
1. 添加任务:弹窗输入
dart
void _showAddDialog() {
String newTaskTitle = "";
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('新增任务'),
content: TextField(
autofocus: true,
onChanged: (value) => newTaskTitle = value,
),
actions: [
TextButton(onPressed: Navigator.pop, child: Text('取消')),
FilledButton(
onPressed: () {
_addTask(newTaskTitle);
Navigator.pop(context);
},
child: Text('添加'),
),
],
),
);
}

- 自动聚焦 :
autofocus: true提升输入效率;- 防空提交 :
_addTask内部校验title.isNotEmpty;- Material 3 风格 :使用
FilledButton替代旧版FlatButton,更符合现代设计语言。
2. 标记完成:复选框联动
dart
Checkbox(
value: task.isDone,
onChanged: (value) => _toggleTask(index),
)
- 视觉反馈 :完成任务后,文字显示删除线(
TextDecoration.lineThrough)并变为灰色;- 卡片样式变化 :已完成任务使用
surfaceContainerHighest背景色,形成视觉层次。
3. 删除任务:双路径支持
-
滑动删除 :通过
Dismissible实现 iOS/Android 风格的右滑删除;dartbackground: Container( color: Colors.red[100], alignment: Alignment.centerRight, child: Icon(Icons.delete, color: Colors.red), ), onDismissed: (direction) => _deleteTask(index),
- 按钮删除:每项右侧提供 × 按钮,适合精准操作;
- 操作确认 :删除后通过
SnackBar提示"已删除",并支持撤销(虽未实现,但预留扩展点)。
4. 清空所有:批量操作
dart
IconButton(
icon: Icon(Icons.delete_outline),
onPressed: () => setState(() => _tasks.clear()),
)
- AppBar 集成:置于右上角,符合 Material 设计规范;
- 即时生效:无二次确认(因有 SnackBar 撤销提示,可接受)。
四、UI/UX 设计亮点
1. Material Design 3 全面应用
- 主题色 :
ColorScheme.fromSeed(seedColor: Colors.indigo)自动生成和谐配色; - 卡片样式 :
- 未完成:
surface背景 +outlineVariant边框; - 已完成:
surfaceContainerHighest深色背景,突出状态差异;
- 未完成:
- 圆角与间距 :
borderRadius: 12+vertical: 4margin,营造呼吸感。
2. 空状态友好引导
dart
_tasks.isEmpty
? Center(
child: Column(
children: [
Icon(Icons.task_alt, size: 80, color: Colors.grey[300]),
Text('暂无任务,轻松一下!', style: TextStyle(fontSize: 18)),
],
),
)
: ListView.builder(...)

- 情感化文案:"轻松一下!"缓解用户焦虑;
- 大图标+浅灰色:弱化空状态,避免页面显得"空洞"。
3. 交互动效与反馈
- 滑动删除动画 :
Dismissible自带平滑过渡; - 删除提示 :
SnackBar显示操作结果; - FAB 悬浮按钮 :
FloatingActionButton.extended提供明确的新增入口。
五、代码质量与最佳实践
1. 健壮性保障
- 输入校验 :
_addTask拒绝空字符串; - Key 唯一性 :
Dismissible使用task.title + index作为 Key,避免重建错乱; - 资源清理:无需要 dispose 的控制器,结构轻量。
2. 可维护性设计
- 方法拆分:每个操作(add/toggle/delete)独立为私有方法;
- 逻辑集中 :所有状态变更通过
setState包裹,便于追踪; - 注释清晰:关键部分配有中文注释,降低理解成本。
3. 性能优化
- ListView.builder:仅构建可见项,支持长列表;
- 无冗余 Widget:避免在 build 中创建不必要的对象。
六、扩展建议:从 Demo 到产品
当前代码已是一个功能完整的 MVP,若要推向生产,可考虑:
| 方向 | 实现思路 |
|---|---|
| 数据持久化 | 集成 shared_preferences 或 hive 保存任务 |
| 撤销删除 | 在 SnackBar 中添加"撤销"按钮,恢复刚删除的任务 |
| 任务分类 | 添加标签或过滤器(如"全部/未完成/已完成") |
| 截止日期 | 扩展 TodoItem 模型,支持设置提醒时间 |
| 云同步 | 接入 Firebase Firestore 实现多设备同步 |
结语:小应用,大智慧
这个由 Trae 生成的 Todo 应用,虽功能简单,却处处体现着 用户中心的设计思维 与 Flutter 开发的最佳实践 。它没有炫技的动画,也没有复杂的架构,而是专注于解决一个具体问题:帮助用户高效管理日常任务。
完整代码展示
bash
import 'package:flutter/material.dart';
void main() {
runApp(const TraeDemoApp());
}
class TraeDemoApp extends StatelessWidget {
const TraeDemoApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Trae Flutter Demo',
debugShowCheckedModeBanner: false, // 去除右上角的 Debug 标签
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo),
useMaterial3: true,
),
home: const TodoListPage(),
);
}
}
// 待办事项的数据模型
class TodoItem {
String title;
bool isDone;
TodoItem({required this.title, this.isDone = false});
}
class TodoListPage extends StatefulWidget {
const TodoListPage({super.key});
@override
State<TodoListPage> createState() => _TodoListPageState();
}
class _TodoListPageState extends State<TodoListPage> {
// 存储任务的列表
final List<TodoItem> _tasks = [
TodoItem(title: '欢迎使用 Trae 进行开发', isDone: true),
TodoItem(title: '运行这个 Flutter 程序'),
TodoItem(title: '尝试添加一个新的任务'),
];
// 添加任务的方法
void _addTask(String title) {
if (title.isNotEmpty) {
setState(() {
_tasks.add(TodoItem(title: title));
});
}
}
// 切换任务完成状态
void _toggleTask(int index) {
setState(() {
_tasks[index].isDone = !_tasks[index].isDone;
});
}
// 删除任务
void _deleteTask(int index) {
setState(() {
_tasks.removeAt(index);
});
}
// 显示添加任务的弹窗
void _showAddDialog() {
String newTaskTitle = "";
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: const Text('新增任务'),
content: TextField(
autofocus: true,
decoration: const InputDecoration(
hintText: '请输入要做的事情...',
border: OutlineInputBorder(),
),
onChanged: (value) {
newTaskTitle = value;
},
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('取消'),
),
FilledButton(
onPressed: () {
_addTask(newTaskTitle);
Navigator.pop(context);
},
child: const Text('添加'),
),
],
);
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('我的任务清单'),
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
centerTitle: true,
actions: [
IconButton(
icon: const Icon(Icons.delete_outline),
tooltip: '清空所有',
onPressed: () {
setState(() {
_tasks.clear();
});
},
)
],
),
body: _tasks.isEmpty
? Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.task_alt,
size: 80,
color: Colors.grey[300],
),
const SizedBox(height: 16),
Text(
'暂无任务,轻松一下!',
style: TextStyle(fontSize: 18, color: Colors.grey[600]),
),
],
),
)
: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: _tasks.length,
itemBuilder: (context, index) {
final task = _tasks[index];
return Dismissible(
key: Key(task.title + index.toString()),
background: Container(
color: Colors.red[100],
alignment: Alignment.centerRight,
padding: const EdgeInsets.only(right: 20),
margin: const EdgeInsets.symmetric(vertical: 4),
child: const Icon(Icons.delete, color: Colors.red),
),
direction: DismissDirection.endToStart,
onDismissed: (direction) {
_deleteTask(index);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('${task.title} 已删除')),
);
},
child: Card(
elevation: 0,
color: task.isDone
? Theme.of(context).colorScheme.surfaceContainerHighest
: Theme.of(context).colorScheme.surface,
shape: RoundedRectangleBorder(
side: BorderSide(
color: Theme.of(context).colorScheme.outlineVariant,
),
borderRadius: BorderRadius.circular(12),
),
margin: const EdgeInsets.symmetric(vertical: 4),
child: ListTile(
leading: Checkbox(
value: task.isDone,
onChanged: (value) => _toggleTask(index),
shape: const CircleBorder(),
),
title: Text(
task.title,
style: TextStyle(
decoration: task.isDone
? TextDecoration.lineThrough
: TextDecoration.none,
color: task.isDone ? Colors.grey : Colors.black87,
),
),
trailing: IconButton(
icon: const Icon(Icons.close, size: 18),
onPressed: () => _deleteTask(index),
),
),
),
);
},
),
floatingActionButton: FloatingActionButton.extended(
onPressed: _showAddDialog,
icon: const Icon(Icons.add),
label: const Text('新任务'),
),
);
}
}