鸿蒙新特性——TimePicker 与 TextClock 组件深度解析
一、引言
时间是最基础的数据类型之一。在移动应用中,无论是设置闹钟、选择提醒时间、记录事件时刻,还是展示当前时间,都离不开时间相关的交互与展示。HarmonyOS NEXT 的 ArkUI 框架为此提供了两个专门的组件:TimePicker(时间选择器)和TextClock(文本时钟)。
TimePicker 负责时间的"选择"——用户通过滚轮式的交互界面选择小时和分钟,是一套完整的拾取器组件。TextClock 则负责时间的"展示"——它自动同步系统时钟,以指定的格式实时显示当前时间,无需开发者手动编写定时器或更新逻辑。
两者虽然都围绕"时间"这一主题,但在设计哲学上截然不同。TimePicker 是输入型组件,关注用户交互和数据采集;TextClock 是展示型组件,关注数据呈现和自动更新。在实际开发中,它们常常协同工作——比如闹钟应用顶部用 TextClock 展示当前时间,下方用 TimePicker 让用户设定提醒时刻。
本文将通过一个完整的**“闹钟提醒”**实战案例,深入解析 TimePicker 和 TextClock 的核心 API、格式系统、交互模式和组合使用技巧。阅读完本文,你将能够:
- 熟练掌握 TimePicker 的时间选择与数据获取
- 理解 TextClock 的格式字符串体系与自动更新机制
- 学会在同一个页面中组合使用 TimePicker 和 TextClock
- 掌握提醒列表的数据管理和状态切换
- 运用快捷预设提升用户操作效率
二、TimePicker 时间选择器深入解析
2.1 组件定位与基本概念
TimePicker 是 ArkUI 中专门用于选择时间的拾取器组件。与 DatePicker(日期选择器)不同,TimePicker 仅处理小时和分钟,不涉及年月日。它的视觉呈现是一个垂直滚轮式界面,用户可以上下滑动来切换小时和分钟的值。
TimePicker 的设计源于一个简单但深刻的 UX 洞察:让用户输入时间的最自然方式是"选择"而非"键入"。在传统表单中,时间输入往往依赖两个 TextInput 框(分别输入时和分),用户需要切换焦点、处理格式校验,体验非常碎片化。TimePicker 将这一过程统一为一个连续的、可视的、即时的操作,用户滑动滚轮的同时就能看到结果,不需要任何确认步骤。
2.2 构造函数与核心参数
TimePicker 的构造函数接受一个可选对象:
TimePicker(options?:{selected?:Date})其中selected参数接受一个Date对象,用于设置初始选中的时间。注意,TimePicker 只关注这个 Date 对象中的小时和分钟部分,年月日会被忽略:
TimePicker({selected:newDate(2026,0,1,8,0)// 初始选中 08:00})在我们的闹钟应用中,我们将selectedHour和selectedMinute作为@State变量维护,并在每次需要构建 TimePicker 时创建一个新的 Date 对象:
@StateselectedHour:number=8;@StateselectedMinute:number=0;TimePicker({selected:newDate(2026,0,1,this.selectedHour,this.selectedMinute)})这里有一个需要特别注意的设计决策:TimePicker 的selected参数只在组件初始化时生效——后续修改this.selectedHour或this.selectedMinute不会自动更新 TimePicker 的滚轮位置。这意味着如果你希望通过预设按钮来改变 TimePicker 的显示值,单靠修改状态变量是不够的。
在我们的实现中,预设按钮点击后会同时更新状态变量并直接添加提醒——不依赖 TimePicker 的视觉同步。这在实践中是一个更健壮的模式:你的数据流是单向的(用户操作 → 更新状态 → 添加提醒),不会因为 UI 组件的内部状态而产生不一致。
2.3 onChange 回调与数据获取
当用户滑动 TimePicker 的滚轮时,onChange回调会在选择值变化时触发:
TimePicker({selected:newDate(2026,0,1,this.selectedHour,this.selectedMinute)}).onChange((value:TimePickerResult)=>{this.onTimeChanged(value);})TimePickerResult包含了两个关键属性:
onTimeChanged(result:TimePickerResult):void{if(result.hour!==undefined){this.selectedHour=result.hour;}if(result.minute!==undefined){this.selectedMinute=result.minute;}}result.hour:当前选中的小时数(0-23,24 小时制)result.minute:当前选中的分钟数(0-59)
这里我们采用了防御性编程——检查hour和minute是否为 undefined 后才赋值。这是因为TimePickerResult的属性定义中,这两个字段是可选的(在某些 API 版本中)。防御性检查可以避免潜在的类型安全问题。
2.4 样式与尺寸控制
TimePicker 支持通过链式方法控制外观:
TimePicker({selected:date}).height(200)// 控制整体高度.backgroundColor('#FFFFFF')// 背景色height是 TimePicker 最重要的样式属性——它决定了滚轮的可视区域大小。过小会导致滚轮难以操作,过大则占用过多屏幕空间。对于闹钟选择场景,200vp 是一个比较合适的折中值,既能清晰显示两组数字(小时和分钟),又不会过度挤压下方的内容区域。
三、TextClock 文本时钟深入解析
3.1 组件定位与设计哲学
TextClock 是一个特殊的文本显示组件:它自动获取系统当前时间,以指定的格式实时展示,并自动更新显示。与普通的Text组件不同,你不需要手动编写setInterval或setTimeout来刷新时间——TextClock 内部已经处理了这一切。
这种"声明式时钟"设计体现了 ArkUI 的核心哲学:让框架处理底层复杂性,开发者只需描述"显示什么"。使用 TextClock,你只需要告诉它格式,它就会忠实地、持续地显示最新时间。
3.2 构造函数与选项
从 API 定义来看,TextClock 的构造函数同样接受一个可选对象:
TextClock(options?:TextClockOptions)TextClockOptions的属性包括:
timeZoneOffset?: number:时区偏移量,取值范围 -14 到 12。负数表示东时区(如中国为 -8,即 UTC+8)。不设置时默认使用系统时区。controller?: TextClockController:控制器实例,用于程序化地停止和启���时钟更新。
在我们的闹钟应用中,使用系统默认时区即可,因此不传入任何选项:
TextClock().format('HH:mm:ss')3.3 format 格式字符串体系
format()是 TextClock 最核心的方法——它决定了时间的显示方式。TextClock 使用一套类似于传统日期格式化的模式字符串系统:
| 模式 | 含义 | 示例 |
|---|---|---|
yyyy | 四位年份 | 2026 |
MM | 两位月份 | 01-12 |
MMM | 英文月份缩写 | Jan-Dec |
MMMM | 英文月份全称 | January-December |
dd | 两位日期 | 01-31 |
ddd | 英文星期缩写 | Mon-Sun |
dddd | 英文星期全称 | Monday-Sunday |
HH | 24 小时制小时 | 00-23 |
hh | 12 小时制小时 | 01-12 |
mm | 分钟 | 00-59 |
ss | 秒 | 00-59 |
默认格式为hh:mm:ss(12 小时制带秒)。在我们的应用中,使用了两种格式:
// 大号时间显示 — 24 小时制带秒TextClock().format('HH:mm:ss').fontSize(52).fontWeight(FontWeight.Bold).fontColor('#FFFFFF').fontFamily('monospace')// 日期显示 — 中文格式TextClock().format('yyyy年MM月dd日 dddd').fontSize(FontSize.BODY).fontColor('#FFFFFF99')第一行格式HH:mm:ss产生类似14:35:02的输出,24 小时制的时间清晰直观。第二行yyyy年MM月dd日 dddd产生类似2026年06月09日 Monday的输出,中文日期格式加上英文星期全称,兼顾了本地化和语义清晰度。
重要提醒:format是一个链式方法(不是构造参数),这一点在初次使用时容易被忽略。如果误写成TextClock({ format: 'HH:mm' })会导致编译错误,因为TextClockOptions中不存在format字段。
3.4 onDateChange 回调与高级用法
除了基本的自动显示,TextClock 还提供了onDateChange回调,在时间变化时触发:
TextClock().format('HH:mm:ss').onDateChange((value:number)=>{// value 是 Unix 时间戳(毫秒)console.log('时间更新: '+value);})这个回调的最小触发间隔为秒级(非表单场景)或分钟级(表单场景)。你可以利用这个回调来做一些"时间驱动"的逻辑——比如在零点时刷新数据,或在特定时刻触发提醒。不过在我们的闹钟应用中,TextClock 只作为展示组件使用,实际的提醒逻辑由用户操作驱动,无需监听时钟回调。
3.5 字体与样式定制
TextClock 继承了TextClockAttribute类中的所有样式方法,包括:
TextClock().fontSize(52)// 字号:52fp.fontWeight(FontWeight.Bold)// 字重:加粗.fontColor('#FFFFFF')// 颜色:白色.fontFamily('monospace')// 字体:等宽.fontStyle(FontStyle.Normal)// 风格:正常使用等宽字体(monospace)是一个经典的时钟设计选择——在等宽字体下,每个数字占据相同的宽度,当秒数从 09 跳变到 10 时,文字宽度不会发生变化,避免了因宽度变化带来的视觉抖动。这种微小的细节在长时间的注视下会产生明显的差异。
四、实战:闹钟提醒应用开发
4.1 页面整体架构
我们的闹钟提醒页面包含以下几个核心模块:
- 当前时间展示区:两个 TextClock,分别显示时间和日期
- 已启用提醒计数:显示当前活跃的提醒数量
- TimePicker 选择器:滚轮式时间选择
- 快捷预设按钮:四个预设时间(起床/午休/下班/睡觉)
- 自定义添加区:TextInput 输入名称 + 添加按钮
- 提醒列表:展示所有提醒,支持启用/关闭/删除
交互流程为:用户通过 TimePicker 或快捷预设选择时间 → 添加提醒 → 提醒出现在列表中 → 可随时切换启用状态或删除。
4.2 数据模型设计
interfaceAlarmItem{id:number;label:string;hour:number;minute:number;enabled:boolean;}interfacePresetItem{label:string;hour:number;minute:number;icon:string;}constPRESETS:PresetItem[]=[{label:'起床',hour:7,minute:0,icon:'🌅'},{label:'午休',hour:12,minute:30,icon:'☀️'},{label:'下班',hour:18,minute:0,icon:'🌆'},{label:'睡觉',hour:22,minute:30,icon:'🌙'},];AlarmItem是单个提醒的数据模型。除了基本的时间(hour/minute)和标签(label)之外,还包含一个enabled字段用于控制启用/关闭状态。这个设计让用户可以在不删除提醒的情况下暂停某个提醒——这在真实场景中非常实用(比如周末不想被起床闹铃叫醒)。
PRESETS数组定义了四个快捷预设,包含了标签、时间和 emoji 图标。预设的设计逻辑是覆盖一天中的关键时间节点——起床、午休、下班、睡觉——选择了这些时刻最具代表性的时间值。emoji 图标让预设按钮更加直观和有趣。
页面的核心状态变量:
@Statealarms:AlarmItem[]=[];// 提醒列表@StateselectedHour:number=8;// TimePicker 当前选中的小时@StateselectedMinute:number=0;// TimePicker 当前选中的分钟@StatelabelInput:string='';// 提醒名称输入@StateshowToast:boolean=false;// Toast 提示显示4.3 时间格式化工具
由于提醒列表中的时间需要以HH:mm格式展示,我们实现了一个简单的格式化函数:
formatTime(h:number,m:number):string{consthh:string=h<10?'0'+h.toString():h.toString();constmm:string=m<10?'0'+m.toString():m.toString();return`${hh}:${mm}`;}该函数将小时和分钟补零到两位数字,确保8:5输出为08:05。之所以不直接使用 Date 对象来格式化,是因为单独维护 hour 和 minute 数值更加直观,避免了 Date 对象在年月日上的额外信息负担。
4.4 添加提醒逻辑
addAlarm(label:string,hour:number,minute:number):boolean{// 去重检查for(leti=0;i<this.alarms.length;i++){if(this.alarms[i].hour===hour&&this.alarms[i].minute===minute){this.showToastMessage('该时间已有提醒,请选择其他时间');returnfalse;}}constalarm:AlarmItem={id:this.nextId++,label:label,hour:hour,minute:minute,enabled:true};constnewAlarms:AlarmItem[]=[alarm];for(leti=0;i<this.alarms.length;i++){newAlarms.push(this.alarms[i]);}if(newAlarms.length>15){newAlarms.splice(15);}this.alarms=newAlarms;this.labelInput='';this.showToastMessage('✅ 提醒添加成功');returntrue;}这个方法的几个设计要点:
去重检查:通过遍历现有提醒,检测是否存在相同时间(hour 和 minute 都一致)的提醒。如果存在,拒绝添加并提示用户。这个检查非常重要——在同一个时刻设置两个提醒是没有意义的。
新提醒前置:通过构造
[alarm, ...新数组]的方式,将新添加的提醒置顶显示。这与大多数闹钟应用的行为一致——最新添加的提醒应该最容易看到。数量上限:最多保留 15 个提醒,超出部分自动移除。虽然在实际使用中很少有人会添加超过 15 个闹钟,但设置上限是一种防御性编程实践。
不可变更新:创建全新的数组并赋值给
this.alarms,而非直接修改原数组。这是 ArkTS 中@State变量更新的正确方式——只有整体替换才能触发 UI 刷新。
4.5 快捷预设的实现
addPresetAlarm(preset:PresetItem):void{this.selectedHour=preset.hour;this.selectedMinute=preset.minute;this.addAlarm(preset.label,preset.hour,preset.minute);}点击预设按钮后,先更新 TimePicker 绑定的状态变量(虽然可能不会立即反映到滚轮位置上,但保持了状态的一致性),再直接添加提醒。预设按钮的视觉设计采用了药丸形状 + 浅蓝底色 + emoji 图标的方案:
Button(`${preset.icon}${preset.label}${this.formatTime(preset.hour,preset.minute)}`).fontSize(FontSize.CAPTION).fontColor(AppColors.PRIMARY).backgroundColor(AppColors.PRIMARY_LIGHT).borderRadius(BorderRadius.FULL).height(30)每个按钮上同时显示图标、标签和时间,信息密度高但阅读负担小。例如 “🌅 起床 07:00” ——用户一眼就能看到预设的含义和具体时间。
4.6 提醒状态切换
toggleAlarm(id:number):void{constupdated:AlarmItem[]=[];for(leti=0;i<this.alarms.length;i++){if(this.alarms[i].id===id){updated.push({id:this.alarms[i].id,label:this.alarms[i].label,hour:this.alarms[i].hour,minute:this.alarms[i].minute,enabled:!this.alarms[i].enabled});}else{updated.push(this.alarms[i]);}}this.alarms=updated;}切换启用状态时,同样遵循不可变更新模式——创建新的数组和修改过的对象。这里使用对象字面量逐个拷贝字段,而不是展开运算符(因为 ArkTS 对展开运算符的支持有限制)。每个提醒卡片上的启用/关闭按钮在视觉上有明显的区分:启用状态显示黄色"关闭"按钮(带浅黄背景),关闭状态显示绿色"开启"按钮(带浅绿背景)。
4.7 Toast 提示机制
showToastMessage(msg:string):void{this.toastText=msg;this.showToast=true;setTimeout(()=>{this.showToast=false;},1800);}Toast 提示使用setTimeout在 1.8 秒后自动隐藏。Tip 本身是一个绝对定位的深色圆角条,显示在屏幕下部 85% 的位置。这种轻量级的反馈机制比弹窗更优雅——不打断用户操作,只提供瞬时的信息确认。
需要注意的是,如果在aboutToDisappear中需要清理定时器,应当存储setTimeout的返回值并在组件销毁时clearTimeout。不过由于 Toast 的自动消失时间极短(1.8 秒),且页面退出时不需要重新渲染,这里省略了清理逻辑。
五、TimePicker 与 TextClock 的组合模式
5.1 信息流向分析
在闹钟页面中,TimePicker 和 TextClock 之间的信息流向如下:
系统时钟 ↓ TextClock(展示当前时间) ↓(用户看到当前时间,决定设置何时提醒) TimePicker(选择提醒时间) ↓ onChange @State selectedHour / selectedMinute ↓ addAlarm() 提醒列表(AlarmItem[])TextClock 作为"参考时间",帮助用户建立时间认知;TimePicker 作为"输入工具",让用户选择目标时间;两者通过@State变量和提醒列表间接关联。
5.2 TimePicker 与 TextInput 的组合
除了 TimePicker,页面还包含一个 TextInput 用于自定义提醒名称。这是一个典型的"选择器 + 文本输入"组合模式:
Row(){TextInput({placeholder:'提醒名称(选填,如"开会")',text:this.labelInput}).onChange((value:string)=>{this.labelInput=value;}).maxLength(20).layoutWeight(1)Button('添加提醒').onClick(()=>{this.addFromPicker();})}这种设计让用户可以选择是否自定义提醒名称——不填则使用默认名称"提醒",填写则使用自定义名称。TextInput 的限制长度(20 字符)确保名称不会过长,在列表显示中保持美观。
5.3 常见的 TimePicker 使用场景
除了闹钟提醒,TimePicker 还广泛适用于以下场景:
- 日程安排:结合 DatePicker 设定事件的具体开始时间
- 计时器:选择倒计时或正计时的目标时长
- 营业时间设置:商户设置每日营业的开始和结束时间
- 免打扰模式:设置静默时段的起止时间
- 定时任务:如定时发送消息、定时备份数据
在每个场景中,TimePicker 都承担同样的角色——让用户以直观的方式选择时间点。而 TextClock 则作为当前时间的参考锚点,帮助用户做出准确的时间决策。
六、完整代码结构
页面的组件树结构如下:
Stack(根节点,用于 Toast 叠加) └── Column(主内容区) ├── Row(顶部标题栏:"⏰ 闹钟提醒") ├── Column(时间展示区,深色背景) │ ├── TextClock(大号 HH:mm:ss) │ ├── TextClock(日期 yyyy年MM月dd日 dddd) │ └── Row(已启用提醒计数) ├── Row(TimePicker 时间选择器) ├── Row(快捷预设按钮) ├── Row(自定义名称输入 + 添加按钮) └── 提醒列表 / 空状态 ├── [有提醒] List > ForEach > 提醒卡片 └── [无提醒] 空状态提示代码约 280 行,所有组件和逻辑在一个文件中。使用了 TimePicker、TextClock、TextInput、Button、List、ForEach 等 ArkUI 组件。
七、总结
本文以闹钟提醒为业务场景,深入解析了 ArkUI 的两个时间相关组件:TimePicker(时间选择器)和 TextClock(文本时钟)。
回顾本文覆盖的核心要点:
TimePicker 核心机制:通过
selected参数设置初始时间,通过onChange回调获取用户选择的小时和分钟。TimePicker 是一个滚轮式拾取器,高度可由height()控制。TextClock 自动更新:无需手动编写定时器,TextClock 自动与系统时钟同步。通过
format()链式方法设置显示格式,支持 yyyy、MM、dd、HH、hh、mm、ss、ddd、dddd 等多种模式。format 链式方法:特别注意
format是 TextClock 的方法而非构造参数。错误地写在构造函数中会导致编译失败。组合使用模式:TextClock 展示"现在",TimePicker 选择"将来",两者通过 @State 变量和提醒列表间接关联。这是时间相关 UI 的经典信息架构。
快捷预设设计:通过预设数组(起床/午休/下班/睡觉)减少用户操作步骤,每个按钮同时展示图标、标签和时间。
状态切换:提醒的启用/关闭状态通过创建新对象和不可变数组更新实现,确保 @State 机制正常触发 UI 刷新。
TimePicker 和 TextClock 虽然各司其职,但在实际应用中几乎是形影不离的组合。掌握好这两个组件,你就能够自如地处理各种时间相关的交互需求——从简单的当前时间展示,到复杂的闹钟提醒管理,再到日程安排和定时任务。
