鸿蒙原生开发——从零构建倒数日追踪器
一、引言
人是靠期待活着的。生日、纪念日、旅行、毕业、节日——这些未来事件给我们时间感,让平凡的日常有了方向。"倒数日"这种 App 之所以流行,原因在于它把抽象的时间变成了具象的数字:"还有 3 天"比"下周三"更有紧迫感,"已过 12 天"比"两周前"更有纪念意义。
从技术角度看,倒数日涉及的核心问题是日期运算——两个日期之间相差多少天、今天是哪个日期、如何判断某个事件是将来还是过去、如何按距离今天的远近排序。这些运算不依赖后端,全部可以在前端用 JavaScript 的 Date API 完成。
本文将用 ArkUI 从零构建一个倒数日追踪器。功能包括:添加事件(名称 + 图标 + 目标日期)、自动计算剩余天数、四色距离标记(今天/明天红色、3 天内品红、7 天内蓝色、更远绿色)、已过事件灰色标记、自动按距离排序(最近的排最前)。配色严格避免了黄色系(红/品红/蓝/绿四色方案),确保所有文字与背景保持足够对比度。
阅读完本文,你将能够:
- 使用
DateAPI 进行日期差值计算 - 实现四色距离标记系统(根据剩余天数动态着色)
- 构建按距离自动排序的列表
- 处理"已过事件"的特殊显示逻辑
- 构建图标选择器 + 日期输入的表单弹窗
二、数据模型与日期运算
2.1 事件实体
interface CountdownEvent {
id: number;
name: string;
icon: string;
date: string; // YYYY-MM-DD
}
日期用 YYYY-MM-DD 格式存储——与日记本相同策略。8 个预设图标涵盖常见事件类型:生日🎂、纪念日💍、毕业🎓、旅行✈️、搬家🏠、节日🎄、庆祝🎉、自定义📅。
2.2 核心日期函数
两个函数构成日期系统的全部基础:
function todayStr(): string {
const d = new Date();
return `${d.getFullYear()}-${(d.getMonth()+1).toString().padStart(2,'0')}-${d.getDate().toString().padStart(2,'0')}`;
}
function daysBetween(from: string, to: string): number {
const f = new Date(from);
const t = new Date(to);
return Math.floor((t.getTime() - f.getTime()) / (1000 * 60 * 60 * 24));
}
todayStr() 返回当天日期的字符串(如 2026-06-11),用于构建"今天"的参照点。
daysBetween() 计算两个日期之间相差的整天数。核心公式:(t.getTime() - f.getTime()) / (1000 * 60 * 60 * 24)。getTime() 返回毫秒时间戳,差值除以 86400000(一天的毫秒数)得到天数。Math.floor 向下取整——因为我们只关心完整的"天",不关心小时和分钟。
正值表示目标日期在未来(“还有 N 天”),负值表示目标日期已过(“已过 N 天”),零表示今天。
2.3 动态排序
事件列表按距今天的距离排序——最近的排在最前:
sortedEvents(): CountdownEvent[] {
return [...this.events].sort((a, b) => {
const da = daysBetween(todayStr(), a.date);
const db = daysBetween(todayStr(), b.date);
if (da < 0 && db >= 0) return 1; // a 已过,b 将来 → a 排后
if (db < 0 && da >= 0) return -1; // b 已过,a 将来 → a 排前
return da - db; // 都将来或都已过 → 按距离
});
}
排序规则:
- 将来的事件排在已过事件前面
- 将来事件按距离近 → 远排列(今天排第一,28 天后排最后)
- 已过事件按距离近 → 远排列(昨天排最前,1 年前的排最后)
用户打开页面后,最紧迫的事件(今天、明天)自然出现在第一位,不需要手动调整。
三、四色距离标记
3.1 颜色设计
用四种颜色标记事件的距离:
| 距离 | 颜色 | 色值 | 语义 |
|---|---|---|---|
| 已过 | 灰色 | #888899 |
“这件事已经发生了” |
| 0-1 天 | 红色 | #FF4D4F |
“就是今天/明天!紧迫” |
| 2-3 天 | 品红 | #EB2F96 |
“快到了,做好准备” |
| 4-7 天 | 蓝色 | #1677FF |
“还有一周,心里有数” |
| 8 天以上 | 绿色 | #52C41A |
“还早,轻松等待” |
四种颜色构成了一个情感梯度:红色 = 紧迫感,品红 = 关注,蓝色 = 平常,绿色 = 从容。用户不需要阅读"还有 X 天"的文字,光看颜色就能感知每件事的紧迫程度。
注意这里完全避免了黄色/金色——红色 → 品红 → 蓝色 → 绿色的渐变中,没有黄色参与。品红(#EB2F96)在 2-3 天的位置替代了本来可能使用橙色/黄色的角色,且白字对比度远优于黄色。
3.2 动态文字标签
除了颜色,每个事件还有对应的文字标签:
proximityLabel(days: number): string {
if (days < 0) return `已过 ${Math.abs(days)} 天`;
if (days === 0) return '今天!';
if (days === 1) return '明天';
return `还有 ${days} 天`;
}
"今天!“和"明天"是两个特殊情况——它们给人最强的紧迫感和期待感,所以用特殊的文字而非简单的"还有 0 天"或"还有 1 天”。感叹号增加了情感色彩——今天发生的事值得一个感叹号。
3.3 半透明背景色
每个事件卡片的图标区域也有对应的半透明背景色:
proximityBg(days: number): string {
if (days < 0) return '#88889915';
if (days <= 1) return '#FF4D4F15';
if (days <= 3) return '#EB2F9615';
if (days <= 7) return '#1677FF15';
return '#52C41A15';
}
15(十六进制)= 约 8% 不透明度。这是一个非常淡的背景色——刚好能感知到色相,但不会和白色卡片背景产生冲突。如果背景色太深(比如 30-50% 不透明度),会和卡片文字争夺视觉注意力。
四、事件管理
4.1 添加事件
点击右下角 FAB("添加事件"蓝色按钮)弹出底部表单,包含三个输入区:
图标选择器:8 个 emoji 图标排列成行。选中态蓝底浅蓝边框(#1677FF22 + 1.5vp 边框),未选中态透明。与日记本的心情选择器不同,这里用边框区分选中态(而非不透明度)——因为图标都需要完整辨认,不适合降低不透明度。
名称输入:TextInput 组件,占位文字"事件名称,如’生日派对’"。限制单行。
日期输入:TextInput 组件,占位文字"日期,如 2026-12-25"。使用自由文本输入而非 DatePicker 的原因是:DatePicker 选择未来几个月甚至几年的日期需要多次滑动,不如直接输入 YYYY-MM-DD 快。
底部两个按钮——取消(灰色)和确认添加(蓝色,输入为空时灰色禁用)。
4.2 删除事件
每条事件卡片右侧有 × 按钮。点击直接删除——与日记本的删除逻辑相同,“可见即确认”,不需要额外的确认弹窗。
五、UI 设计
5.1 深色标题栏 + 白色卡片列表
与记账本类似的布局架构:
- 深色标题栏(
#1a1a2e):标题"⏳ 倒数日" + 事件计数 - 浅灰背景(
#F2F3F5):白色事件卡片列表 - 蓝色 FAB:右下角悬浮
5.2 事件卡片布局
每张卡片水平排列四个元素:
- 图标区(56×56vp 圆角方块,淡色背景):大号 emoji
- 信息区(flex 1):事件名称(加粗深色)+ 日期(浅灰小字)
- 天数标签:动态文字 + 动态颜色(红色/品红/蓝色/绿色/灰色)
- 删除按钮:浅灰 ×
卡片之间用 1vp 的细微间隙分隔,第一张卡片顶部与列表顶部对齐(borderRadius 特殊处理)。
5.3 空状态
当事件列表为空时,显示空状态引导:📅 emoji + “还没有倒数事件” + “点击右下角 + 添加一个”。空状态是用户教育的一部分——告诉用户这个页面是做什么的、怎么开始使用。
六、完整代码结构
CountdownPage
├── Stack(根容器)
│ ├── Column(主界面)
│ │ ├── Row(标题栏:⏳ 倒数日 + N 个事件)
│ │ └── Scroll(事件列表)
│ │ ├── if 空态 → 空状态引导
│ │ └── ForEach → Row(事件卡片)
│ │ ├── Column(图标 + 半透明色底)
│ │ ├── Column(名称 + 日期)
│ │ ├── Text(还有/已过 N 天,动态色)
│ │ └── Text(删除 ×)
│ ├── Button(FAB:"+ 添加事件")
│ └── if showAdd → Column(底部弹窗)
│ ├── Row(8 个图标选择)
│ ├── TextInput(事件名称)
│ ├── TextInput(日期 YYYY-MM-DD)
│ └── Row(取消 + 确认添加)
七、总结
本文从零构建了一个倒数日追踪器。与前几篇的功能型应用不同,倒数日的核心是日期运算 + 视觉距离编码。
核心要点回顾:
-
日期运算:
todayStr()返回当天日期字符串,daysBetween()计算两个日期的整天差。全部使用原生DateAPI,无需第三方库。 -
四色距离标记:红色(0-1 天)= 紧迫,品红(2-3 天)= 关注,蓝色(4-7 天)= 平常,绿色(8 天+)= 从容。已过事件灰色。颜色构成情感梯度,用户不需要读文字就能感知紧迫程度。
-
自动排序:
sortedEvents()将将来事件排在已过事件前面,同类事件按距离升序排列。用户打开页面后最近的事件自然出现在第一位。 -
配色安全:四色方案中完全没有黄色/金色——红色 → 品红 → 蓝色 → 绿色的情感梯度中,品红替代了传统方案中的橙色/黄色位置。所有颜色与白字对比度均达标。
-
动态标签:"今天!"和"明天"使用特殊文字而非数字,增加情感色彩和紧迫感。
-
图标选择器:用边框区分选中态(而非不透明度),因为所有图标都需要完整辨认。
倒数日是"小而美"的典型——一个界面、一个列表、几个事件卡片。但日期运算、距离排序、颜色编码、情感标签这些细节组合在一起,形成了一个用户愿意每天打开看的工具。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)