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 可直接插入任何页面

📖 六、扩展思考

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

📘 下篇预告

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


相关推荐
No丶slovenly6 分钟前
flutter笔记-输入框
前端·笔记·flutter
国产化创客36 分钟前
ESP32+Web实现智能气象站
前端·物联网·智能家居·智能硬件
Volunteer Technology40 分钟前
DynamicTP动态线程池(四)
java·spring boot·后端·spring
野犬寒鸦43 分钟前
从零起步学习并发编程 || 第九章:Future 类详解及CompletableFuture 类在项目实战中的应用
java·开发语言·jvm·数据库·后端·学习
uzong1 小时前
软件工程师应该尽量改掉的坏习惯
后端
阿林来了1 小时前
Flutter三方库适配OpenHarmony【flutter_speech】— 开发环境搭建
flutter·harmonyos·鸿蒙
coderYYY1 小时前
VSCode终端启动报错
前端·ide·vscode·npm·编辑器
sdff113962 小时前
【Flutter】NewsHub跨平台开发:Flutter适配鸿蒙实战教程
flutter·华为·harmonyos
高山上有一只小老虎2 小时前
SpringBoot项目单元测试
spring boot·后端·单元测试
无巧不成书02182 小时前
【开源鸿蒙+Flutter实战】Step One复盘(DAY1-7)|环境闭环+网络请求+列表交互 全避坑(真机验证版)
flutter·开源·harmonyos