鸿蒙新特性:CalendarPicker 日历组件详解——构建一个日程管理应用
日历是时间管理类应用中最高频的交互界面之一。HarmonyOS NEXT ArkUI 提供了 CalendarPicker 组件,以月视图网格的方式展示完整日历,支持年份月份滑动切换、日期选中高亮和自定义日期范围。配合事件数据,可以轻松构建日程管理、酒店预订、排班管理等场景。
本文将深入讲解 CalendarPicker 的核心 API,并通过一个完整的"日程管理"实战案例,展示月历视图与事件数据的联动显示。
关键词:HarmonyOS、ArkUI、CalendarPicker、日历、日程管理、月视图
一、CalendarPicker 组件概览
CalendarPicker 是 ArkUI 日历时间组件家族中的第三个成员,与前两篇文章介绍的 DatePicker 和 TimePicker 不同,CalendarPicker 侧重于"以月为单位的时间空间可视化"。
| 组件 | 交互模式 | 主要用途 |
|---|---|---|
| DatePicker | 三列滚轮(年月日) | 精确日期选择 |
| TimePicker | 两列滚轮(时分) | 精确时间选择 |
| CalendarPicker | 月视图网格 | 日期浏览与可视化 |
CalendarPicker 的特点在于"一览性"——用户不需要滑动滚轮,直接在一个月视图网格中看到所有日期及其分布。这对于需要"看到某月有哪些日期被占用"的场景(如日程、排班、预订)非常关键。
二、CalendarPicker 核心 API
2.1 构造函数
CalendarPicker({
start: Date, // 可选的最早日期
end: Date, // 可选的最晚日期
selected: Date // 当前选中日期
})
三个参数与 DatePicker 的构造函数完全一致,但组件的视觉呈现完全不同——DatePicker 是滚轮,CalendarPicker 是网格。
2.2 onChange 回调
当用户点击日历网格中的某个日期时,onChange 被触发,返回选中的 Date 对象:
CalendarPicker({ ... })
.onChange((value: Date) => {
// value 是选中的 Date 对象
this.selTimestamp = value.getTime();
this.updateEvents(value);
})
注意:onChange 的参数类型是 Date,不是数字时间戳。这与某些其他 ArkUI 组件的回调不同,使用时要留意类型匹配。
2.3 关键属性
| 属性 | 类型 | 说明 |
|---|---|---|
.onChange() |
Callback<Date> |
选中日期变化回调 |
.edgeAlign() |
CalendarEdgeAlign |
日期对齐方式 |
.fadeEffect() |
boolean |
非当前月日期是否淡化 |
2.4 CalendarEdgeAlign 对齐模式
CalendarPicker 支持两种日期对齐模式:
CalendarEdgeAlign.START:日期从左边开始排列CalendarEdgeAlign.END:日期从右边开始排列
大多数场景使用默认的 START 对齐即可。
三、实战:日程管理
我们实现一个日程管理页面,包含:
- CalendarPicker 日历视图:按月浏览,点击选择日期
- 选中日期信息展示:显示选中日期、星期几、当年第几天
- 当日日程列表:根据选中日期展示对应的日程事件
- "今天"按钮:一键跳转回当前日期
3.1 事件数据结构
事件数据以日期为键(MM-DD 格式),存储日程的时间、标题和标识颜色:
interface EventItem {
date: string; // 'MM-DD'
title: string;
time: string; // '10:00 - 11:30' 或 '全天'
color: string; // 左侧色条颜色
}
private events: EventItem[] = [
{ date: '07-01', title: '团队周会 — 产品迭代评审',
time: '10:00 - 11:30', color: '#1677FF' },
{ date: '07-03', title: '技术分享:ArkUI 自定义组件',
time: '14:00 - 15:00', color: '#52C41A' },
{ date: '07-07', title: '版本 2.5.0 发布上线',
time: '09:00 - 12:00', color: '#FF5722' },
// ... 更多事件
];
这个数据结构设计的关键在于 date 字段使用 MM-DD 而非完整日期——这样同月同日的事件在不同年份也能匹配,同时保持了数据的简洁性。
3.2 日期选中处理
当用户在日历上点击某个日期时,更新选中状态并查询该日期的日程:
updateSelection(date: Date): void {
this.selTimestamp = date.getTime();
const y = date.getFullYear().toString();
const m = (date.getMonth() + 1).toString().padStart(2, '0');
const d = date.getDate().toString().padStart(2, '0');
this.selDateStr = y.concat('-', m, '-', d);
const weekdays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
this.selWeekday = weekdays[date.getDay()];
// 查询当日事件
const key = m.concat('-', d);
this.selEvents = [];
for (let i = 0; i < this.events.length; i++) {
if (this.events[i].date === key) {
this.selEvents.push(this.events[i]);
}
}
}
这里用到的几个关键 JavaScript Date API:
date.getMonth()+ 1:转换为 1-based 月份date.getDay():获取星期几(0=周日,6=周六)date.getFullYear():获取 4 位年份
3.3 当年第几天计算
辅助信息展示——计算选中日期是当年的第几天:
dayOfYear(date: Date): number {
const start = new Date(date.getFullYear(), 0, 1);
return Math.floor(
(date.getTime() - start.getTime()) / (1000 * 60 * 60 * 24)
) + 1;
}
原理:用目标日期的时间戳减去当年 1 月 1 日的时间戳,得到毫秒差,再换算为天数。加 1 是因为 1 月 1 日是第 1 天而非第 0 天。
3.4 CalendarPicker 集成
将 CalendarPicker 嵌入页面中,指定日期范围和选中日期:
CalendarPicker({
start: new Date(2025, 0, 1),
end: new Date(2027, 11, 31),
selected: new Date(this.selTimestamp)
})
.onChange((value: Date) => { this.onCalendarChange(value); })
日期范围设置为 2025-2027,实际项目中可以根据需求调整。selected 通过 selTimestamp 状态驱动,实现"今天"按钮跳转后的视觉同步。
3.5 日程列表渲染
根据选中日期动态渲染日程列表,每个事件使用左侧色条标识类型:
if (this.selEvents.length === 0) {
Row() {
Text('暂无日程安排')
.fontSize(14).fontColor('#CCCCDD')
}
.width('100%').height(48).justifyContent(FlexAlign.Center)
.backgroundColor('#FAFAFC').borderRadius(10)
} else {
ForEach(this.selEvents, (evt: EventItem) => {
Row() {
Row()
.width(4).height(36).borderRadius(2)
.backgroundColor(evt.color).margin({ right: 12 })
Column() {
Text(evt.title).fontSize(14).fontColor('#1a1a2e')
.fontWeight(FontWeight.Medium).maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
Text(evt.time).fontSize(11).fontColor('#9999AA')
}
.alignItems(HorizontalAlign.Start).layoutWeight(1)
}
.width('100%').padding({ top: 10, bottom: 10, left: 12, right: 12 })
.backgroundColor('#FFFFFF').borderRadius(10).margin({ bottom: 6 })
}, (evt: EventItem, idx: number) => idx.toString())
}
空状态与有数据状态的分支渲染是一个重要的 UX 细节。当选中日期没有日程时,不应留白,而要明确告知用户"暂无日程安排"。这种设计让用户确信"不是 bug,而是这天真的没有安排"。
3.6 "今天"按钮
一个简单的"今天"按钮,点击后选中日期跳转回当前日期:
Button('今天')
.fontSize(13).fontColor('#1677FF').fontWeight(FontWeight.Medium)
.padding({ top: 6, bottom: 6, left: 14, right: 14 })
.borderRadius(14).backgroundColor('#EEF3FF')
.onClick(() => { this.goToday(); })
goToday() 调用 updateSelection(new Date()),同时更新 selTimestamp 状态,CalendarPicker 自动同步选中日期。
四、交互流程
- 进入页面:CalendarPicker 显示当前月份,今天的日期高亮,选中日期显示今日信息,日程列表展示今日事件
- 浏览月份:左右滑动切换月份,日历网格更新
- 选择日期:点击日历中的某个日期(例如 7 月 7 日),选中高亮切换,下方信息卡片更新为 7 月 7 日的信息,日程列表刷新为当日的 1 个事件
- 查看空日期:点击没有事件的日期(例如 7 月 2 日),日程列表显示"暂无日程安排"
- 返回今天:浏览到其他月份后,点击"今天"按钮,日历跳回当前月并选中今天
五、设计要点总结
5.1 数据驱动的日程查询
事件数据以 MM-DD 格式存储,查询时直接用字符串拼接匹配。这种设计简单直接,适合事件数量在百条以下的场景。对于更大规模的数据(数千条),可以考虑使用 Map<string, EventItem[]> 结构优化查询效率。
5.2 色条标识
日程列表的每个事件使用左侧 4px 宽的色条标识类型——蓝色(会议)、绿色(分享培训)、橙红(发布重要节点)、紫色(设计评审)、粉色(团建活动)。色条在视觉上比文字标签更轻量,用户扫一眼就能区分事件类型。
5.3 空状态处理
空状态不是一个"边缘情况"——对于日程管理来说,大多数日期的确是空的。因此空状态的 UI 设计同样重要。使用浅灰底色 + 居中文字,既不被忽略,也不抢占视线。
六、总结
CalendarPicker 是鸿蒙日历时间组件体系中的"可视化担当"。它不追求精确到分钟的精度(那是 TimePicker 的职责),也不追求快速输入(那是 DatePicker 的特长),而是提供一个"一览无余"的月视图——这正是日程管理、排班预订等场景最需要的。
本文通过"日程管理"这个实战案例,覆盖了:
- CalendarPicker 的月视图网格交互
onChange回调的类型(Date而非number)- 事件数据驱动的高亮与查询
- 空状态与有数据状态的分支渲染
- "今天"按钮的状态同步
至此,鸿蒙日历时间组件三部曲(DatePicker、TimePicker、CalendarPicker)全部完成。三个组件各司其职,组合起来可以构建从日期选择到时间设定再到日历可视化的完整时间交互方案。
本文是"鸿蒙新特性"系列博客的第 19 篇,完整代码可在 DevEco Studio 6.1.1 (API 24) 环境中编译运行。Demo 包含 4 个交互点——日历月视图浏览、日期选中与信息展示、日程列表动态渲染、今日按钮跳转——覆盖了日程管理的基础场景。建议读者在此基础上扩展"日程添加"(弹窗表单写入新事件)和"月份标记"(在日历网格中显示有事件的日期点),作为进阶练习。
更多推荐



所有评论(0)