一、引言

时间是最基础的数据类型之一。在移动应用中,无论是设置闹钟、选择提醒时间、记录事件时刻,还是展示当前时间,都离不开时间相关的交互与展示。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: new Date(2026, 0, 1, 8, 0)  // 初始选中 08:00
})

在我们的闹钟应用中,我们将 selectedHourselectedMinute 作为 @State 变量维护,并在每次需要构建 TimePicker 时创建一个新的 Date 对象:

@State selectedHour: number = 8;
@State selectedMinute: number = 0;

TimePicker({
  selected: new Date(2026, 0, 1, this.selectedHour, this.selectedMinute)
})

这里有一个需要特别注意的设计决策:TimePicker 的 selected 参数只在组件初始化时生效——后续修改 this.selectedHourthis.selectedMinute 不会自动更新 TimePicker 的滚轮位置。这意味着如果你希望通过预设按钮来改变 TimePicker 的显示值,单靠修改状态变量是不够的。

在我们的实现中,预设按钮点击后会同时更新状态变量并直接添加提醒——不依赖 TimePicker 的视觉同步。这在实践中是一个更健壮的模式:你的数据流是单向的(用户操作 → 更新状态 → 添加提醒),不会因为 UI 组件的内部状态而产生不一致。

2.3 onChange 回调与数据获取

当用户滑动 TimePicker 的滚轮时,onChange 回调会在选择值变化时触发:

TimePicker({ selected: new Date(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)

这里我们采用了防御性编程——检查 hourminute 是否为 undefined 后才赋值。这是因为 TimePickerResult 的属性定义中,这两个字段是可选的(在某些 API 版本中)。防御性检查可以避免潜在的类型安全问题。

2.4 样式与尺寸控制

TimePicker 支持通过链式方法控制外观:

TimePicker({ selected: date })
  .height(200)           // 控制整体高度
  .backgroundColor('#FFFFFF')  // 背景色

height 是 TimePicker 最重要的样式属性——它决定了滚轮的可视区域大小。过小会导致滚轮难以操作,过大则占用过多屏幕空间。对于闹钟选择场景,200vp 是一个比较合适的折中值,既能清晰显示两组数字(小时和分钟),又不会过度挤压下方的内容区域。
在这里插入图片描述

三、TextClock 文本时钟深入解析

3.1 组件定位与设计哲学

TextClock 是一个特殊的文本显示组件:它自动获取系统当前时间,以指定的格式实时展示,并自动更新显示。与普通的 Text 组件不同,你不需要手动编写 setIntervalsetTimeout 来刷新时间——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 页面整体架构

我们的闹钟提醒页面包含以下几个核心模块:

  1. 当前时间展示区:两个 TextClock,分别显示时间和日期
  2. 已启用提醒计数:显示当前活跃的提醒数量
  3. TimePicker 选择器:滚轮式时间选择
  4. 快捷预设按钮:四个预设时间(起床/午休/下班/睡觉)
  5. 自定义添加区:TextInput 输入名称 + 添加按钮
  6. 提醒列表:展示所有提醒,支持启用/关闭/删除

交互流程为:用户通过 TimePicker 或快捷预设选择时间 → 添加提醒 → 提醒出现在列表中 → 可随时切换启用状态或删除。

4.2 数据模型设计

interface AlarmItem {
  id: number;
  label: string;
  hour: number;
  minute: number;
  enabled: boolean;
}

interface PresetItem {
  label: string;
  hour: number;
  minute: number;
  icon: string;
}

const PRESETS: 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 图标让预设按钮更加直观和有趣。

页面的核心状态变量:

@State alarms: AlarmItem[] = [];        // 提醒列表
@State selectedHour: number = 8;        // TimePicker 当前选中的小时
@State selectedMinute: number = 0;      // TimePicker 当前选中的分钟
@State labelInput: string = '';         // 提醒名称输入
@State showToast: boolean = false;      // Toast 提示显示

4.3 时间格式化工具

由于提醒列表中的时间需要以 HH:mm 格式展示,我们实现了一个简单的格式化函数:

formatTime(h: number, m: number): string {
  const hh: string = h < 10 ? '0' + h.toString() : h.toString();
  const mm: 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 (let i = 0; i < this.alarms.length; i++) {
    if (this.alarms[i].hour === hour && this.alarms[i].minute === minute) {
      this.showToastMessage('该时间已有提醒,请选择其他时间');
      return false;
    }
  }

  const alarm: AlarmItem = {
    id: this.nextId++,
    label: label,
    hour: hour,
    minute: minute,
    enabled: true
  };

  const newAlarms: AlarmItem[] = [alarm];
  for (let i = 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('✅ 提醒添加成功');
  return true;
}

这个方法的几个设计要点:

  1. 去重检查:通过遍历现有提醒,检测是否存在相同时间(hour 和 minute 都一致)的提醒。如果存在,拒绝添加并提示用户。这个检查非常重要——在同一个时刻设置两个提醒是没有意义的。

  2. 新提醒前置:通过构造 [alarm, ...新数组] 的方式,将新添加的提醒置顶显示。这与大多数闹钟应用的行为一致——最新添加的提醒应该最容易看到。

  3. 数量上限:最多保留 15 个提醒,超出部分自动移除。虽然在实际使用中很少有人会添加超过 15 个闹钟,但设置上限是一种防御性编程实践。

  4. 不可变更新:创建全新的数组并赋值给 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 {
  const updated: AlarmItem[] = [];
  for (let i = 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(文本时钟)。

回顾本文覆盖的核心要点:

  1. TimePicker 核心机制:通过 selected 参数设置初始时间,通过 onChange 回调获取用户选择的小时和分钟。TimePicker 是一个滚轮式拾取器,高度可由 height() 控制。

  2. TextClock 自动更新:无需手动编写定时器,TextClock 自动与系统时钟同步。通过 format() 链式方法设置显示格式,支持 yyyy、MM、dd、HH、hh、mm、ss、ddd、dddd 等多种模式。

  3. format 链式方法:特别注意 format 是 TextClock 的方法而非构造参数。错误地写在构造函数中会导致编译失败。

  4. 组合使用模式:TextClock 展示"现在",TimePicker 选择"将来",两者通过 @State 变量和提醒列表间接关联。这是时间相关 UI 的经典信息架构。

  5. 快捷预设设计:通过预设数组(起床/午休/下班/睡觉)减少用户操作步骤,每个按钮同时展示图标、标签和时间。

  6. 状态切换:提醒的启用/关闭状态通过创建新对象和不可变数组更新实现,确保 @State 机制正常触发 UI 刷新。

TimePicker 和 TextClock 虽然各司其职,但在实际应用中几乎是形影不离的组合。掌握好这两个组件,你就能够自如地处理各种时间相关的交互需求——从简单的当前时间展示,到复杂的闹钟提醒管理,再到日程安排和定时任务。

Logo

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

更多推荐