鸿蒙Flutter实战:日期选择器与截止日期高亮提醒
前言待办事项的核心价值是在截止时间前完成。一个没有截止日期的待办只是想法有了截止日期才是承诺。Flutter 内置的showDatePicker可以直接弹出 Material 3 风格的日期选择器配合intl包的日期格式化就能构建一套完整的截止日期系统。鸿蒙 Flutter 备忘录的待办模块允许为每条待办设置截止日期并在列表中自动标记已逾期的条目——截至日期超过今天该行文字变为红色加粗提示。项目仓库todo_flutter_harmony依赖dependencies:intl:^0.20.2intl包提供DateFormat类用于日期的格式化和本地化。它是 Dart 官方维护的国际化核心库。日期选择器的调用Flutter 内置的showDatePicker返回一个FutureDateTime?——用户选择日期后返回所选日期点取消则返回 nullFuturevoid_pickDueDate()async{finalnowDateTime.now();finalpickedawaitshowDatePicker(context:context,initialDate:_dueDate??now,firstDate:now,// 最早可选今天lastDate:DateTime(now.year5),// 最晚可选5 年后builder:(context,child){returnTheme(data:Theme.of(context).copyWith(colorScheme:Theme.of(context).colorScheme.copyWith(primary:constColor(0xFF4DB6AC),// 主题色),),child:child!,);},);if(!mounted)return;if(picked!null){setState(()_dueDatepicked);}}三个关键参数initialDate初始显示的日期。如果已有截止日期就显示它否则显示今天firstDate最早可选日期。设为DateTime.now()防止用户选择过去的日期lastDate最晚可选日期。设为 5 年后兼顾实用性和合理性builder参数用于自定义选择器的主题色——这里将其设为主题薄荷绿#4DB6AC。DateFormat 格式化// 在 Todo 卡片中显示截止日期String_formatDueDate(DateTimedueDate){finalnowDateTime.now();finaltomorrowDateTime(now.year,now.month,now.day1);if(_isSameDay(dueDate,now)){return今天;}elseif(_isSameDay(dueDate,tomorrow)){return明天;}elseif(dueDate.yearnow.year){returnDateFormat(M月d日).format(dueDate);// 6月15日}else{returnDateFormat(yyyy年M月d日).format(dueDate);// 2027年3月1日}}bool_isSameDay(DateTimea,DateTimeb){returna.yearb.yeara.monthb.montha.dayb.day;}智能格式化同年的日期省略年份不同年的才显示年份。“今天”/明天的文字比纯数字更友好。编辑页的日期 UIclassTodoEditPageextendsStatefulWidget{// ...}class_TodoEditPageStateextendsStateTodoEditPage{DateTime?_dueDate;bool _hasDueDatefalse;overrideWidgetbuild(BuildContextcontext){returnScaffold(appBar:AppBar(title:constText(待办事项),actions:[IconButton(icon:constIcon(Icons.check),onPressed:_saveTodo,),],),body:SingleChildScrollView(padding:constEdgeInsets.all(16),child:Column(crossAxisAlignment:CrossAxisAlignment.start,children:[// 标题输入TextField(controller:_titleController,decoration:constInputDecoration(labelText:标题,border:OutlineInputBorder(),),),constSizedBox(height:16),// 备注输入TextField(controller:_noteController,decoration:constInputDecoration(labelText:备注 (可选),border:OutlineInputBorder(),),maxLines:3,),constSizedBox(height:16),// 截止日期设置SwitchListTile(title:constText(设置截止日期),value:_hasDueDate,onChanged:(value){setState((){_hasDueDatevalue;if(!value){_dueDatenull;}elseif(_dueDatenull){_dueDateDateTime.now();}});},activeColor:constColor(0xFF4DB6AC),),if(_hasDueDate_dueDate!null)ListTile(leading:constIcon(Icons.calendar_today),title:Text(_formatDueDate(_dueDate!)),trailing:constIcon(Icons.edit),onTap:_pickDueDate,),],),),);}}逾期的判断classTodo{// ...finalDateTime?dueDate;/// 是否已逾期仅对未完成且设置了截止日期的待办生效boolgetisOverdue{if(isCompleted)returnfalse;// 已完成的不管if(dueDatenull)returnfalse;// 没设截止日期的不管finalnowDateTime.now();// 截止日期的当天还没逾期到第二天才算逾期// 即dueDate 是 5月26日则 5月26日 23:59 之前都不算逾期finaldeadlineDateTime(dueDate!.year,dueDate!.month,dueDate!.day1);returnnow.isAfter(deadline);}}逾期判断用截止日期的下一天 00:00作为比较点而不是用今天的日期 截止日期。这样保证截止日期当天都不算逾期——给用户一整个白天的灵活度。列表中的逾期视觉效果Widget_buildDueDate(Todotodo){if(todo.dueDatenull)returnconstSizedBox.shrink();returnRow(children:[Icon(todo.isOverdue?Icons.warning_amber_rounded:Icons.calendar_today,size:14,color:todo.isOverdue?Colors.red.shade600:Colors.grey.shade500,),constSizedBox(width:4),Text(_formatDueDate(todo.dueDate!),style:TextStyle(fontSize:13,color:todo.isOverdue?Colors.red.shade600:Colors.grey.shade600,fontWeight:todo.isOverdue?FontWeight.w600:FontWeight.normal,),),],);}逾期条目的视觉信号红色文字Colors.red.shade600比标准错误红色略深避免过于刺眼粗体FontWeight.w600与正常条目形成权重对比警告图标Icons.warning_amber_rounded替代日历图标这三重变化组合在一起用户扫一眼列表就知道哪些待办过期了。排序逾期优先在TodoProvider的排序逻辑中逾期的条目排在前面ListTodogetsortedTodos{finalsortedListTodo.from(_todos);sorted.sort((a,b){// 1. 未完成的在前if(a.isCompleted!b.isCompleted)returna.isCompleted?1:-1;// 2. 逾期的在前if(a.isOverdue!b.isOverdue)returna.isOverdue?-1:1;// 3. 有截止日期的在前if(a.dueDate!nullb.dueDatenull)return-1;if(a.dueDatenullb.dueDate!null)return1;// 4. 截止日期早的在前if(a.dueDate!nullb.dueDate!null){returna.dueDate!.compareTo(b.dueDate!);}// 5. 创建时间晚的在前returnb.createdAt.compareTo(a.createdAt);});returnsorted;}排序优先级未完成的 已完成的逾期的 未逾期的有截止日期 无截止日期截止日期早 截止日期晚新创建 旧创建这样的排序把用户最需要关注的条目已逾期、截止日期将近放在列表最上方。鸿蒙兼容性showDatePickerMaterial 3 组件Flutter 框架层实现不依赖原生日期选择器——它在鸿蒙上与 Android/iOS 的外观完全一致DateFormatintl包纯 Dart 实现与平台无关DateTime运算Dart 核心库与平台无关showDatePicker在 Flutter 中是纯 Dart 实现不是调用原生日期选择器这意味着在 Android、iOS、鸿蒙 OHOS 上弹出的日期选择器 UI 完全一致。如果你希望各平台使用原生日期选择器风格可以考虑用MaterialDatePickervsCupertinoDatePicker根据平台切换。进阶推送通知提醒如果未来希望在截止日期到达时推送通知提醒可以基于现有的dueDate字段扩展// 在应用启动时检查void_checkDueSoonTodos(ListTodotodos){finalnowDateTime.now();finaltomorrowDateTime(now.year,now.month,now.day1);for(finaltodointodos){if(todo.isCompleted)continue;if(todo.dueDatenull)continue;if(_isSameDay(todo.dueDate!,tomorrow)){_scheduleReminder(todo);// 明天截止的今晚提醒}}}总结日期选择器和截止日期高亮的实现涉及三个组件showDatePicker弹出 Material 3 日期选择器限制可选范围今天 ~ 5 年后DateFormat智能格式化——“今天/明天/6月15日/2027年3月1日”逾期判断isOverdue在截止日第二天 00:00 起算逾期当天不算配合排序策略逾期优先用户打开待办列表时最需要关注的条目永远在最上方。完整项目代码见todo_flutter_harmony