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

📖 六、扩展思考

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

📘 下篇预告

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


相关推荐
SuperEugene15 小时前
TypeScript+Vue 实战:告别 any 滥用,统一接口 / Props / 表单类型,实现类型安全|编码语法规范篇
开发语言·前端·javascript·vue.js·安全·typescript
luom010215 小时前
SpringBoot - Cookie & Session 用户登录及登录状态保持功能实现
java·spring boot·后端
黄俊懿15 小时前
【架构师从入门到进阶】第二章:系统衡量指标——第一节:伸缩性、扩展性、安全性
分布式·后端·中间件·架构·系统架构·架构设计
我是永恒15 小时前
上架一个跨境工具导航网站
前端
电子羊15 小时前
Spec 编程工作流文档
前端
希望永不加班15 小时前
SpringBoot 核心配置文件:application.yml 与 application.properties
java·spring boot·后端·spring
弓.长.15 小时前
ReactNative for OpenHarmony项目鸿蒙化三方库:react-native-pdf — PDF文档查看器
react native·pdf·harmonyos
GISer_Jing15 小时前
从CLI到GUI桌面应用——前端工程化进阶之路
前端·人工智能·aigc·交互
散峰而望15 小时前
【基础算法】从入门到实战:递归型枚举与回溯剪枝,暴力搜索的初级优化指南
数据结构·c++·后端·算法·机器学习·github·剪枝
还是大剑师兰特16 小时前
Vue3 报错:computed value is readonly 解决方案
前端·vue.js