7、打造鸿蒙原生日历组件:自定义 UI + 数据交互(附实操案例与效果图)

传统 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 状态管理 切换月份后,自动更新日历
动态列表渲染 使用 ForEachGrid 实现网格布局
响应式更新 无需手动刷新界面,状态变化即自动重绘
组件复用 CalendarView 可直接插入任何页面

📖 六、扩展思考

  • ✅ 支持点击某天触发回调(例如签到、打卡)
  • ✅ 加入节假日、农历显示
  • ✅ 集成天气预报在日历格子中(超酷)

📘 下篇预告

《鸿蒙动画开发实战:做一个会跳舞的按钮》------深入鸿蒙动画系统,打造灵动交互体验!


相关推荐
止语Lab1 小时前
Go并发编程实战:Channel 还是 Mutex?一个场景驱动的选择框架
开发语言·后端·golang
懂懂tty2 小时前
React状态更新流程
前端·react.js
Utopia^2 小时前
鸿蒙flutter第三方库适配 - 联系人备份工具
flutter·华为·harmonyos
小码哥_常2 小时前
告别繁琐!手把手教你封装超实用Android原生Adapter基类
前端
小码哥_常2 小时前
Spring Boot一键限速:守护你的接口“高速路”
后端
阿丰资源2 小时前
基于SpringBoot的物流信息管理系统设计与实现(附资料)
java·spring boot·后端
skywalk81632 小时前
pytest测试的时候这是什么意思?Migrating <class ‘kotti.resources.File‘>
前端·python
一只蝉nahc3 小时前
vue使用iframe内嵌unity模型,并且向模型传递信息,接受信息
前端·vue.js·unity
王码码20353 小时前
Go语言的包管理:从GOPATH到Go Modules
后端·golang·go·接口
子兮曰3 小时前
Bun v1.3.12 深度解析:新特性、性能优化与实战指南
前端·typescript·bun