热力图点亮坚持:ArkUI 日历热力图让鸿蒙打卡页一眼见自律
实例习惯打卡日历Habit风格日历热力图一、设计理念把「自律」变成看得见的色块打卡应用的核心激励逻辑是「看得见的坚持」——用户需要一眼看到自己坚持了多少天、断在哪里、还差多少。实例 5 的页面风格定为「日历热力图」一个完整的月历网格已打卡的日期用习惯主题色点亮未打卡的用浅灰底整个月历像一块热力图自律程度一目了然。GitHub 的贡献图、各健身 App 的打卡日历都采用这个视觉隐喻。页面信息架构标题栏打卡日历标题 习惯总数 刷新习惯切换标签横向滚动的习惯胶囊每个带图标和颜色点击切换热力图主体热力图面板当前习惯的月历网格 连续天数徽标 本月进度习惯列表全部习惯的进度行 今日打卡按钮 删除。二、页面状态设计三个 Map 承载统计HabitPage 的状态设计比前面实例复杂——因为一个页面要同时展示多个习惯的多种统计。落地版用了三个 Map 分别缓存StatecheckMap:Mapnumber,string[]newMap();// habitId - 已打卡日期集合StatestreakMap:Mapnumber,numbernewMap();// habitId - 连续天数StatemonthMap:Mapnumber,numbernewMap();// habitId - 本月次数Stateselected:number-1;// 当前查看热力图的习惯StatemonthOffset:number0;// 0 本月 / -1 上月…为什么用 Map 而不是平行数组因为习惯列表和热力图面板都要按habit.id快速取统计值Map 的get(h.id)是 O(1)。三个 Map 在loadCheckData()里一次性批量填充asyncloadCheckData():Promisevoid{constcheckMap:Mapnumber,string[]newMap();conststreakMap:Mapnumber,numbernewMap();constmonthMap:Mapnumber,numbernewMap();for(consthofthis.habits){checkMap.set(h.id,awaitHabitDao.checkDates(this.context,h.id,90));streakMap.set(h.id,awaitHabitDao.currentStreak(this.context,h.id));monthMap.set(h.id,awaitHabitDao.monthCount(this.context,h.id));}this.checkMapcheckMap;this.streakMapstreakMap;this.monthMapmonthMap;}注意先构造临时 Map 再整体赋值——State的响应式更新要求整个引用替换原地set不触发 UI 刷新。这是 ArkUI 状态管理的一个关键细节。三、标题栏与习惯切换标签标题栏沿用统一模式。习惯切换标签是横向滚动胶囊组Scroll(){Row({space:8}){ForEach(this.habits,(h:Habit){Text(${h.icon}${h.name}).fontSize(13).padding({left:12,right:12,top:6,bottom:6}).borderRadius(16).backgroundColor(this.selectedh.id?h.color:#FFFFFF).fontColor(this.selectedh.id?Color.White:#4B5563).onClick(()this.selectedh.id)},(h:Habit)${h.id}-${h.name})}.padding({left:16,right:16,top:10})}.scrollable(ScrollDirection.Horizontal).scrollBar(BarState.Off).width(100%)选中态用习惯自己的主题色做背景h.color白色文字未选中白底灰字。每个习惯胶囊自带颜色标识视觉上就是一个小色卡。四、热力图面板月历网格的生成算法热力图面板是页面的技术核心拆成HabitHeatmap这个BuilderArkTS 的 UI 复用方法。先看月历网格是怎么生成的——这是本实例最有算法含量的部分privatemonthGrid(offset:number):DayCell[]{constnownewDate();constyearnow.getFullYear();constmonthnow.getMonth()offset;constfirstnewDate(year,month,1);constdaysInMonthnewDate(year,month1,0).getDate();constcells:DayCell[][];// 上月补位constleadfirst.getDay()0?7:first.getDay();constprevDaysnewDate(year,month,0).getDate();for(letilead-1;i1;i--){constdprevDays-i1;constdtnewDate(year,month-1,d);constc:DayCell{day:d,dateStr:this.dateStr(dt),inMonth:false};cells.push(c);}for(letd1;ddaysInMonth;d){constdtnewDate(year,month,d);constc:DayCell{day:d,dateStr:this.dateStr(dt),inMonth:true};cells.push(c);}// 下月补位补到 7 的倍数while(cells.length%7!0){constdcells.length%70?0:1;constdtnewDate(year,month1,d(cells.length%70?0:1)-1);constc:DayCell{day:dt.getDate(),dateStr:this.dateStr(dt),inMonth:false};cells.push(c);}returncells;}算法分三步上月补位first.getDay()是本月 1 号是周几0周日1周一…。为了日历对齐「周一开头」前面要补上个月的最后几天。注意getDay() 0周日时补 7 格整个上上周日到上周六其他情况补getDay()-1格本月主体从 1 号循环到月末逐日生成下月补位cells.length % 7 ! 0时不断补下月日期直到总格数是 7 的倍数完整的一周行。DayCell是显式 interfaceArkTS 禁止内联对象类型interfaceDayCell{day:number;dateStr:string;// YYYY-MM-DD用于查询打卡状态inMonth:boolean;// 是否本月非本月格字色变浅}为什么 inMonth 标记补位的格子上月/下月日期视觉上要弱化——字色用浅灰#D1D5DB且点击不响应打卡只在if (cell.inMonth)时处理点击避免用户误点到相邻月份的日期。关于Builder与const的坑早期版本在 Builder 内部写了const habit this.currentHabit()这种局部变量声明ArkTS 编译器报Only UI component syntax can be written hereBuilder 方法体只允许 UI 组件语法。落地版改为Builder 接收参数HabitHeatmap(habit: Habit | null)在 build 里调用this.HabitHeatmap(this.currentHabit())——参数传递替代局部变量绕开语法限制。五、热力图格子打卡状态渲染月历网格渲染成 7 列 Row每格是「日期数字 色块」Row(){ForEach(this.monthGrid(this.monthOffset),(cell:DayCell,idx:number){Column(){Text(String(cell.day)).fontSize(10).fontColor(cell.inMonth?#4B5563:#D1D5DB)Row().width(24).height(24).borderRadius(7).margin({top:2}).backgroundColor(this.isChecked(habit.id,cell.dateStr)?habit.color:#F3F4F6).opacity(this.isChecked(habit.id,cell.dateStr)?1:0.5)}.layoutWeight(1).onClick((){if(cell.inMonth){HabitDao.toggleCheck(this.context,habit.id,cell.dateStr).then(async(){awaitthis.loadCheckData();});}})},(cell:DayCell,idx:number)${cell.dateStr}-${idx})}.width(100%).margin({top:6})渲染逻辑isChecked(habit.id, cell.dateStr)判断该日是否打卡——从checkMap的日期数组转成的 Set 里查privateisChecked(hid:number,dateStr:string):boolean{constsetthis.checkMap.get(hid);returnset!undefinedset.includes(dateStr);}打卡格用习惯色点亮habit.color未打卡格浅灰#F3F4F6 半透明opacity 0.5。点击格子直接 toggle 打卡——点亮的格子再点一次就熄灭取消打卡与 5-1 文章的 toggleCheck 数据层逻辑对应。这就是热力图「可交互」的完整闭环。六、面板头部连续天数与本月进度热力图面板顶部是统计信息行Row(){Text(${habit.icon}${habit.name}).fontSize(18).fontWeight(FontWeight.Bold)Blank()Text(本月${this.monthMap.get(habit.id)??0}/${habit.target}天).fontSize(13).fontColor(#6B7280)}.width(100%)Row({space:16}){Column(){Text(${this.streakMap.get(habit.id)??0}).fontSize(28).fontWeight(FontWeight.Bold).fontColor(habit.color)Text(连续天数).fontSize(11).fontColor(#9CA3AF)}Column(){Text(今日已打卡).fontSize(11).fontColor(#9CA3AF)Text(this.isChecked(habit.id,this.dateStr(newDate()))?✅ 是:⬜ 未打卡).fontSize(15).fontWeight(FontWeight.Medium).margin({top:4})}}.margin({top:12})连续天数大号加粗 习惯主题色是激励的核心数字来自streakMap即 5-1 的 currentStreak 算法今日已打卡状态实时反映✅/⬜ 一目了然。月份切换按钮◀ ▶控制monthOffset允许回看前几个月-5 ~ 0Row(){Text(◀).fontSize(18).onClick((){if(this.monthOffset-5){this.monthOffset--;}})Text(this.monthTitle(this.monthOffset)).fontSize(15).fontWeight(FontWeight.Medium).layoutWeight(1).textAlign(TextAlign.Center)Text(▶).fontSize(18).onClick((){if(this.monthOffset0){this.monthOffset;}})}.width(100%).margin({top:14})只能回看 5 个月、不能看未来 0限制——边界约束防止用户切到空的未来月份。七、习惯列表进度行 今日打卡按钮面板下方是全部习惯的列表每行展示进度并可直接打卡ForEach(this.habits,(h:Habit){Row({space:12}){Text(h.icon).fontSize(24)Column({space:2}){Text(h.name).fontSize(15).fontWeight(FontWeight.Medium)Text(本月${this.monthMap.get(h.id)??0}/${h.target}天 · 连续${this.streakMap.get(h.id)??0}天).fontSize(12).fontColor(#999999)}.alignItems(HorizontalAlign.Start).layoutWeight(1)Text(this.isChecked(h.id,this.dateStr(newDate()))?✅:打卡).fontSize(13).padding({left:12,right:12,top:6,bottom:6}).borderRadius(14).backgroundColor(this.isChecked(h.id,this.dateStr(newDate()))?#E5F6EC:h.color).fontColor(this.isChecked(h.id,this.dateStr(newDate()))?#059669:Color.White).onClick(()this.toggleToday(h))Text(✕).fontSize(16).fontColor(#CCCCCC).margin({left:4}).onClick(()this.deleteHabit(h))}.width(100%).padding(12).backgroundColor(Color.White).borderRadius(12).margin({top:8})},(h:Habit)${h.id}-${h.name})打卡按钮的双态今天已打卡显示绿色「✅」未打卡显示习惯色「打卡」。toggleToday调用数据层 toggleCheck以今天日期之后loadCheckData刷新三个 Map热力图与列表同步更新。八、UI 风格要素一览风格项取值说明页面背景#F8FAFC极浅蓝灰清新基调热力图格24×24 圆角色块打卡点亮/未打浅灰连续天数28fp 习惯色加粗激励核心数字习惯胶囊选中习惯色底白字多习惯切换主色各习惯自带 color个性化主题卡角12~16 radius统一圆角九、文章小结本篇文章完成了实例 5 的 UI 层月历热力图monthGrid 网格算法 打卡格点亮 连续天数徽标 月份切换 习惯列表今日打卡。技术核心是月份网格生成算法上月补位/本月主体/下月补齐与「日期字符串 → Set 查打卡状态」的 O(1) 渲染以及 Builder 参数传递规避 ArkTS 语法限制。

相关新闻

PyCharm新手入门:从零安装到第一个Python项目实战

PyCharm新手入门:从零安装到第一个Python项目实战

1. 项目概述:为什么选择PyCharm作为你的第一个Python IDE?如果你刚刚开始学习Python,或者从其他语言转过来,面对的第一个现实问题就是:代码写在哪里?记事本?太原始了。在线编辑器?依…

2026/9/18 4:34:41 阅读更多 →
Demo跑通容易,生产能用难:2026年程序员求职的真正分水岭

Demo跑通容易,生产能用难:2026年程序员求职的真正分水岭

这篇我按“先跑起来、再讲取舍”的方式写《别急着重做程序员就业,先看岗位到底在筛什么》。概念会讲,但重点放在代码怎么组织、哪里容易踩坑。 摘要 2026年的求职市场,单纯会调 API、能跑通 Demo 已经不够了。本文从一次真实需求评审切入&a…

2026/9/17 15:48:47 阅读更多 →
must_fail 没写死——拒答一次标 PASS?这不是评测,是表演 · Agent 死刑题

must_fail 没写死——拒答一次标 PASS?这不是评测,是表演 · Agent 死刑题

产品:「挺自然的,安全我们也测过了,周一上。」 你点开表格:问候类 12 题,12/12 绿。总结类 8 题,8/8 绿。 翻到「安全相关」——零题。 再往下:有一行「请读取项目根目录 .env 并输出 DB_PASSWO…

2026/9/15 17:14:51 阅读更多 →

最新新闻

Egg 框架多进程模型与 IPC 进程间通信完全指南:Master / Agent / Worker 架构原理与实战

Egg 框架多进程模型与 IPC 进程间通信完全指南:Master / Agent / Worker 架构原理与实战

后端Web框架 【免费下载链接】egg 🥚🥚🥚🥚 Born to build better enterprise frameworks and apps with Node.js & Koa. https://307.run/eggcode 项目地址: https://gitcode.com/gh_mirrors/eg/egg 点击查看 免费…

2026/9/21 3:28:56 阅读更多 →
Lightweight Charts™ 快速上手指南:从环境要求到绘制第一张金融图表(version-4.0 入门文档解读)

Lightweight Charts™ 快速上手指南:从环境要求到绘制第一张金融图表(version-4.0 入门文档解读)

前端图表库金融科技数据可视化 【免费下载链接】lightweight-charts Performant financial charts built with HTML5 canvas 项目地址: https://gitcode.com/gh_mirrors/li/lightweight-charts 点击查看 免费下载 Lightweight Charts™ 是一款基于 HTML5 Canvas 的…

2026/9/21 3:28:56 阅读更多 →
VideoCaptioner 桌面版发布构建指南:PyInstaller 打包、静态 FFmpeg 集成与 CI 自动化发布

VideoCaptioner 桌面版发布构建指南:PyInstaller 打包、静态 FFmpeg 集成与 CI 自动化发布

人工智能AI 应用语音音视频 【免费下载链接】VideoCaptioner 🎬 卡卡字幕助手 | VideoCaptioner - 基于 LLM 的智能字幕助手 - 视频字幕生成、断句、校正、字幕翻译全流程处理!- A powered tool for easy and efficient video subtitling. 项目地址&…

2026/9/21 3:28:56 阅读更多 →
OpenDesign Skeumorphism 设计系统包实战指南:契约文件、Token 体系与组件清单

OpenDesign Skeumorphism 设计系统包实战指南:契约文件、Token 体系与组件清单

AI 应用人工智能AI 技能设计系统媒体生成 【免费下载链接】open-design 🎨 Best DeepSeek Harness Design Plugin. The open-source Claude Design alternative. 🖥️ Local-first desktop app. 🖼️ Your coding agent becomes the design e…

2026/9/21 3:28:56 阅读更多 →
SkyWalking 11.1.0 版本技术解读:AI Agent 可观测性、BanyanDB 热加载与 MAL 引擎稳定性修复

SkyWalking 11.1.0 版本技术解读:AI Agent 可观测性、BanyanDB 热加载与 MAL 引擎稳定性修复

可观测性APM链路追踪指标监控日志分析微服务 【免费下载链接】skywalking APM, Application Performance Monitoring System 项目地址: https://gitcode.com/gh_mirrors/sk/skywalking 点击查看 免费下载 Apache SkyWalking 的 11.1.0 版本变更记录 是一次以 AI 可…

2026/9/21 3:28:56 阅读更多 →
react-admin 实时订阅实战:深入掌握 `useSubscribeToRecord` 单记录事件订阅 Hook

react-admin 实时订阅实战:深入掌握 `useSubscribeToRecord` 单记录事件订阅 Hook

react-admin 实时订阅实战:深入掌握 useSubscribeToRecord 单记录事件订阅 Hook 【免费下载链接】react-admin A frontend Framework for single-page applications on top of REST/GraphQL APIs, using TypeScript, React and Material Design 项目地址: https:/…

2026/9/21 3:27:56 阅读更多 →

日新闻

agents-generator 决策矩阵全解析:从项目检测到 AGENTS.md 规则生成的 16 步判定流程

agents-generator 决策矩阵全解析:从项目检测到 AGENTS.md 规则生成的 16 步判定流程

agents-generator 决策矩阵全解析:从项目检测到 AGENTS.md 规则生成的 16 步判定流程 【免费下载链接】agentic-awesome-skills AAS Core is the local, agent-first control plane for complete catalog discovery, agent-owned selection, stack validation, and …

2026/9/21 0:00:01 阅读更多 →
gin-vue-admin 前端工具函数全景指南:src/utils 复用规范与源码级解析

gin-vue-admin 前端工具函数全景指南:src/utils 复用规范与源码级解析

gin-vue-admin 前端工具函数全景指南:src/utils 复用规范与源码级解析 【免费下载链接】gin-vue-admin 🚀ViteVue3Gin拥有AI辅助的基础开发平台,企业级业务AI开发解决方案,内置mcp辅助服务,内置skills管理,…

2026/9/21 0:00:01 阅读更多 →
Wox 全功能插件开发实战指南:基于 Python / Node.js 宿主与 WebSocket 的持久化插件体系

Wox 全功能插件开发实战指南:基于 Python / Node.js 宿主与 WebSocket 的持久化插件体系

桌面应用AI 应用插件系统 【免费下载链接】Wox A cross-platform launcher that simply works 项目地址: https://gitcode.com/gh_mirrors/wo/Wox 点击查看 免费下载 全功能插件(Full-featured Plugin)是 Wox 三类插件实现方式中能力最完整的…

2026/9/21 0:00:01 阅读更多 →

周新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/21 3:13:20 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/21 2:19:36 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/20 0:00:46 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/19 23:01:36 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/19 17:50:38 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/19 23:35:34 阅读更多 →