前言待办列表的典型场景是用户有 20 条待办其中 5 条已经完成了。他需要能在全部待办和未完成和已完成三种视图之间快速切换。这是 GTD 方法论中聚焦当下的核心需求——已完成的条目不应干扰未完成事项的浏览。鸿蒙 Flutter 备忘录的待办模块实现了一个简洁的三态筛选器All / Active / Completed。本文将拆解从枚举定义、UI 切换、到 Provider 数据过滤的完整链路。项目仓库todo_flutter_harmony筛选枚举enumTodoFilter{all,// 全部active,// 进行中未完成completed,// 已完成}枚举比字符串更安全——编译器会检查switch的完整性IDE 也能提供自动补全。模型定义classTodo{finalint?id;finalStringtitle;finalString?note;finalbool isCompleted;finalDateTime?dueDate;finalDateTimecreatedAt;constTodo({this.id,requiredthis.title,this.note,this.isCompletedfalse,this.dueDate,requiredthis.createdAt,});// 是否已过期boolgetisOverdue{if(isCompleted)returnfalse;if(dueDatenull)returnfalse;returnDateTime.now().isAfter(dueDate!);}MapString,dynamictoMap(){id:id,title:title,note:note,isCompleted:isCompleted?1:0,// SQLite 兼容性dueDate:dueDate?.millisecondsSinceEpoch,createdAt:createdAt.millisecondsSinceEpoch,};factoryTodo.fromMap(MapString,dynamicmap)Todo(id:map[id],title:map[title]??,note:map[note],isCompleted:(map[isCompleted]??0)1,dueDate:map[dueDate]!null?DateTime.fromMillisecondsSinceEpoch(map[dueDate]):null,createdAt:DateTime.fromMillisecondsSinceEpoch(map[createdAt]),);}TodoProvider筛选逻辑classTodoProviderextendsChangeNotifier{ListTodo_todos[];TodoFilter_filterTodoFilter.all;ListTodogettodos_todos;TodoFiltergetfilter_filter;ListTodogetfilteredTodos{switch(_filter){caseTodoFilter.all:returnList.unmodifiable(_todos);caseTodoFilter.active:return_todos.where((t)!t.isCompleted).toList();caseTodoFilter.completed:return_todos.where((t)t.isCompleted).toList();}}// 各状态的数量intgettotalCount_todos.length;intgetactiveCount_todos.where((t)!t.isCompleted).length;intgetcompletedCount_todos.where((t)t.isCompleted).length;voidsetFilter(TodoFilternewFilter){_filternewFilter;notifyListeners();}voidloadTodos()async{_todosawaitDatabaseHelper.instance.getAllTodos();_todos.sort((a,b)b.createdAt.compareTo(a.createdAt));notifyListeners();}FuturevoidtoggleTodo(int id)async{finaltodo_todos.firstWhere((t)t.idid);finalupdatedtodo.copyWith(isCompleted:!todo.isCompleted);awaitDatabaseHelper.instance.updateTodo(updated);awaitloadTodos();}FuturevoidaddTodo(Todotodo)async{awaitDatabaseHelper.instance.insertTodo(todo);awaitloadTodos();}FuturevoiddeleteTodo(int id)async{awaitDatabaseHelper.instance.deleteTodo(id);awaitloadTodos();}}UI筛选切换器使用 Material 3 的SegmentedButton构建三态切换classTodoFilterBarextendsStatelessWidget{constTodoFilterBar({super.key});overrideWidgetbuild(BuildContextcontext){returnConsumerTodoProvider(builder:(context,provider,_){returnPadding(padding:constEdgeInsets.symmetric(horizontal:16,vertical:8),child:SegmentedButtonTodoFilter(segments:[ButtonSegmentTodoFilter(value:TodoFilter.all,label:Text(全部 (${provider.totalCount})),icon:constIcon(Icons.list,size:18),),ButtonSegmentTodoFilter(value:TodoFilter.active,label:Text(进行中 (${provider.activeCount})),icon:Icon(Icons.radio_button_unchecked,size:18,color:Colors.orange.shade600),),ButtonSegmentTodoFilter(value:TodoFilter.completed,label:Text(已完成 (${provider.completedCount})),icon:Icon(Icons.check_circle_outline,size:18,color:Colors.green.shade600),),],selected:{provider.filter},onSelectionChanged:(selected){provider.setFilter(selected.first);},style:ButtonStyle(visualDensity:VisualDensity.compact,),),);},);}}关键细节每个筛选项的 label 都带了实时数量——“全部 (15)”、“进行中 (10)”、“已完成 (5)”。这给用户在点击前就提供了信息参考。待办列表页classTodoListPageextendsStatelessWidget{overrideWidgetbuild(BuildContextcontext){returnColumn(children:[TodoFilterBar(),constDivider(height:1),Expanded(child:ConsumerTodoProvider(builder:(context,provider,_){finaltodosprovider.filteredTodos;if(todos.isEmpty){return_buildEmptyState(provider.filter);}returnListView.builder(itemCount:todos.length,itemBuilder:(context,index){returnAnimatedListItem(delay:index*50,child:_buildTodoItem(todos[index],provider),);},);},),),],);}Widget_buildEmptyState(TodoFilterfilter){Stringmessage;IconDataicon;switch(filter){caseTodoFilter.all:message暂无待办事项;iconIcons.inbox_outlined;break;caseTodoFilter.active:message所有待办已完成;iconIcons.celebration_outlined;break;caseTodoFilter.completed:message暂无已完成事项;iconIcons.checklist_outlined;break;}returnCenter(child:Column(mainAxisSize:MainAxisSize.min,children:[Icon(icon,size:72,color:Colors.grey.shade300),constSizedBox(height:12),Text(message,style:TextStyle(fontSize:16,color:Colors.grey.shade500,)),],),);}}三种筛选状态各有不同的空状态提示——进行中为空时显示庆祝图标比千篇一律的暂无数据友好得多。待办卡片Widget_buildTodoItem(Todotodo,TodoProviderprovider){returnSlideActionTile(leftActions:[SlideAction(label:删除,icon:Icons.delete_outline,color:Colors.red,onTap:()provider.deleteTodo(todo.id!),),],rightActions:[SlideAction(label:todo.isCompleted?撤销:完成,icon:todo.isCompleted?Icons.undo:Icons.check,color:todo.isCompleted?Colors.orange:Colors.green,onTap:()provider.toggleTodo(todo.id!),),],child:Card(elevation:1,shape:RoundedRectangleBorder(borderRadius:BorderRadius.circular(12)),child:Padding(padding:constEdgeInsets.all(14),child:Row(children:[// 完成状态复选框GestureDetector(onTap:()provider.toggleTodo(todo.id!),child:Icon(todo.isCompleted?Icons.check_circle:Icons.radio_button_unchecked,color:todo.isCompleted?constColor(0xFF4DB6AC):Colors.grey.shade400,size:24,),),constSizedBox(width:12),// 标题和备注Expanded(child:Column(crossAxisAlignment:CrossAxisAlignment.start,children:[Text(todo.title,style:TextStyle(fontSize:16,fontWeight:FontWeight.w500,decoration:todo.isCompleted?TextDecoration.lineThrough:null,color:todo.isCompleted?Colors.grey.shade500:Colors.black87,),),if(todo.dueDate!null)...[constSizedBox(height:4),Row(children:[Icon(Icons.calendar_today,size:13,color:todo.isOverdue?Colors.red:Colors.grey),constSizedBox(width:4),Text(DateFormat(MM-dd).format(todo.dueDate!),style:TextStyle(fontSize:12,color:todo.isOverdue?Colors.red:Colors.grey,fontWeight:todo.isOverdue?FontWeight.w600:FontWeight.normal,),),],),],],),),],),),),);}视觉细节已完成的待办标题加删除线 变灰过期的待办截止日期变红色加粗点击圆圈图标即可切换完成状态鸿蒙兼容性三态筛选器完全在 Flutter 层实现SegmentedButtonMaterial 3 组件TodoFilter枚举 filteredTodosgetter纯 Dart 逻辑Provider 响应式更新Flutter 框架层零原生依赖鸿蒙 OHOS 上直接可用。总结待办事项三态筛选器的实现可以浓缩为数据层TodoFilter枚举定义三种状态filteredTodosgetter 用where()做内存过滤UI 层Material 3SegmentedButton三段式切换带实时数量统计交互层空状态按筛选类型差异化展示复选框 删除线区分完成态整个筛选逻辑的核心只有 10 行代码却让待办列表的可用性上升了一个台阶。完整项目代码见todo_flutter_harmony