当前位置: 首页 > news >正文

鸿蒙Flutter实战:日期选择器与截止日期高亮提醒

前言

待办事项的核心价值是"在截止时间前完成"。一个没有截止日期的待办只是"想法",有了截止日期才是"承诺"。Flutter 内置的showDatePicker可以直接弹出 Material 3 风格的日期选择器,配合intl包的日期格式化,就能构建一套完整的截止日期系统。

鸿蒙 Flutter 备忘录的待办模块允许为每条待办设置截止日期,并在列表中自动标记已逾期的条目——截至日期超过今天,该行文字变为红色加粗提示。

项目仓库:todo_flutter_harmony

依赖

dependencies:intl:^0.20.2

intl包提供DateFormat类,用于日期的格式化和本地化。它是 Dart 官方维护的国际化核心库。

日期选择器的调用

Flutter 内置的showDatePicker返回一个Future<DateTime?>——用户选择日期后返回所选日期,点取消则返回 null:

Future<void>_pickDueDate()async{finalnow=DateTime.now();finalpicked=awaitshowDatePicker(context:context,initialDate:_dueDate??now,firstDate:now,// 最早可选:今天lastDate:DateTime(now.year+5),// 最晚可选: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(()=>_dueDate=picked);}}

三个关键参数:

  • initialDate:初始显示的日期。如果已有截止日期就显示它,否则显示今天
  • firstDate:最早可选日期。设为DateTime.now()防止用户选择过去的日期
  • lastDate:最晚可选日期。设为 5 年后,兼顾实用性和合理性

builder参数用于自定义选择器的主题色——这里将其设为主题薄荷绿#4DB6AC

DateFormat 格式化

// 在 Todo 卡片中显示截止日期String_formatDueDate(DateTimedueDate){finalnow=DateTime.now();finaltomorrow=DateTime(now.year,now.month,now.day+1);if(_isSameDay(dueDate,now)){return'今天';}elseif(_isSameDay(dueDate,tomorrow)){return'明天';}elseif(dueDate.year==now.year){returnDateFormat('M月d日').format(dueDate);// "6月15日"}else{returnDateFormat('yyyy年M月d日').format(dueDate);// "2027年3月1日"}}bool_isSameDay(DateTimea,DateTimeb){returna.year==b.year&&a.month==b.month&&a.day==b.day;}

智能格式化:同年的日期省略年份,不同年的才显示年份。“今天”/"明天"的文字比纯数字更友好。

编辑页的日期 UI

classTodoEditPageextendsStatefulWidget{// ...}class_TodoEditPageStateextendsState<TodoEditPage>{DateTime?_dueDate;bool _hasDueDate=false;@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((){_hasDueDate=value;if(!value){_dueDate=null;}elseif(_dueDate==null){_dueDate=DateTime.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(dueDate==null)returnfalse;// 没设截止日期的不管finalnow=DateTime.now();// 截止日期的当天还没逾期,到第二天才算逾期// 即:dueDate 是 5月26日,则 5月26日 23:59 之前都不算逾期finaldeadline=DateTime(dueDate!.year,dueDate!.month,dueDate!.day+1);returnnow.isAfter(deadline);}}

逾期判断用"截止日期的下一天 00:00"作为比较点,而不是用"今天的日期 > 截止日期"。这样保证截止日期当天都不算逾期——给用户一整个白天的灵活度。

列表中的逾期视觉效果

Widget_buildDueDate(Todotodo){if(todo.dueDate==null)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,),),],);}

逾期条目的视觉信号:

  1. 红色文字Colors.red.shade600,比标准错误红色略深,避免过于刺眼
  2. 粗体FontWeight.w600,与正常条目形成权重对比
  3. 警告图标Icons.warning_amber_rounded替代日历图标

这三重变化组合在一起,用户扫一眼列表就知道哪些待办过期了。

排序:逾期优先

TodoProvider的排序逻辑中,逾期的条目排在前面:

List<Todo>getsortedTodos{finalsorted=List<Todo>.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!=null&&b.dueDate==null)return-1;if(a.dueDate==null&&b.dueDate!=null)return1;// 4. 截止日期早的在前if(a.dueDate!=null&&b.dueDate!=null){returna.dueDate!.compareTo(b.dueDate!);}// 5. 创建时间晚的在前returnb.createdAt.compareTo(a.createdAt);});returnsorted;}

排序优先级:

  1. 未完成的 > 已完成的
  2. 逾期的 > 未逾期的
  3. 有截止日期 > 无截止日期
  4. 截止日期早 > 截止日期晚
  5. 新创建 > 旧创建

这样的排序把用户最需要关注的条目(已逾期、截止日期将近)放在列表最上方。

鸿蒙兼容性

  • showDatePicker:Material 3 组件,Flutter 框架层实现,不依赖原生日期选择器——它在鸿蒙上与 Android/iOS 的外观完全一致
  • DateFormatintl包,纯 Dart 实现,与平台无关
  • DateTime运算:Dart 核心库,与平台无关

showDatePicker在 Flutter 中是纯 Dart 实现(不是调用原生日期选择器),这意味着在 Android、iOS、鸿蒙 OHOS 上弹出的日期选择器 UI 完全一致。如果你希望各平台使用原生日期选择器风格,可以考虑用MaterialDatePickervsCupertinoDatePicker根据平台切换。

进阶:推送通知提醒

如果未来希望在截止日期到达时推送通知提醒,可以基于现有的dueDate字段扩展:

// 在应用启动时检查void_checkDueSoonTodos(List<Todo>todos){finalnow=DateTime.now();finaltomorrow=DateTime(now.year,now.month,now.day+1);for(finaltodointodos){if(todo.isCompleted)continue;if(todo.dueDate==null)continue;if(_isSameDay(todo.dueDate!,tomorrow)){_scheduleReminder(todo);// 明天截止的,今晚提醒}}}

总结

日期选择器和截止日期高亮的实现涉及三个组件:

  1. showDatePicker:弹出 Material 3 日期选择器,限制可选范围(今天 ~ 5 年后)
  2. DateFormat:智能格式化——“今天/明天/6月15日/2027年3月1日”
  3. 逾期判断isOverdue在截止日第二天 00:00 起算逾期,当天不算

配合排序策略(逾期优先),用户打开待办列表时,最需要关注的条目永远在最上方。

完整项目代码见:todo_flutter_harmony

http://www.gsyq.cn/news/1456591.html

相关文章:

  • 2026年 自动光杆排线器厂家推荐榜:全自动、私服、多功能排线机及扭簧直簧配件深度解析 - 品牌企业推荐师(官方)
  • ENVI Classic直方图匹配实战:如何让两期卫星影像‘色调一致’,为变化监测打好基础
  • 混装不确定性区域6%AFFF/AR抗溶性水成膜消防泡沫液选购指南,浙江金瑞恒一剂多用 - 品牌速递
  • AI 大模型时代的 FDE 转型实战: Harness+ LLM
  • 危化品运输车3%AFFF/AR抗溶性水成膜泡沫灭火剂选购指南,浙江金瑞恒适配性强 - 品牌速递
  • 手把手教你用Verilog实现FP16加法器:从IEEE 754格式到波形验证的保姆级教程
  • 2026尼日利亚五项清关政策更新,拉高能源装备进口综合成本
  • 2026年焙烧炉/石灰焙烧炉/轻烧粉焙烧炉/氢氧化镁/二水磷酸铁焙烧炉厂家推荐:多行业热工装备与节能技术深度解析 - 品牌企业推荐师(官方)
  • Element Plus 表单实战:从 ElementUI 迁移到 Vue 3 的 5 个关键变化与避坑指南
  • 基于Arduino与BMP280的低功耗气压趋势仪DIY指南
  • AMD Ryzen终极调试手册:5个专业技巧彻底释放硬件性能
  • Navidrome(docker-compose) + Tempo + Feishin 完整部署文档(DeepSeek)
  • 2026年 IGBT模块/功率模块/可控硅/二极管/整流桥/晶闸管品牌推荐榜单:高效稳定与高性价比全解析 - 品牌企业推荐师(官方)
  • 保姆级教程:用COMSOL 6.1搞定七芯光纤超模仿真(附网格划分与边界条件避坑指南)
  • 2026年 彩盒印刷/包装印刷/礼品包装盒厂家推荐榜:按需定制、天地盖与异形盒工艺实力之选 - 企业推荐官【官方】
  • Unity开发避坑指南:别再滥用material了,小心内存泄漏和性能问题
  • 把核心数据锁进“信息孤岛”:专网独立部署如何实现安全与效率兼得
  • 2026年自动绕线机厂家推荐排行榜:全自动收线绕线机、精密绕线机、多功能收线机源头厂家深度解析 - 品牌企业推荐师(官方)
  • ESP8266双传感器融合:PIR与微波雷达协同实现高可靠人体检测
  • 从MySQL到OceanBase:如何利用多租户特性,在单集群里安全隔离你的测试和生产环境?
  • 2026年 印刷/彩盒/包装印刷厂家推荐榜单:大型印务、UV印刷与按需包装礼盒的匠心之选 - 企业推荐官【官方】
  • Unity Scene视图左上角那个‘Shaded’下拉菜单,你真的会用吗?从着色到线框的四种查看技巧
  • 脑器官模块化系统与神经AI数字孪生技术解析
  • 从零打造五自由度仿生机械臂:3D打印、Arduino与舵机控制全解析
  • vdds
  • 光model测试
  • gdsg
  • 别再死记硬背PCA步骤了!用鸢尾花数据集手把手带你理解每一步的数学原理(附Python代码)
  • 不只是重装:深度解析联想USB Recovery Creator如何完整克隆出厂状态
  • K8s 环境下大模型分布式训练的网络带宽优化:针对推理服务冷热备方案