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

Flutter for OpenHarmony 跨平台开发:日历打卡功能实战指南

Flutter for OpenHarmony 跨平台开发:日历打卡功能实战指南

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net


一、引言

嘿,亲爱的开发者们~有没有想过,用一套代码就能让你的创意在鸿蒙设备上绽放?今天要和大家分享的,是一个超级实用又可爱的小功能——日历打卡!无论是想养成早起的好习惯,还是坚持每天运动,这个小小的打卡助手都能成为你最好的伙伴呢~

在这个快节奏的时代,养成一个好习惯真的不容易。而日历打卡应用,就像是一个贴心的小伙伴,每天提醒你、鼓励你、陪伴你。当你看到日历上一个个绿色的小圆圈时,那种成就感简直太棒啦!

本文将带领大家使用 Flutter for OpenHarmony 跨平台技术,从零开始实现一个功能完善的日历打卡应用。让我们一起开启这段美妙的开发之旅吧~


二、技术背景

2.1 Flutter for OpenHarmony 简介

Flutter 就像是一个神奇的魔法棒,轻轻一挥,你的应用就能在鸿蒙、Android、iOS 上自由奔跑啦!Flutter 是 Google 推出的开源 UI 框架,以其"一次编写,多处运行"的理念深受开发者喜爱。而 Flutter for OpenHarmony 则是为我们打开了一扇通往鸿蒙生态的大门。

开源鸿蒙(OpenHarmony)是由开放原子开源基金会孵化的开源项目,旨在构建万物智联的操作系统生态。Flutter for OpenHarmony 的出现,让 Flutter 开发者能够无缝地将应用部署到鸿蒙设备上,极大地降低了跨平台开发的门槛。

2.2 跨平台开发的优势

相比传统的原生开发,Flutter 跨平台开发有着独特的魅力:

开发效率提升:一套代码,多端运行,再也不用为不同平台分别开发啦!

热重载体验:修改代码后立即看到效果,开发体验超级流畅~

精美的 UI:Flutter 提供丰富的 Material Design 和 Cupertino 组件,让你的应用颜值在线!

性能优异:Flutter 直接编译为原生代码,运行流畅不卡顿。

2.3 与原生鸿蒙开发的对比

特性Flutter for OpenHarmony原生鸿蒙开发
学习曲线较平缓,Dart语言简洁ArkTS/ArkUI需要学习
开发效率一套代码多端运行仅限鸿蒙平台
UI组件丰富的跨平台组件鸿蒙特有组件
性能接近原生原生性能
生态Flutter庞大生态鸿蒙生态

三、功能设计

3.1 功能概述

我们要实现的日历打卡功能,可不是简单的"点一下就完事"哦!它包含以下贴心的小功能:

多习惯追踪:支持早起、运动、阅读、学习、冥想、喝水等多种习惯,想追踪哪个就选哪个~

日历视图展示:清晰的月历视图,一目了然地看到自己的打卡记录。

打卡统计:本月打卡次数、连续打卡天数、总打卡天数,让成就感看得见!

视觉反馈:已打卡的日期显示绿色圆圈,今天有蓝色边框提示,未来日期则显示为灰色不可点击。

3.2 界面设计

界面从上到下依次为:

  1. 习惯选择器:横向滚动的 FilterChip 列表,选择要追踪的习惯
  2. 统计面板:渐变背景的统计卡片,显示打卡数据
  3. 月份切换:左右箭头切换月份,中间显示年月
  4. 星期标题:日一二三四五六
  5. 日历网格:7列的日期网格,点击即可打卡

四、核心实现

4.1 数据结构设计

首先,我们需要设计数据结构来存储打卡记录:

// 当前显示的月份DateTime_currentMonth=DateTime.now();// 当前选中的习惯String_selectedHabit='早起';// 支持的习惯列表finalList<String>_habits=['早起','运动','阅读','学习','冥想','喝水'];// 打卡记录存储,key为习惯名称,value为打卡日期集合finalMap<String,Set<String>>_habitRecords={};

这里使用Map<String, Set<String>>来存储打卡记录,每个习惯对应一个日期字符串集合。日期字符串格式为年-月-日,例如2026-04-29

4.2 打卡状态判断

判断某一天是否已打卡:

bool_isChecked(DateTimedate){finalkey='${date.year}-${date.month}-${date.day}';return(_habitRecords[_selectedHabit]??{}).contains(key);}

4.3 打卡/取消打卡

点击日期时的处理逻辑:

void_toggleCheck(DateTimedate){finalkey='${date.year}-${date.month}-${date.day}';setState((){_habitRecords[_selectedHabit]??={};if(_habitRecords[_selectedHabit]!.contains(key)){_habitRecords[_selectedHabit]!.remove(key);}else{_habitRecords[_selectedHabit]!.add(key);}});}

4.4 统计数据计算

本月打卡次数

int_getMonthCheckCount(){return(_habitRecords[_selectedHabit]??{}).where((d)=>d.startsWith('${_currentMonth.year}-${_currentMonth.month}')).length;}

连续打卡天数

int_getStreak(){int streak=0;DateTimecheckDate=DateTime.now();while(true){finalkey='${checkDate.year}-${checkDate.month}-${checkDate.day}';if((_habitRecords[_selectedHabit]??{}).contains(key)){streak++;checkDate=checkDate.subtract(constDuration(days:1));}else{break;}}returnstreak;}

五、完整代码实现

5.1 日历打卡功能完整代码

import'package:flutter/material.dart';classCalendarFeatureextendsStatefulWidget{constCalendarFeature({super.key});@overrideState<CalendarFeature>createState()=>_CalendarFeatureState();}class_CalendarFeatureStateextendsState<CalendarFeature>{DateTime_currentMonth=DateTime.now();String_selectedHabit='早起';finalList<String>_habits=['早起','运动','阅读','学习','冥想','喝水'];finalMap<String,Set<String>>_habitRecords={};bool_isChecked(DateTimedate){finalkey='${date.year}-${date.month}-${date.day}';return(_habitRecords[_selectedHabit]??{}).contains(key);}void_toggleCheck(DateTimedate){finalkey='${date.year}-${date.month}-${date.day}';setState((){_habitRecords[_selectedHabit]??={};if(_habitRecords[_selectedHabit]!.contains(key)){_habitRecords[_selectedHabit]!.remove(key);}else{_habitRecords[_selectedHabit]!.add(key);}});}int_getMonthCheckCount(){return(_habitRecords[_selectedHabit]??{}).where((d)=>d.startsWith('${_currentMonth.year}-${_currentMonth.month}')).length;}int_getStreak(){int streak=0;DateTimecheckDate=DateTime.now();while(true){finalkey='${checkDate.year}-${checkDate.month}-${checkDate.day}';if((_habitRecords[_selectedHabit]??{}).contains(key)){streak++;checkDate=checkDate.subtract(constDuration(days:1));}else{break;}}returnstreak;}@overrideWidgetbuild(BuildContextcontext){returnColumn(children:[_buildHabitSelector(),_buildStats(),_buildMonthHeader(),_buildWeekDays(),Expanded(child:_buildCalendarGrid()),],);}Widget_buildHabitSelector(){returnContainer(padding:constEdgeInsets.all(12),child:SingleChildScrollView(scrollDirection:Axis.horizontal,child:Row(children:_habits.map((habit)=>Padding(padding:constEdgeInsets.only(right:8),child:FilterChip(label:Text(habit),selected:_selectedHabit==habit,onSelected:(selected){setState(()=>_selectedHabit=habit);},selectedColor:Colors.green.shade200,),)).toList(),),),);}Widget_buildStats(){returnContainer(margin:constEdgeInsets.symmetric(horizontal:12),padding:constEdgeInsets.all(16),decoration:BoxDecoration(gradient:LinearGradient(colors:[Colors.green.shade400,Colors.green.shade600],begin:Alignment.centerLeft,end:Alignment.centerRight,),borderRadius:BorderRadius.circular(12),),child:Row(mainAxisAlignment:MainAxisAlignment.spaceAround,children:[_buildStatItem('本月打卡',_getMonthCheckCount(),Icons.calendar_today),_buildStatItem('连续天数',_getStreak(),Icons.local_fire_department),_buildStatItem('总天数',(_habitRecords[_selectedHabit]??{}).length,Icons.star),],),);}Widget_buildStatItem(Stringlabel,int value,IconDataicon){returnColumn(children:[Icon(icon,color:Colors.white,size:24),constSizedBox(height:4),Text('$value',style:constTextStyle(fontSize:24,fontWeight:FontWeight.bold,color:Colors.white)),Text(label,style:constTextStyle(fontSize:12,color:Colors.white70)),],);}Widget_buildMonthHeader(){returnContainer(padding:constEdgeInsets.symmetric(horizontal:16,vertical:12),child:Row(mainAxisAlignment:MainAxisAlignment.spaceBetween,children:[IconButton(icon:constIcon(Icons.chevron_left,size:28),onPressed:()=>setState(()=>_currentMonth=DateTime(_currentMonth.year,_currentMonth.month-1)),),Text('${_currentMonth.year}${_currentMonth.month}月',style:constTextStyle(fontSize:20,fontWeight:FontWeight.bold),),IconButton(icon:constIcon(Icons.chevron_right,size:28),onPressed:()=>setState(()=>_currentMonth=DateTime(_currentMonth.year,_currentMonth.month+1)),),],),);}Widget_buildWeekDays(){constweekDays=['日','一','二','三','四','五','六'];returnContainer(padding:constEdgeInsets.symmetric(vertical:8),child:Row(children:weekDays.map((d)=>Expanded(child:Center(child:Text(d,style:constTextStyle(fontWeight:FontWeight.bold,color:Colors.grey)),),)).toList(),),);}Widget_buildCalendarGrid(){finalfirstDay=DateTime(_currentMonth.year,_currentMonth.month,1);finallastDay=DateTime(_currentMonth.year,_currentMonth.month+1,0);finalstartWeekday=firstDay.weekday%7;finaltotalDays=lastDay.day+startWeekday;returnGridView.builder(padding:constEdgeInsets.symmetric(horizontal:8),gridDelegate:constSliverGridDelegateWithFixedCrossAxisCount(crossAxisCount:7,childAspectRatio:1,),itemCount:totalDays,itemBuilder:(context,index){if(index<startWeekday)returnconstSizedBox();finalday=index-startWeekday+1;finaldate=DateTime(_currentMonth.year,_currentMonth.month,day);finalchecked=_isChecked(date);finalisToday=_isToday(date);finalisFuture=date.isAfter(DateTime.now());returnGestureDetector(onTap:isFuture?null:()=>_toggleCheck(date),child:Container(margin:constEdgeInsets.all(4),decoration:BoxDecoration(color:checked?Colors.green:(isToday?Colors.blue.shade50:null),shape:BoxShape.circle,border:isToday&&!checked?Border.all(color:Colors.blue,width:2):null,),child:Stack(alignment:Alignment.center,children:[Text('$day',style:TextStyle(color:checked?Colors.white:(isFuture?Colors.grey.shade300:null),fontWeight:isToday?FontWeight.bold:null,),),if(checked)constPositioned(bottom:2,child:Icon(Icons.check,size:12,color:Colors.white),),],),),);},);}bool_isToday(DateTimedate){finalnow=DateTime.now();returndate.year==now.year&&date.month==now.month&&date.day==now.day;}}

六、运行效果

运行效果展示:

  • 习惯选择器可以横向滚动选择不同习惯
  • 统计面板显示渐变绿色背景,数据清晰
  • 日历网格中已打卡日期显示绿色圆圈
  • 今天有蓝色边框高亮提示
  • 点击日期即可完成打卡/取消打卡

七、关键技术点解析

7.1 GridView 构建日历网格

使用GridView.builder构建日历网格,关键点在于计算起始位置:

finalfirstDay=DateTime(_currentMonth.year,_currentMonth.month,1);finalstartWeekday=firstDay.weekday%7;

weekday % 7是为了将周一为1的星期转换为周日为0的格式。

7.2 FilterChip 实现习惯选择

FilterChip是 Material Design 3 的组件,非常适合做这种可选择的标签:

FilterChip(label:Text(habit),selected:_selectedHabit==habit,onSelected:(selected){setState(()=>_selectedHabit=habit);},selectedColor:Colors.green.shade200,)

7.3 鸿蒙适配要点

在鸿蒙设备上运行 Flutter 应用,需要注意:

  1. 签名配置:在 DevEco Studio 中配置自动签名
  2. 权限配置:如需网络请求,需在module.json5中配置网络权限
  3. 触摸反馈:使用InkWellGestureDetector处理触摸事件

八、总结与展望

通过本文的学习,我们使用 Flutter for OpenHarmony 成功实现了一个功能完善的日历打卡应用。从数据结构设计到 UI 实现,再到统计数据计算,每一步都体现了 Flutter 跨平台开发的便捷与高效。

功能回顾

  • ✅ 多习惯追踪
  • ✅ 日历视图展示
  • ✅ 打卡统计
  • ✅ 视觉反馈

可扩展方向

  • 数据持久化:使用shared_preferenceshive保存打卡记录
  • 提醒功能:添加本地通知提醒
  • 数据可视化:添加打卡趋势图表
  • 社交分享:分享打卡成就到社交平台

Flutter for OpenHarmony 的生态正在蓬勃发展,越来越多的开发者加入到这个大家庭中。相信在不久的将来,我们会看到更多优秀的跨平台应用诞生!

http://www.jsqmd.com/news/734957/

相关文章:

  • 创业公司选开源协议:MIT、Apache还是GPL?从三个真实项目故事看选择
  • 2026年5月评价高的黑龙江格宾网哪家好排行厂家推荐榜,石笼网/格宾网/雷诺护垫厂家选择指南 - 海棠依旧大
  • 将Taotoken配置为Claude Code插件的自定义大模型供应商
  • 企业内网系统安全集成大模型能力的架构设计与实践
  • 避坑指南:用LAMMPS做石墨烯剪切模拟时,velocity命令和边界条件设置的那些‘坑’
  • stylelint-config-prettier 与 stylelint 16.x
  • 告别时钟抖动噩梦:JESD204B系统里SYSREF与Device Clock的配置避坑全记录
  • Docker 27网络策略引擎深度拆解(CNI v1.4+NetworkPolicy v2.0实测报告)
  • 告别单调文字!用Unity编辑器一键生成TextMeshPro艺术字(附完整源码)
  • DRB与FINDER查询机制对比及分布式系统优化实践
  • 2026年现阶段湖北胶水类定做厂家可靠度深度剖析与选择指南 - 2026年企业推荐榜
  • 从‘钢铁直男’到‘太极大师’:机器人柔顺控制(阻抗/导纳)选型避坑指南
  • 别再对着英文界面发愁了!手把手教你用OptiSystem 15.0完成第一个光通信仿真(附EDFA案例)
  • 企业级IT资产管理挑战与Snipe-IT开源解决方案的技术架构与实施路径
  • acbDecrypter:游戏音频解密的终极解决方案 - 快速提取加密音频文件
  • 2026年现阶段,探寻济南实木家具定制工厂直营的实力之选:天宏创展 - 2026年企业推荐榜
  • 为什么92%的团队在VSCode 2026多智能体项目中3个月内失败?——基于GitHub Top 50开源Agent项目的故障热力图分析
  • 抖音批量下载终极指南:免费开源工具快速下载无水印视频
  • 2026年Q2垃圾房定制技术解析:不锈钢公交站台、不锈钢垃圾房、仿古公交站台、公交站台价格、公交站台岗亭、四分类垃圾房选择指南 - 优质品牌商家
  • Flutter for OpenHarmony 萌系社交实战合集:一键登录 + 实时聊天全攻略
  • 在安阳找GEO代运营,花小钱办大事有可能吗?我们实地算了5家公司的账,终于找到这个“性价比之王” - 行业深度观察
  • piz:用自然语言生成并安全执行Shell命令的AI终端助手
  • 别只写理想模型了!用Verilog-AMS为电阻添加热噪声,让你的仿真更贴近现实
  • 在 Claude Code 中无缝切换不同大模型提升编程助手效率
  • 2026年当下,企业如何选择靠谱的财税规划“直销工厂”? - 2026年企业推荐榜
  • Flutter for OpenHarmony 萌系 UI 实战合集:骨架屏 + 引导页一站式指南
  • NovelClaw:基于记忆系统与工作台范式的AI长篇创作解决方案
  • 低查重AI写教材工具推荐:快速生成50万字教材,出版级品质!
  • Halcon图像预处理实战:从‘fabrik.png’到清晰轮廓,手把手教你搞定工业视觉第一步
  • 苹果设备全家桶专栏介绍:iPhone 参数速查、选购建议、二手验机与生态使用完整指引