鸿蒙 ArkTS 实战:选择器演示 Select/DatePicker/TimePicker(示例 92)

引言
选择器(Picker)是用户输入类应用中最常见的交互组件。比起手动在文本框中输入文字或数字,选择器通过预设选项列表、滚轮、日历等可视化的方式,让用户以更低的认知成本完成数据录入。HarmonyOS NEXT 提供了 Select 下拉选择器、DatePicker 日期选择器、TimePicker 时间选择器三种原生组件,分别对应离散列表选择、日期选择、时间选择三大场景。
示例 92 以「选择器演示」为主题,将三种选择器组合在一个页面中,构建了一个类似「预约信息填写」的表单场景:用户先从下拉列表中选择城市,再通过日期滚轮选择日期,最后通过时间滚轮选择时间,页面底部实时汇总三种选择结果。此外还提供了「重置」按钮,一键恢复所有选择器到默认状态。整个流程涉及 Select 的 selected 与 value 绑定、DatePicker 的 onChange 回调与日期格式化、TimePicker 的 24 小时制与时间格式化、以及多选择器的状态联动,几乎涵盖了选择器组件的全部核心用法。
这篇文章会严格按源码顺序,先介绍应用的整体功能与布局结构,再拆解三种选择器的核心属性与事件,接着逐段解读 .ets 源码中的选择器配置、格式化方法、重置逻辑,然后分析日期/时间格式化的技巧、SelectOption 数据结构、24 小时制切换,最后给出运行操作指南、可扩展方向与常见问题调试技巧。读完后,你不仅能看懂这一个选择器演示页面,还能举一反三,把它应用到预约表单、订单筛选、日程安排等任何需要选择器组件的场景。
1. 应用概述与功能
「选择器演示」是一个面向表单输入场景的工具型页面,交互路径清晰:选择城市 → 选择日期 → 选择时间 → 查看汇总结果 → 可重置。
页面自上而下分为五块区域:顶部返回栏(返回按钮 + 标题「选择器演示」);城市选择卡片(标签 + 当前值 + Select 下拉组件);日期选择卡片(标签 + DatePicker 滚轮);时间选择卡片(标签 + TimePicker 滚轮);汇总结果卡片(城市/日期/时间三行蓝色文字);底部「重置」按钮。
1.1 核心功能清单
- 城市下拉选择:使用
Select组件提供北京、上海、广州、深圳四个城市选项,支持显示当前选中值。 - 日期选择:使用
DatePicker组件提供日期滚轮,支持年月日三列滚动选择,初始值为当前日期。 - 时间选择:使用
TimePicker组件提供时间滚轮,支持时/分两列滚动选择,24 小时制。 - 实时汇总:页面底部卡片实时显示三种选择器的当前值,用户每次操作都会立即更新。
- 一键重置:点击「重置」按钮恢复所有选择器到默认值(城市=北京,日期=今天,时间=当前),并弹出提示。
1.2 技术要点一览
整个示例用到的关键技术对「表单选择器」类页面很有代表性:Select 组件的 selected/value/onSelect 三件套、DatePicker 的 selected 初始值与 onChange 回调、TimePicker 的 useMilitaryTime 24 小时制开关、SelectOption 数据结构的定义、日期时间的格式化方法(pad 补零 + getFullYear/getMonth/getDate/getHours/getMinutes)、以及多个 @State 状态变量之间的联动更新。把这些要点串起来,就构成了一条完整的「选择器交互 → 状态更新 → 格式化 → 汇总展示」的数据流。
2. 核心知识点
在逐段读代码之前,先把选择器演示页面承载的 ArkTS 核心知识讲清楚。
2.1 Select 组件
Select 是 ArkUI 提供的下拉选择器组件,用于从预定义的选项列表中选择一个值:
Select(this.cities)
.selected(this.cityIdx)
.value(this.cityText)
.font({ size: 16, weight: FontWeight.Medium })
.width('100%')
.onSelect((index: number, value: string) => {
this.cityIdx = index;
this.cityText = value;
})
Select 组件的核心属性和事件:
- 数据源:构造函数接收一个
SelectOption[]数组,每个元素是一个{ value: string }对象。 selected:设置当前选中项的索引,用于高亮显示选中项。value:设置选择器显示的文本,即选中后输入框中展示的文字。onSelect:选择回调,参数为(index: number, value: string),分别在用户选择某项时触发。
selected 和 value 通常需要同时设置——selected 控制下拉列表中的高亮项,value 控制输入框中显示的文字。如果只设置 value 不设置 selected,下拉列表中不会高亮任何项。
2.2 SelectOption 数据结构
Select 组件的数据源是 SelectOption[] 类型:
private cities: SelectOption[] = [
{ value: '北京' },
{ value: '上海' },
{ value: '广州' },
{ value: '深圳' }
];
SelectOption 是 ArkUI 内置的接口,每个选项是一个包含 value 字段的对象。value 是显示在下拉列表和输入框中的文字。虽然 SelectOption 还支持 icon 字段(选项图标),但本示例只使用了 value 字段,保持简洁。
2.3 DatePicker 组件
DatePicker 是 ArkUI 提供的日期选择器组件,以滚轮的形式展示年、月、日三列:
DatePicker({ selected: this.date })
.lunar(false)
.onChange((value: DatePickerResult) => {
this.date = new Date(value.year!, value.month! - 1, value.day!);
this.dateText = value.year! + '-' + this.pad(value.month!) + '-' + this.pad(value.day!);
})
DatePicker 的核心属性和事件:
selected:构造参数,设置初始选中的日期,类型为Date。lunar:是否显示农历,false为公历,true为农历。onChange:选择回调,参数为DatePickerResult类型,包含year、month、day三个字段。
注意 DatePickerResult 中的 month 是 1-based(1~12),而 JavaScript Date 的 month 是 0-based(0~11),所以在构造 Date 对象时需要 value.month! - 1。
2.4 TimePicker 组件
TimePicker 是 ArkUI 提供的时间选择器组件,以滚轮的形式展示时、分两列:
TimePicker({ selected: this.time })
.useMilitaryTime(true)
.onChange((value: TimePickerResult) => {
this.timeText = this.pad(value.hour) + ':' + this.pad(value.minute);
})
TimePicker 的核心属性和事件:
selected:构造参数,设置初始选中的时间,类型为Date。useMilitaryTime:是否使用 24 小时制,true为 24 小时制(0~23),false为 12 小时制(AM/PM)。onChange:选择回调,参数为TimePickerResult类型,包含hour和minute两个字段。
2.5 日期时间格式化
示例定义了两个辅助方法用于格式化日期和时间:
private pad(n: number): string {
return n < 10 ? '0' + n : n.toString();
}
private formatDate(d: Date): string {
return d.getFullYear() + '-' + this.pad(d.getMonth() + 1) + '-' + this.pad(d.getDate());
}
private formatTime(t: Date): string {
return this.pad(t.getHours()) + ':' + this.pad(t.getMinutes());
}
pad 方法是一个通用的补零函数,将个位数前面补零,保证两位显示。例如 pad(3) 返回 "03",pad(15) 返回 "15"。
formatDate 将 Date 对象格式化为 yyyy-MM-dd 字符串,注意 getMonth() 返回 0~11,所以需要 +1。formatTime 将 Date 对象格式化为 HH:mm 字符串。这两个方法在 onChange 回调和 resetAll 重置方法中都被调用。
3. 源码逐段解析
现在开始按源码顺序逐段解读 index92.ets,从导入声明到 build 方法,完整展示选择器演示的实现细节。
3.1 导入声明与组件声明
import { router } from '@kit.ArkUI';
import { promptAction } from '@kit.ArkUI';
@Entry
@Component
struct Index92 {
源码开头导入了 router(页面导航)和 promptAction(轻提示),@Entry 和 @Component 标记组件为页面入口。
3.2 状态变量定义
@State cityIdx: number = 0;
@State cityText: string = '北京';
@State date: Date = new Date();
@State time: Date = new Date();
@State dateText: string = '';
@State timeText: string = '';
组件声明了六个 @State 状态变量:
cityIdx:城市选择器的选中索引,初始为 0(第一个城市「北京」)。cityText:城市选择器的显示文本,初始为「北京」。date:日期选择器的Date对象,初始为当前日期new Date()。time:时间选择器的Date对象,初始为当前时间new Date()。dateText:格式化后的日期文本,初始为空(在aboutToAppear中设置或由onChange更新)。timeText:格式化后的时间文本,初始为空(同上)。
注意 date 和 time 都初始化为 new Date(),即当前时刻。虽然它们指向同一时刻,但用途不同——date 只关心年月日部分,time 只关心时分部分。
3.3 城市选项数据
private cities: SelectOption[] = [
{ value: '北京' },
{ value: '上海' },
{ value: '广州' },
{ value: '深圳' }
];
城市列表定义了四个一线城市选项,每个选项是一个 { value: string } 对象。使用 SelectOption[] 类型确保与 Select 组件的数据源类型匹配。
3.4 格式化辅助方法
private pad(n: number): string {
return n < 10 ? '0' + n : n.toString();
}
private formatDate(d: Date): string {
return d.getFullYear() + '-' + this.pad(d.getMonth() + 1) + '-' + this.pad(d.getDate());
}
private formatTime(t: Date): string {
return this.pad(t.getHours()) + ':' + this.pad(t.getMinutes());
}
这三个方法已在第 2 节详细介绍过。值得注意的是 formatDate 中 getMonth() + 1 的处理——JavaScript 的 Date.getMonth() 返回 0-based 的月份(0=一月),所以需要加 1 才是人类可读的月份。这是日期处理中最常见的「坑」之一。
3.5 resetAll 重置方法
private resetAll(): void {
this.cityIdx = 0;
this.cityText = '北京';
this.date = new Date();
this.time = new Date();
this.dateText = this.formatDate(this.date);
this.timeText = this.formatTime(this.time);
promptAction.showToast({ message: '已恢复默认' });
}
resetAll 方法将所有选择器恢复到初始状态:
- 城市索引重置为 0,文本重置为「北京」。
- 日期和时间重置为当前时刻
new Date()。 - 日期文本和时间文本通过格式化方法重新计算。
- 弹出「已恢复默认」的 Toast 提示。
这里需要特别注意:重置时不仅要修改 @State 变量,还要确保格式化文本也同步更新。如果只重置 date 和 time 而不更新 dateText 和 timeText,汇总卡片中的文本不会刷新。
3.6 build 方法整体结构
build 方法构建了整个页面的 UI 结构,最外层是一个 Column,包含顶部返回栏、城市选择卡片、日期选择卡片、时间选择卡片、汇总结果卡片和重置按钮:
build() {
Column() {
// 顶部返回栏
Row() { ... }
// 城市选择
Column() { ... }
// 日期选择
Column() { ... }
// 时间选择
Column() { ... }
// 汇总显示
Column() { ... }
// 重置按钮
Button('重置') { ... }
}
.width('100%')
.height('100%')
.backgroundColor('#f2f3f5')
}
最外层 Column 全屏宽高,浅灰色背景。
3.7 城市选择卡片
Column() {
Row() {
Text('选择城市')
.fontSize(15)
.fontColor('#333333')
Blank()
Text('当前:' + this.cityText)
.fontSize(14)
.fontColor('#1a6cff')
}
.width('100%')
.margin({ bottom: 10 })
Select(this.cities)
.selected(this.cityIdx)
.value(this.cityText)
.font({ size: 16, weight: FontWeight.Medium })
.width('100%')
.onSelect((index: number, value: string) => {
this.cityIdx = index;
this.cityText = value;
})
}
.width('90%')
.padding(16)
.backgroundColor('#ffffff')
.borderRadius(12)
.margin({ top: 16 })
城市选择卡片包含两部分:
- 标签行:左侧「选择城市」标签,右侧「当前:北京」蓝色文字,通过
Blank()弹性分隔。 - Select 组件:数据源为
this.cities,绑定selected和value属性,onSelect回调更新索引和文本。
卡片整体宽度 90%,白色背景,圆角 12,带内边距,顶部外边距 16,与返回栏保持间距。
3.8 日期选择卡片
Column() {
Text('选择日期')
.fontSize(15)
.fontColor('#333333')
.width('100%')
.margin({ bottom: 6 })
DatePicker({ selected: this.date })
.lunar(false)
.onChange((value: DatePickerResult) => {
this.date = new Date(value.year!, value.month! - 1, value.day!);
this.dateText = value.year! + '-' + this.pad(value.month!) + '-' + this.pad(value.day!);
})
}
.width('90%')
.padding(16)
.backgroundColor('#ffffff')
.borderRadius(12)
.margin({ top: 12 })
日期选择卡片包含标签和 DatePicker 组件。DatePicker 初始选中 this.date(当前日期),关闭农历模式(lunar(false)),onChange 回调中更新 date 对象和 dateText 文本。
注意 onChange 回调中的日期构造:new Date(value.year!, value.month! - 1, value.day!)。DatePickerResult 的 month 是 1-based,而 Date 构造函数的 month 参数是 0-based,所以需要减 1。非空断言操作符 ! 用于告诉编译器这些值不为 undefined(DatePickerResult 的字段是可选的)。
3.9 时间选择卡片
Column() {
Text('选择时间')
.fontSize(15)
.fontColor('#333333')
.width('100%')
.margin({ bottom: 6 })
TimePicker({ selected: this.time })
.useMilitaryTime(true)
.onChange((value: TimePickerResult) => {
this.timeText = this.pad(value.hour) + ':' + this.pad(value.minute);
})
}
.width('90%')
.padding(16)
.backgroundColor('#ffffff')
.borderRadius(12)
.margin({ top: 12 })
时间选择卡片包含标签和 TimePicker 组件。TimePicker 初始选中 this.time(当前时间),启用 24 小时制(useMilitaryTime(true)),onChange 回调中更新 timeText 文本。
注意 TimePickerResult 的 hour 和 minute 字段是必填的(非可选),所以不需要非空断言操作符。
3.10 汇总结果卡片
Column() {
Text('选择结果')
.fontSize(15)
.fontWeight(FontWeight.Bold)
.fontColor('#333333')
.margin({ bottom: 10 })
Text('城市:' + this.cityText)
.fontSize(16)
.fontColor('#1a6cff')
.margin({ bottom: 6 })
Text('日期:' + this.dateText)
.fontSize(16)
.fontColor('#1a6cff')
.margin({ bottom: 6 })
Text('时间:' + this.timeText)
.fontSize(16)
.fontColor('#1a6cff')
}
.width('90%')
.padding(16)
.backgroundColor('#ffffff')
.borderRadius(12)
.margin({ top: 12 })
汇总结果卡片以蓝色文字展示三种选择器的当前值。由于 cityText、dateText、timeText 都是 @State 变量,用户每次操作选择器时,这些文本会自动更新,汇总卡片也会同步刷新。
3.11 重置按钮
Button('重置')
.width(200)
.height(46)
.backgroundColor('#1a6cff')
.fontColor(Color.White)
.margin({ top: 20 })
.onClick(() => {
this.resetAll();
})
重置按钮宽度 200vp、高度 46vp,蓝色主题,点击后调用 resetAll() 方法恢复所有选择器到默认状态。
4. 交互流程详解
4.1 城市选择流程
用户点击 Select 组件,弹出下拉列表,选择某个城市:
Select组件展开下拉列表,高亮当前选中项(selected(this.cityIdx)控制)。- 用户点击某个选项,触发
onSelect回调,参数为(index, value)。 - 回调中更新
this.cityIdx = index和this.cityText = value。 @State变量变化触发 UI 刷新:Select的输入框显示新选中的城市名,标签行的「当前」文本更新,汇总卡片的「城市」行更新。
4.2 日期选择流程
用户滑动 DatePicker 的滚轮,选择年/月/日:
- 用户滚动滚轮,
DatePicker实时触发onChange回调。 - 回调中更新
this.date为新的Date对象,并格式化this.dateText。 @State变量变化触发汇总卡片的「日期」行更新。
4.3 时间选择流程
用户滑动 TimePicker 的滚轮,选择时/分:
- 用户滚动滚轮,
TimePicker实时触发onChange回调。 - 回调中格式化
this.timeText。 @State变量变化触发汇总卡片的「时间」行更新。
4.4 重置流程
用户点击「重置」按钮:
resetAll()方法被调用。- 所有
@State变量恢复为初始值。 - 城市选择器回到第一个选项,日期和时间选择器回到当前时刻。
- 汇总卡片三行文本同步更新。
- 弹出「已恢复默认」Toast 提示。
5. UI 样式设计思路
5.1 卡片式布局
每个选择器都包裹在独立的白色圆角卡片中,卡片宽度 90%,圆角 12vp,内边距 16vp,卡片间间距 12vp。这种卡片式布局让页面层次分明,用户可以清晰地识别每个选择器的功能区域。
5.2 主题色统一
整个页面使用统一的主题色 #1a6cff:
- 返回按钮背景色
Select选中文字颜色- 汇总卡片中的值文本颜色
- 重置按钮背景色
统一的主题色让页面视觉风格一致,提升了整体的专业感。
5.3 标签与值分离
城市选择卡片采用了「标签在左,当前值在右」的布局:
选择城市 当前:北京
[ Select 下拉组件 ]
这种布局让用户在操作前就能看到当前值,操作后也能立即对比新旧值,提升了交互的可感知性。
6. 运行与测试
6.1 运行步骤
- 使用 DevEco Studio 打开项目。
- 运行项目到模拟器或真机。
- 在首页找到「选择器演示」示例入口,点击进入。
- 点击城市下拉列表,选择不同城市,观察汇总卡片更新。
- 滑动日期滚轮,选择不同日期,观察汇总卡片更新。
- 滑动时间滚轮,选择不同时间,观察汇总卡片更新。
- 点击「重置」按钮,确认所有选择器恢复默认值。
6.2 测试场景
| 测试场景 | 预期结果 |
|---|---|
| 选择城市「上海」 | 下拉列表高亮上海,输入框显示上海,汇总更新 |
| 滑动日期到 2025-01-15 | 日期文本更新为 2025-01-15,汇总更新 |
| 滑动时间到 14:30 | 时间文本更新为 14:30,汇总更新 |
| 点击「重置」按钮 | 所有选择器恢复默认,弹出「已恢复默认」提示 |
| 快速连续滑动日期滚轮 | 日期文本实时更新,无卡顿 |
7. 可扩展方向
7.1 级联选择器
将城市选择扩展为省/市级联选择器——选择省份后,城市列表自动更新为该省份下的城市。需要在 onSelect 回调中动态修改 Select 的数据源。
7.2 农历日期
将 DatePicker 的 lunar 属性设为 true,即可显示农历日期。可以增加一个开关按钮,让用户在公历和农历之间切换。
7.3 12 小时制切换
将 TimePicker 的 useMilitaryTime 设为 false,即可切换到 12 小时制(AM/PM)。可以增加一个 Toggle 开关,让用户自由切换。
7.4 表单提交验证
增加「提交」按钮,在提交前验证所有选择器是否已选择(日期和时间默认有值,城市需要确认不是默认占位符)。验证通过后跳转到确认页面或弹出确认对话框。
7.5 日期范围限制
通过 DatePicker 的 start 和 end 属性限制可选日期范围,例如只能选择未来 7 天内的日期,适用于预约场景。
8. 常见问题与调试
8.1 Select 选中项不高亮
问题:Select 的下拉列表中没有高亮任何项。
排查:
- 确认
selected(this.cityIdx)是否正确设置。selected需要传入当前选中项的索引。 - 检查
cityIdx的值是否在数据源的有效索引范围内(0~3)。
8.2 DatePicker 月份错误
问题:选择 3 月,但 Date 对象的月份变成了 2 月。
排查:
- 确认
onChange回调中构造Date时是否做了month - 1处理。DatePickerResult.month是 1-based,Date构造函数的 month 是 0-based。 - 检查
formatDate方法中getMonth() + 1是否正确——显示时要加回 1。
8.3 重置后文本不更新
问题:点击「重置」按钮后,Select 和 DatePicker 视觉上恢复了,但汇总卡片的文本没有更新。
排查:
- 确认
resetAll()方法中是否同时更新了dateText和timeText。只修改date和time对象不会自动更新格式化文本。 - 在
resetAll()中添加日志,确认格式化方法的返回值是否正确。
8.4 TimePicker 显示 12 小时制
问题:TimePicker 显示了 AM/PM,而不是预期的 24 小时制。
排查:
- 确认
useMilitaryTime(true)是否设置。默认可能不是 24 小时制。 - 检查是否有其他代码覆盖了
useMilitaryTime的值。
9. 技术总结
示例 92 的选择器演示将三种原生选择器组件有机组合,展示了 ArkUI 选择器体系的完整用法。通过这个示例,我们可以总结出选择器组件的使用范式:
- Select 三件套:
selected控制高亮、value控制显示文字、onSelect处理选择事件,三者缺一不可。 - DatePicker 月份转换:
DatePickerResult.month是 1-based,Date的 month 是 0-based,两者之间需要加减 1 转换。 - TimePicker 24 小时制:
useMilitaryTime(true)开启 24 小时制,适用于需要精确时间的场景。 - 格式化辅助方法:
pad补零 +formatDate/formatTime是日期时间格式化的标准模式,复用性高。 - 状态联动:每个选择器的
onChange回调只需更新对应的@State变量,ArkUI 的响应式机制会自动处理 UI 刷新。
掌握了这些范式后,就可以轻松地将选择器组件应用到预约表单、日程管理、订单筛选等实际业务场景中。选择器作为表单输入的核心组件,是每个鸿蒙开发者必须熟练掌握的基础技能。
更多推荐



所有评论(0)