一、引言

日期选择是移动端应用中最基础也最频繁的操作之一。订机票时选择出发日期和返程日期,酒店预订中选择入住和退房日期,日程管理中选择会议时间——日期选择无处不在。在传统开发中,实现一个日期选择器需要处理日历算法(每月天数、闰年判断)、滚动交互、日期格式化、范围校验等复杂逻辑,工作量极大且容易出错。

HarmonyOS 提供了 DatePicker 组件——一个内置完整日历逻辑的日期选择器。它自动处理月份天数、闰年、日期范围等复杂计算,支持公历/农历切换,通过声明式 API 即可完成日期选择的所有功能。开发者只需指定起始日期、结束日期和默认选中日期,DatePicker 负责剩下的所有交互细节。

本文通过一个出行日期选择 Demo 深入讲解 DatePicker 组件的核心用法:如何设置日期范围?如何联动出发和返回日期?如何切换农历显示?以及 onDateChange 回调的使用模式。

阅读完本文,你将能够:

  • 使用 DatePicker 替代自定义日历选择方案
  • 掌握 start/end/selected 构造函数参数
  • 使用 lunar() 链式方法切换农历显示
  • 实现双 DatePicker 联动(出发日期 → 返回日期)
  • 理解 onDateChange 回调与日期校验的最佳实践

二、DatePicker 组件 API 总览

2.1 构造函数

DatePicker(options?: DatePickerOptions)

DatePickerOptions 包含三个可选参数:

interface DatePickerOptions {
  start?: Date;     // 起始日期,默认 1970-1-1
  end?: Date;       // 结束日期,默认 2100-12-31
  selected?: Date;  // 默认选中日期,默认系统当前日期
}
参数 类型 默认值 说明
start Date 1970-01-01 可选择的最早日期
end Date 2100-12-31 可选择的最晚日期
selected Date 当前系统日期 初始选中的日期

2.2 链式方法

// 农历显示
.lunar(value: boolean): DatePickerAttribute

// 日期变化回调
.onDateChange(callback: Callback<Date>): DatePickerAttribute
方法 说明
lunar(boolean) 是否显示农历日历
onDateChange(Callback<Date>) 用户选择新日期时的回调,参数为 Date 对象

注意:onChange 已在 API 10 废弃,应使用 onDateChangeonDateChange 的回调参数是 Date 对象而非 DatePickerResult,可以直接用于日期比较和运算。

2.3 DatePicker vs 自定义日历

特性 DatePicker 自定义日历
日历算法 内置(月份天数、闰年) 需手动实现
滚动交互 内置(滚轮式选择) 需手动实现
农历支持 一行 .lunar(true) 需引入农历库
日期范围限制 start/end 参数 需手动校验
样式定制 有限(textStyle/selectedTextStyle) 完全自由
双日期联动 需手动处理 onDateChange 同左

DatePicker 适合标准日期选择场景(出行、预订、日程),自定义日历适合需要高度定制 UI 的场景(如日历视图、日期标记)。
在这里插入图片描述

三、Demo 设计:出行日期选择

3.1 功能概述

Demo 是一个出行日期选择页面,模拟机票/酒店预订的日期选择流程:

  1. 出发日期选择:DatePicker 选择出发日期,范围从今天起一年内
  2. 返回日期选择:DatePicker 选择返回日期,start 自动跟随出发日期
  3. 智能联动:选择出发日期时,如果返回日期早于出发日期,自动调整为出发日期+1天
  4. 农历切换:Toggle 开关控制两个 DatePicker 同时切换农历/公历显示
  5. 行程信息:实时显示出发/返回日期、星期、行程天数
  6. 日期详情表:信息汇总卡片展示当前所有日期相关参数

3.2 出发日期选择器

DatePicker({
  start: new Date(),                                    // 从今天开始
  end: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000), // 到一年后
  selected: this.departDate                             // 初始为今天
})
  .lunar(this.showLunar)                                // 农历开关跟随 @State
  .onDateChange((value: Date) => {
    this.departDate = value;
    if (value.getTime() >= this.returnDate.getTime()) {
      this.returnDate = new Date(value.getTime() + 24 * 60 * 60 * 1000);
    }
    this.updateTripDays();
  })

关键逻辑:

  • start 设置为 new Date() 确保用户无法选择过去的日期
  • end 设置为一年后,控制可选范围
  • onDateChange 回调中检查:如果选择的出发日期 >= 返回日期,自动将返回日期设为出发日期+1天
  • 这种"智能联动"是出行日期选择的标配体验——确保不会出现"返回早于出发"的无效状态

3.3 返回日期选择器

DatePicker({
  start: new Date(this.departDate.getTime() + 24 * 60 * 60 * 1000),  // 出发日期+1天
  end: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000),              // 到一年后
  selected: this.returnDate
})
  .lunar(this.showLunar)
  .onDateChange((value: Date) => {
    this.returnDate = value;
    this.updateTripDays();
  })

返回日期的 start 动态绑定到 this.departDate.getTime() + 1天——当用户修改出发日期后,返回日期选择器的起始日期自动变化。这确保了用户在选择返回日期时,不可能选到早于出发日期的日期。DatePicker 的 start 参数在运行时重新计算,每次 @State departDate 变化后,返回选择器的 start 也跟随更新。

这种"响应式日期范围"的设计比静态校验更优雅——用户根本看不到不合法的日期选项,而非选中后报错。

3.4 农历切换

@State showLunar: boolean = false;

// 在 Toggle 中切换
Toggle({ type: ToggleType.Switch, isOn: this.showLunar })
  .selectedColor('#1677FF')
  .onChange((v: boolean) => { this.showLunar = v; })

// 两个 DatePicker 都绑定到同一 @State
DatePicker({ ... })
  .lunar(this.showLunar)   // 出发选择器
DatePicker({ ... })
  .lunar(this.showLunar)   // 返回选择器

lunar() 接受一个 boolean 值。当 showLunar 变化时,两个 DatePicker 同步切换农历/公历显示。农历模式在日期滚轮的每个公历日期下方显示对应的农历日期,对于国内用户查看传统节日(春节、中秋等)非常实用。

3.5 行程天数计算

updateTripDays(): void {
  const diff = this.returnDate.getTime() - this.departDate.getTime();
  this.tripDays = Math.round(diff / (24 * 60 * 60 * 1000));
}

通过两个 Date 对象的时间戳差值计算天数。getTime() 返回毫秒数,除以一天的毫秒数(24×60×60×1000)得到天数。Math.round 处理浮点精度问题。

3.6 页面结构

┌──────────────────────────────────────────┐
│ 📅 日期选择(深色标题栏)                  │
├──────────────────────────────────────────┤
│ 📘 DatePicker 组件说明卡片                │
├──────────────────────────────────────────┤
│ ┌────────────────────────────────────┐   │
│ │   2026-06-26   →   2026-07-03      │   │
│ │     周五       7天      周四       │   │
│ └────────────────────────────────────┘   │
├──────────────────────────────────────────┤
│ 出发日期  2026-06-26 周五                │
│ ┌────────────────────────────────────┐   │
│ │         [DatePicker]               │   │
│ │   年    月    日                   │   │
│ └────────────────────────────────────┘   │
├──────────────────────────────────────────┤
│ 返回日期  2026-07-03 周四                │
│ ┌────────────────────────────────────┐   │
│ │         [DatePicker]               │   │
│ │   年    月    日                   │   │
│ └────────────────────────────────────┘   │
├──────────────────────────────────────────┤
│ 农历显示                    [====]      │
├──────────────────────────────────────────┤
│ 日期详情                                │
│ ┌────────────────────────────────────┐   │
│ │ 出发日期    2026-06-26             │   │
│ │ 出发星期    周五                   │   │
│ │ 返回日期    2026-07-03             │   │
│ │ 返回星期    周四                   │   │
│ │ 行程天数    7 天                   │   │
│ │ 农历模式    关闭                   │   │
│ └────────────────────────────────────┘   │
└──────────────────────────────────────────┘

在这里插入图片描述

四、DatePicker 组件的最佳实践

4.1 日期范围的设计

日期范围的设置决定了用户体验的质量:

  • start 不应硬编码:Demo 中出发选择器的 start 为 new Date()(今天),返回选择器的 start 为 出发日期+1天。硬编码的日期(如 new Date('2026-01-01'))会导致 Demo 过期后无法使用。
  • end 要有合理的边界:Demo 中选择一年后作为 end。对于酒店预订(通常最多预订 6 个月),end 可以设得更近——减少用户的滚动量。
  • 动态 start:返回选择器的 start 绑定到出发日期,当出发日期变化时,返回的起始日期自动更新。这是 DatePicker 的响应式能力——start 参数在每次渲染时重新计算。

4.2 onDateChange 回调的使用模式

onDateChange 在用户每次滚动选择日期时触发。回调中应处理三件事:

  1. 更新 @State:将新日期赋值给状态变量,触发 UI 刷新
  2. 联动校验:如果存在多个 DatePicker 的依赖关系(如出发→返回),在回调中检查和修正
  3. 计算衍生数据:如行程天数、工作日判断等
.onDateChange((value: Date) => {
  // 1. 更新状态
  this.departDate = value;
  // 2. 联动校验
  if (value.getTime() >= this.returnDate.getTime()) {
    this.returnDate = new Date(value.getTime() + 24 * 60 * 60 * 1000);
  }
  // 3. 计算衍生数据
  this.updateTripDays();
})

4.3 农历模式的使用场景

农历模式通过 .lunar(true) 开启。适合以下场景:

  • 传统文化相关应用:需要显示农历日期、节气、传统节日
  • 出行日期选择:用户可能根据农历选择出行日期(如避开清明节、选择中秋节)
  • 国内用户群体:大多数中国用户对农历日期有认知需求

农历模式会增加 DatePicker 的显示密度——每个公历日期下方多一行农历文字。在小屏幕设备上需要考虑阅读舒适度。

4.4 双 DatePicker 联动模式

Demo 中的出发/返回双日期选择是一种经典的双 DatePicker 联动模式。其核心逻辑链为:

用户修改出发日期
  → onDateChange 更新 departDate
    → 检查 returnDate >= departDate(如否,自动调整)
    → 返回选择器的 start 变为 departDate + 1 天
    → 更新行程天数

这种联动通过 @State 的响应式更新实现了自动化的日期范围管理。开发者不需要手动调用 DatePicker 的"更新方法"——DatePicker 从 @State 变量中读取新的 start/end/selected 值,在下次渲染时自动应用。

4.5 DatePicker 与其他日期组件的配合

DatePicker 通常与以下组件配合使用:

  • TextClock:同时展示"当前选择的日期"和"实时时钟"
  • Text:显示格式化的日期字符串和星期
  • Toggle:控制农历开关
  • Button:确认选择、提交预约等操作

在 Demo 中,DatePicker 的选中日期通过 Text 展示在顶部摘要区域和底部详情表中,让用户可以清楚地看到当前选择,而非仅依赖 DatePicker 滚轮中的选中状态。

五、完整代码结构

DatePickerPage (~240 行)
├── 状态变量
│   ├── @State departDate — 出发日期
│   ├── @State returnDate — 返回日期(初始出发+7天)
│   ├── @State showLunar — 农历显示开关
│   └── @State tripDays — 行程天数
├── 方法
│   ├── updateTripDays() — 计算行程天数
│   ├── formatDate(d) — 日期格式化为 yyyy-MM-dd
│   └── getWeekday(d) — 获取星期中文名
├── 视图
│   ├── 标题栏 — 📅 日期选择
│   ├── 说明卡片 — DatePicker 组件介绍
│   ├── 行程摘要 — 出发/→/返回 + 天数
│   ├── 出发日期选择器 — DatePicker + 标题 + 已选日期
│   ├── 返回日期选择器 — DatePicker(start 联动)+ 标题 + 已选日期
│   ├── 农历 Toggle — Switch 控制
│   └── 日期详情表 — 6 行信息汇总
└── @Builder infoRow() — 详情行组件

六、总结

本文通过一个出行日期选择 Demo 深入讲解了 HarmonyOS 中的 DatePicker 日期选择器组件。DatePicker 将日历算法、滚动交互、范围管理封装为声明式组件,通过 start/end/selected 三个构造函数参数和 lunar()/onDateChange() 两个链式方法,覆盖了日期选择的绝大部分需求。

核心要点回顾:

  1. 三个构造函数参数控制范围start 指定最早可选日期,end 指定最晚可选日期,selected 指定初始选中日期。参数支持动态值,可在 @State 变化时自动更新。

  2. onDateChange 替代废弃的 onChange:回调参数是 Date 对象(非 DatePickerResult),可直接使用 getTime()getDay() 等方法进行日期比较和计算。

  3. lunar() 切换农历:一行代码实现公历/农历切换。农历模式在日期下方显示对应农历日期,适合国内用户和传统文化场景。

  4. 双 DatePicker 联动:通过 @State 变量建立依赖关系——返回选择器的 start 绑定到出发日期+1天,出发选择器的 onDateChange 中自动修正返回日期。响应式更新确保了日期范围的自动管理。

  5. DatePicker 是滚轮式选择器:与日历面板式选择器不同,DatePicker 使用滚轮选择年/月/日三列。这种交互适合精确日期选择,但不适合需要"看整个月"的场景。

DatePicker 是日期选择场景中的声明式方案——不需要日历算法、不需要手动范围校验、不需要农历库。三行声明(start + end + selected)替代了过去需要几十行代码的日历选择功能。

七、扩展思考

DatePicker 解决了标准日期选择需求,但在实际项目中,日期选择还有更多变化:

日期面板 vs 日期滚轮:DatePicker 是滚轮式选择器,适合精确选择具体日期。日历面板式选择(如酒店预订中的入住/退房日历,带有价格标记、节假日标记)需要自定义开发——DatePicker 不支持自定义日期单元格样式。

时分秒选择:DatePicker 只选择日期(年/月/日),不包括时间(时/分/秒)。如果需要同时选择日期和时间,需要结合 TimePicker 组件或使用 DatePickerDialogshowTime 选项。

DatePickerDialog:当不希望 DatePicker 占用页面空间时,可以使用弹窗模式——DatePickerDialog.show()。弹窗模式的选项更多(如确认/取消按钮样式、背景模糊效果),但它是一个模态弹窗,与内联 DatePicker 的交互模型不同。

日期格式化:DatePicker 本身不提供日期格式化功能——onDateChange 返回的是 Date 对象,开发者需要自行格式化为 yyyy-MM-ddMM月dd日 等字符串格式。Demo 中的 formatDate() 方法就是典型的日期格式化实现。

国际化和本地化:DatePicker 的星期和月份名称跟随系统语言自动切换。lunar() 模式下的农历文本也支持中英文环境。但在某些特定场景(如需要显示多种语言的日期),需要额外的本地化处理。

性能考量:DatePicker 本身是一个相对重的组件(包含年/月/日三列滚轮)。一页上放置两个 DatePicker(如 Demo 中的出发/返回),在低端设备上滚动可能有轻微延迟。对于性能敏感的场景,可以考虑按需渲染(点击展开 DatePicker,选择后折叠)。

理解 DatePicker 的定位——标准日期滚轮选择器——是正确使用它的关键。它是"选择日期"需求的标准解,但不是所有"日期相关 UI"的通用解。日历面板、日期范围选择器、日期标记等需求需要不同的实现方案。

通过本文的 Demo——出行日期选择,你将 DatePicker 的核心 API 和双选择器联动模式应用到实际的旅行预订场景中,构建了一个完整的日期选择交互流程。这个模式可以直接作为任何出行、预订、日程应用的日期选择起点模板。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐