传统 App 的日历大多笨重难用,鸿蒙 ArkTS 声明式开发让构建一个轻量、高颜值、可交互的日历变得异常简单。今天,我们从零手写一个支持切换月份的原生鸿蒙日历组件!
🎯 一、目标功能
- ✅ 展示当前月份的日历网格
- ✅ 点击左右按钮切换上/下个月
- ✅ 高亮当前日期
- ✅ 动态生成每月的天数布局
最终效果:
markdown
< 2025年4月 >
日 一 二 三 四 五 六
1 2 3 4 5
6 7 8 9 10 11 12
...
🧩 二、数据准备:生成日历网格
先封装一个生成指定年月的日历数据方法:
typescript
function generateCalendar(year: number, month: number): Array<number|null> {
const date = new Date(year, month - 1, 1);
const firstDay = date.getDay(); // 当月第一天是星期几
const daysInMonth = new Date(year, month, 0).getDate(); // 当月天数
const calendar: Array<number|null> = Array(firstDay).fill(null); // 填充空白
for (let i = 1; i <= daysInMonth; i++) {
calendar.push(i);
}
return calendar;
}
null
表示空白格子- 之后我们用
ForEach
渲染日历
🛠️ 三、编写日历组件 CalendarView
scss
@Entry
@Component
struct CalendarView {
@State currentYear: number = new Date().getFullYear()
@State currentMonth: number = new Date().getMonth() + 1
@State calendarData: Array<number|null> = []
aboutToAppear() {
this.updateCalendar();
}
updateCalendar() {
this.calendarData = generateCalendar(this.currentYear, this.currentMonth);
}
prevMonth() {
if (this.currentMonth === 1) {
this.currentYear -= 1;
this.currentMonth = 12;
} else {
this.currentMonth -= 1;
}
this.updateCalendar();
}
nextMonth() {
if (this.currentMonth === 12) {
this.currentYear += 1;
this.currentMonth = 1;
} else {
this.currentMonth += 1;
}
this.updateCalendar();
}
build() {
Column() {
// 顶部标题与切换按钮
Row() {
Button('<').onClick(() => this.prevMonth())
Text(`${this.currentYear}年${this.currentMonth}月`)
.fontSize(22)
.fontWeight(FontWeight.Bold)
.margin({ left: 12, right: 12 })
Button('>').onClick(() => this.nextMonth())
}
.justifyContent(FlexAlign.Center)
.margin({ bottom: 20 })
// 星期标题
Row() {
['日','一','二','三','四','五','六'].forEach(day => {
Text(day)
.width('13%')
.textAlign(TextAlign.Center)
.fontSize(16)
.fontWeight(FontWeight.Medium)
})
}
.margin({ bottom: 10 })
// 渲染日历网格
Grid({
columns: 7,
space: 6
}) {
ForEach(this.calendarData, (day, index) => {
if (day === null) {
Text('')
.width('13%')
.height(40)
} else {
const isToday = day === new Date().getDate()
&& this.currentMonth === new Date().getMonth() + 1
&& this.currentYear === new Date().getFullYear();
Text(`${day}`)
.width('13%')
.height(40)
.textAlign(TextAlign.Center)
.backgroundColor(isToday ? '#aaddff' : 'transparent')
.borderRadius(8)
}
}, item => `${item}-${Math.random()}`)
}
}
.padding(20)
.alignItems(HorizontalAlign.Center)
}
}
📸 四、实际效果图(示意)
运行效果👇(真实效果更丝滑)

- 当前日期高亮
- 支持上下切换月份
- 布局自适应屏幕宽度
(如果需要,我可以给你配高清自制示意图)
📌 五、实操亮点总结
技术点 | 说明 |
---|---|
@State 状态管理 |
切换月份后,自动更新日历 |
动态列表渲染 | 使用 ForEach 和 Grid 实现网格布局 |
响应式更新 | 无需手动刷新界面,状态变化即自动重绘 |
组件复用 | CalendarView 可直接插入任何页面 |
📖 六、扩展思考
- ✅ 支持点击某天触发回调(例如签到、打卡)
- ✅ 加入节假日、农历显示
- ✅ 集成天气预报在日历格子中(超酷)
📘 下篇预告
《鸿蒙动画开发实战:做一个会跳舞的按钮》------深入鸿蒙动画系统,打造灵动交互体验!