
构建独立任务表单页面与完善编辑体验
-
- 引言:从"能用"到"好用",再到"专业"
- 一、为什么需要独立表单页面?
- [二、架构扩展:支持编辑模式的 ViewModel 设计](#二、架构扩展:支持编辑模式的 ViewModel 设计)
-
- [1. 扩展 `Todo` 模型(隐含变更)](#1. 扩展
Todo模型(隐含变更)) - [2. ViewModel 新增 `updateTodo` 方法](#2. ViewModel 新增
updateTodo方法)
- [1. 扩展 `Todo` 模型(隐含变更)](#1. 扩展
- [三、表单页面实现:`TodoFormScreen` 的核心逻辑](#三、表单页面实现:
TodoFormScreen的核心逻辑) -
- [1. 页面结构与状态管理](#1. 页面结构与状态管理)
- [2. 表单验证与实时反馈](#2. 表单验证与实时反馈)
- [3. 日期与时间选择器集成](#3. 日期与时间选择器集成)
- [4. 优先级选择器(Chip 样式)](#4. 优先级选择器(Chip 样式))
- 四、导航与状态同步:确保体验连贯
-
- [1. 从列表页跳转到表单页](#1. 从列表页跳转到表单页)
- [2. 表单页返回结果](#2. 表单页返回结果)
- [五、OpenHarmony 适配考量](#五、OpenHarmony 适配考量)
- 六、工程实践与测试策略
-
- [1. 遵循开发指南](#1. 遵循开发指南)
- [2. 可测试性设计](#2. 可测试性设计)
- [3. 代码质量](#3. 代码质量)
- 结语:功能深化是架构韧性的试金石

引言:从"能用"到"好用",再到"专业"
在完成基础交互优化后,待办事项应用已具备良好的视觉表现和流畅的操作反馈。然而,一个真正实用的任务管理工具,不能仅依赖快速输入框------用户需要更精细的控制能力:设置截止时间、指定优先级、编写详细描述。这正是本次迭代的核心目标。
通过引入独立的任务表单页面,我们不仅提升了功能完整性,更进一步验证了 MVVM 架构在复杂场景下的扩展能力。本文将深入剖析表单页面的设计与实现,重点探讨:
- 如何在保持架构清晰的前提下支持添加与编辑双模式
- 如何实现跨平台兼容的日期/时间选择器集成
- 如何通过表单验证保障数据质量
- 如何设计导航与状态同步机制,确保用户体验连贯
所有实践均以 OpenHarmony 平台兼容性为前提,为未来深度集成奠定坚实基础。
一、为什么需要独立表单页面?
虽然快速输入(一行 TextField + Add 按钮)适合简单场景,但它存在明显局限:
- 无法设置任务截止时间
- 无法指定优先级
- 标题长度受限,无法输入详细说明
- 缺乏输入验证,易产生无效数据
独立表单页面的价值:
- ✅ 提供完整任务属性编辑能力
- ✅ 支持复杂输入组件(日期、时间、选择器)
- ✅ 实现实时表单验证,提升数据质量
- ✅ 符合用户对"专业工具"的心理预期
更重要的是,它迫使我们思考页面间数据流 与状态同步问题,这是构建多页面应用的关键挑战。
二、架构扩展:支持编辑模式的 ViewModel 设计
1. 扩展 Todo 模型(隐含变更)
虽然变更记录未明确提及,但为支持新功能,Todo 模型需增强:
dart
// lib/models/todo.dart (扩展后)
class Todo {
final String id;
final String title;
final bool completed;
final DateTime? createdAt;
final DateTime? dueDate; // 新增:截止日期
final int priority; // 新增:优先级 (0=低, 1=中, 2=高)
Todo({
required this.id,
required this.title,
this.completed = false,
this.createdAt,
this.dueDate,
this.priority = 1, // 默认中等优先级
});
// ... copyWith 需同步更新
Todo copyWith({
String? title,
bool? completed,
DateTime? dueDate,
int? priority,
}) {
return Todo(
id: id,
title: title ?? this.title,
completed: completed ?? this.completed,
createdAt: createdAt,
dueDate: dueDate ?? this.dueDate,
priority: priority ?? this.priority,
);
}
}
设计原则 :模型变更不影响现有 Repository 接口,因
toJson/fromJson可自动处理新增字段。
2. ViewModel 新增 updateTodo 方法
dart
// lib/viewmodels/todo_notifier.dart
Future<void> updateTodo(Todo updatedTodo) async {
await ref.read(todoRepositoryProvider).updateTodo(updatedTodo);
// 局部更新状态,避免全量重载
final todos = state.value!;
final index = todos.indexWhere((t) => t.id == updatedTodo.id);
if (index != -1) {
state = AsyncData([...todos]..[index] = updatedTodo);
}
}
关键点 :与
addTodo不同,updateTodo直接替换列表中的对象,效率更高。
三、表单页面实现:TodoFormScreen 的核心逻辑
1. 页面结构与状态管理
dart
// lib/screens/todo_form_screen.dart
class TodoFormScreen extends ConsumerStatefulWidget {
final Todo? initialTodo; // null 表示新建,非 null 表示编辑
const TodoFormScreen({super.key, this.initialTodo});
@override
ConsumerState<TodoFormScreen> createState() => _TodoFormScreenState();
}
class _TodoFormScreenState extends ConsumerState<TodoFormScreen> {
late final TextEditingController _titleController;
late final FocusNode _titleFocusNode;
late Todo _currentTodo;
@override
void initState() {
super.initState();
_currentTodo = widget.initialTodo ?? Todo(id: '', title: '');
_titleController = TextEditingController(text: _currentTodo.title);
_titleFocusNode = FocusNode()..requestFocus();
}
@override
void dispose() {
_titleController.dispose();
_titleFocusNode.dispose();
super.dispose();
}
双模式支持 :通过
initialTodo参数区分新建/编辑,复用同一套 UI 逻辑。
2. 表单验证与实时反馈
dart
String? _validateTitle(String? value) {
if (value == null || value.isEmpty) return 'Title is required';
if (value.length < 2) return 'Title must be at least 2 characters';
if (value.length > 200) return 'Title cannot exceed 200 characters';
return null;
}
// 在 TextFormField 中使用
TextFormField(
controller: _titleController,
focusNode: _titleFocusNode,
validator: _validateTitle,
onChanged: (value) {
_currentTodo = _currentTodo.copyWith(title: value);
},
decoration: InputDecoration(
labelText: 'Task Title',
errorText: _validateTitle(_titleController.text), // 实时显示错误
),
)
优势:用户输入时即时反馈,避免提交时才发现错误。
3. 日期与时间选择器集成
dart
ElevatedButton.icon(
onPressed: () async {
final picked = await showDatePicker(
context: context,
initialDate: _currentTodo.dueDate ?? DateTime.now(),
firstDate: DateTime(2020),
lastDate: DateTime(2030),
);
if (picked != null) {
// 再选择时间
final time = await showTimePicker(
context: context,
initialTime: TimeOfDay.now(),
);
if (time != null) {
final dateTime = DateTime(
picked.year,
picked.month,
picked.day,
time.hour,
time.minute,
);
setState(() {
_currentTodo = _currentTodo.copyWith(dueDate: dateTime);
});
}
}
},
icon: Icon(Icons.calendar_today),
label: Text(_currentTodo.dueDate != null
? '${_currentTodo.dueDate!.month}/${_currentTodo.dueDate!.day} ${_currentTodo.dueDate!.hour}:${_currentTodo.dueDate!.minute.toString().padLeft(2, '0')}'
: 'Set Due Date'),
)
OpenHarmony 兼容性 :
showDatePicker和showTimePicker是 Flutter 官方组件,在 OpenHarmony 上通过 Skia 渲染,无需原生桥接。
4. 优先级选择器(Chip 样式)
dart
Wrap(
spacing: 8,
children: [
for (final level in [0, 1, 2])
ChoiceChip(
label: Text(['Low', 'Medium', 'High'][level]),
selected: _currentTodo.priority == level,
onSelected: (selected) {
if (selected) {
setState(() {
_currentTodo = _currentTodo.copyWith(priority: level);
});
}
},
backgroundColor: Colors.grey[200],
selectedColor: Theme.of(context).colorScheme.primary.withOpacity(0.2),
),
],
)
UI 一致性:采用 Chip 组件,与主页面的统计芯片风格统一。
四、导航与状态同步:确保体验连贯
1. 从列表页跳转到表单页
dart
// lib/screens/todo_screen.dart
// 添加"详细添加"按钮
ElevatedButton.icon(
onPressed: () async {
final result = await Navigator.push<bool>(
context,
MaterialPageRoute(builder: (_) => const TodoFormScreen()),
);
if (result == true) {
// 刷新列表
ref.read(todoListProvider.notifier).refresh();
}
},
icon: Icon(Icons.add),
label: Text('Add Detailed'),
)
// 任务卡片添加编辑按钮
IconButton(
icon: Icon(Icons.edit),
onPressed: () async {
final result = await Navigator.push<bool>(
context,
MaterialPageRoute(
builder: (_) => TodoFormScreen(initialTodo: todo),
),
);
if (result == true) {
ref.read(todoListProvider.notifier).refresh();
}
},
)
2. 表单页返回结果
dart
// lib/screens/todo_form_screen.dart
ElevatedButton(
onPressed: () async {
if (_formKey.currentState?.validate() ?? false) {
final todo = _currentTodo.copyWith(
id: _currentTodo.id.isEmpty
? DateTime.now().millisecondsSinceEpoch.toString()
: _currentTodo.id,
title: _titleController.text.trim(),
);
if (widget.initialTodo == null) {
await ref.read(todoListProvider.notifier).addTodo(todo.title);
} else {
await ref.read(todoListProvider.notifier).updateTodo(todo);
}
if (!mounted) return;
Navigator.pop(context, true); // 返回 true 表示需要刷新
}
},
child: Text(widget.initialTodo == null ? 'Create' : 'Update'),
)
关键机制:
- 使用
Navigator.push<bool>传递布尔结果- 调用
refresh()确保列表最新- 自动收起键盘(因页面 pop)
五、OpenHarmony 适配考量
尽管当前仍在标准 Flutter 环境开发,但所有设计均考虑 OpenHarmony 特性:
- 无平台特定 API:未使用 Android/iOS 原生选择器
- 触摸与键盘友好:表单支持外接键盘输入(车机/平板场景)
- 响应式布局:在大屏设备上自动调整表单宽度
- 无障碍支持:所有交互元素均有语义标签
未来扩展 :若 OpenHarmony 提供专属日期选择器,只需在
Repository或Utils层封装调用,不影响表单逻辑。
六、工程实践与测试策略
1. 遵循开发指南
- 先更新变更记录文档
- 新增文件严格按 MVVM 分层
- 运行
build_runner生成代码
2. 可测试性设计
Todo模型的copyWith易于单元测试- 表单验证逻辑可独立测试
- ViewModel 的
updateTodo方法可通过 Mock Repository 验证
3. 代码质量
- 使用
flutter analyze确保无警告 - 表单状态集中管理,避免分散的
setState
结语:功能深化是架构韧性的试金石
本次任务表单页面的引入,是对项目架构的一次重要考验。令人欣慰的是,Riverpod + MVVM 架构轻松应对了复杂度的提升:
- 新增功能未破坏原有分层
- 双模式(新建/编辑)通过参数化实现,避免代码重复
- 导航与状态同步机制简洁可靠
这证明了前期架构投入的价值------它不仅支撑了当前需求,更为未来集成 Hive 持久化、OpenHarmony 通知、分布式任务同步 等高级功能预留了清晰路径。
欢迎加入开源鸿蒙跨平台社区: https://openharmonycrossplatform.csdn.net