- 一、概述
- 二、Clock 组件介绍
- 三、时间获取与格式化
- 四、正计时(秒表)
- 五、倒计时(定时器)
- 六、多个倒计时同时管理
- 七、闹钟提醒
- 八、实战案例一:番茄钟 App
- 九、实战案例二:考试倒计时 App
- 十、进阶技巧
- 十一、常见问题
- 十二、总结
- 参考资料
一、概述
在 App 开发中,时间相关功能无处不在:秒表计时、倒计时提醒、闹钟、番茄钟、考试倒计时…… App Inventor 2 提供了内置的 Clock(时钟)组件,它是所有时间相关功能的核心。
Clock 组件本质上是一个定时器(Timer),能够以固定的时间间隔反复触发事件,同时也提供了丰富的时间处理方法。掌握 Clock 组件,是开发实用工具类 App 的必备技能。
本文涵盖:
- Clock 组件属性与事件详解
- 时间获取与格式化
- 正计时(秒表)实现
- 倒计时(定时器)实现
- 多个倒计时同时管理
- 闹钟提醒功能
- 实战案例:番茄钟 App、考试倒计时 App
二、Clock 组件介绍
Clock 组件位于 App Inventor 2 组件面板的 Sensors(传感器) 分类中,是一个不可见组件(运行时在屏幕上不可见)。
2.1 添加 Clock 组件
- 在设计视图(Designer)左侧组件面板中,找到 “Sensors” 分类
- 将 Clock 组件拖入屏幕
- 默认名称为
Clock1,可以在组件列表中重命名
2.2 Clock 组件属性
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
TimerEnabled |
Boolean | true | 定时器是否启用 |
TimerInterval |
Number | 1000 | 定时器触发间隔,单位为毫秒(ms) |
TimerAlwaysFires |
Boolean | true | 是否在应用不在前台时也触发定时器 |
属性详解:
- TimerInterval(定时器间隔):这是最核心的属性。
1000表示每 1000 毫秒(即 1 秒)触发一次Timer事件。如果你需要更精确的计时(如百毫秒级),可以设置为100(0.1 秒)甚至10(0.01 秒)。 - TimerEnabled(定时器开关):设置为
false可以暂停定时器,设置为true重新启动。这对于秒表的”暂停/继续”功能非常重要。 - TimerAlwaysFires(后台触发):默认为
true,表示即使 App 在后台,定时器仍然会触发。设为false则只有 App 在前台时才触发。
2.3 Clock 组件事件
| 事件 | 说明 |
|---|---|
Clock1.Timer() |
定时器事件,每隔 TimerInterval 毫秒自动触发一次 |
这是 Clock 组件的唯一事件,也是最核心的事件。所有基于时间的逻辑都写在这个事件的处理程序中。
2.4 Clock 组件方法
Clock 组件提供了丰富的时间处理方法:
时间获取方法
| 方法 | 返回值 | 说明 |
|---|---|---|
Clock1.Now() |
InstantInTime | 获取当前时间(时间瞬间对象) |
Clock1.MakeInstant(text) |
InstantInTime | 将文本日期时间转换为时间瞬间对象 |
Clock1.MakeInstantFromMillis(millis) |
InstantInTime | 将毫秒数转换为时间瞬间对象 |
时间格式化方法
| 方法 | 返回值 | 说明 |
|---|---|---|
Clock1.FormatDateTime(instant, pattern) |
文本 | 按指定格式输出日期时间 |
Clock1.FormatDate(instant, pattern) |
文本 | 按指定格式输出日期 |
Clock1.FormatTime(instant, pattern) |
文本 | 按指定格式输出时间 |
时间计算方法
| 方法 | 返回值 | 说明 |
|---|---|---|
Clock1.AddYears(instant, years) |
InstantInTime | 在指定时间上加/减年数 |
Clock1.AddMonths(instant, months) |
InstantInTime | 在指定时间上加/减月数 |
Clock1.AddWeeks(instant, weeks) |
InstantInTime | 在指定时间上加/减周数 |
Clock1.AddDays(instant, days) |
InstantInTime | 在指定时间上加/减天数 |
Clock1.AddHours(instant, hours) |
InstantInTime | 在指定时间上加/减小时数 |
Clock1.AddMinutes(instant, minutes) |
InstantInTime | 在指定时间上加/减分钟数 |
Clock1.AddSeconds(instant, seconds) |
InstantInTime | 在指定时间上加/减秒数 |
Clock1.Duration(start, end) |
数值(毫秒) | 计算两个时间点之间的毫秒差 |
时间提取方法
| 方法 | 返回值 | 说明 |
|---|---|---|
Clock1.Year(instant) |
数值 | 获取年份 |
Clock1.Month(instant) |
数值 | 获取月份(1-12) |
Clock1.DayOfMonth(instant) |
数值 | 获取日(1-31) |
Clock1.Weekday(instant) |
数值 | 获取星期几(1=周日,7=周六) |
Clock1.Hour(instant) |
数值 | 获取小时(0-23) |
Clock1.Minute(instant) |
数值 | 获取分钟(0-59) |
Clock1.Second(instant) |
数值 | 获取秒(0-59) |
时间转换方法
| 方法 | 返回值 | 说明 |
|---|---|---|
Clock1.GetMillis(instant) |
数值 | 将时间瞬间对象转换为毫秒数 |
Clock1.SystemTime() |
数值 | 获取系统时间的毫秒数 |
三、时间获取与格式化
3.1 获取当前时间
获取当前时间使用 Clock1.Now() 方法,它返回一个 InstantInTime(时间瞬间)对象。
当 Screen1.Initialize 执行:
设置 Label_Time.Text 为 Clock1.FormatDateTime(Clock1.Now(), "yyyy-MM-dd HH:mm:ss")
常用日期时间格式模式:
| 模式 | 示例输出 | 说明 |
|---|---|---|
yyyy-MM-dd |
2025-01-15 | 年-月-日 |
HH:mm:ss |
14:30:25 | 24小时制 时:分:秒 |
hh:mm:ss a |
02:30:25 PM | 12小时制 + AM/PM |
yyyy-MM-dd HH:mm:ss |
2025-01-15 14:30:25 | 完整日期时间 |
MM/dd/yyyy |
01/15/2025 | 美式日期 |
EEEE |
星期三 | 星期几的完整名称 |
3.2 提取时间的各个部分
有时你需要分别获取时、分、秒来显示或计算:
当 Clock1.Timer 执行:
设置 变量 当前时间 为 Clock1.Now()
设置 Label_Hour.Text 为 连接文本(Clock1.Hour(当前时间), "时")
设置 Label_Minute.Text 为 连接文本(Clock1.Minute(当前时间), "分")
设置 Label_Second.Text 为 连接文本(Clock1.Second(当前时间), "秒")
3.3 显示实时时钟
将 Clock 组件的 TimerInterval 设为 1000(1 秒),在 Timer 事件中更新显示,即可实现实时时钟:
当 Clock1.Timer 执行:
设置 Label_Clock.Text 为 Clock1.FormatDateTime(Clock1.Now(), "HH:mm:ss")
四、正计时(秒表)
秒表是 Clock 组件最经典的应用之一。核心思路是记录开始时间,然后不断计算已经过去的时间。
4.1 界面设计
| 组件 | 名称 | 用途 |
|---|---|---|
| Label | Label_Display | 显示计时时间(如 00:05.23) |
| Button | Button_Start | 开始/继续按钮 |
| Button | Button_Stop | 暂停按钮 |
| Button | Button_Reset | 重置按钮 |
| Clock | Clock_Stopwatch | 计时器,Interval = 10(10毫秒) |
4.2 需要的变量
初始化全局变量 已经过去的毫秒数 为 0
初始化全局变量 是否正在计时 为 false
初始化全局变量 暂停时的系统时间 为 0
4.3 开始计时
当 Button_Start.Click 执行:
如果 是否正在计时 = false 那么:
设置 全局变量 是否正在计时 为 true
设置 全局变量 暂停时的系统时间 为 Clock1.SystemTime()
设置 Clock_Stopwatch.TimerEnabled 为 true
说明: 记录开始/恢复时刻的系统时间,之后每 10ms 计算与这个时间点的差值。
4.4 Timer 事件:更新显示
当 Clock_Stopwatch.Timer 执行:
设置 全局变量 已经过去的毫秒数 为
已经过去的毫秒数 + (Clock1.SystemTime() - 暂停时的系统时间)
设置 全局变量 暂停时的系统时间 为 Clock1.SystemTime()
设置 Label_Display.Text 为 毫秒转显示文本(已经过去的毫秒数)
过程:毫秒转显示文本
定义 毫秒转显示文本(总毫秒数) 执行:
设置 变量 总秒数 为 向下取整(总毫秒数 / 1000)
设置 变量 分钟 为 向下取整(总秒数 / 60)
设置 变量 秒数 为 总秒数 模 60
设置 变量 毫秒 为 向下取整((总毫秒数 模 1000) / 10)
返回 连接文本(
格式化两位数(分钟), ":",
格式化两位数(秒数), ".",
格式化两位数(毫秒)
)
过程:格式化两位数(补零)
定义 格式化两位数(数值) 执行:
如果 数值 < 10 那么:
返回 连接文本("0", 数值)
否则:
返回 连接文本("", 数值)
4.5 暂停计时
当 Button_Stop.Click 执行:
设置 全局变量 是否正在计时 为 false
设置 Clock_Stopwatch.TimerEnabled 为 false
4.6 重置
当 Button_Reset.Click 执行:
设置 全局变量 是否正在计时 为 false
设置 全局变量 已经过去的毫秒数 为 0
设置 Clock_Stopwatch.TimerEnabled 为 false
设置 Label_Display.Text 为 "00:00.00"
4.7 精度说明
由于 Android 系统的定时器精度限制,TimerInterval = 10 实际上可能不是精确的 10ms。但我们使用 SystemTime() 计算实际差值,因此显示时间是精确的——即使 Timer 事件有抖动,累计时间仍然准确。
五、倒计时(定时器)
倒计时是另一个非常常见的功能。核心思路是设定目标时间,然后每秒更新剩余时间。
5.1 基础倒计时
界面设计
| 组件 | 名称 | 用途 |
|---|---|---|
| Label | Label_Countdown | 显示剩余时间 |
| TextBox | TextBox_Minutes | 输入倒计时分钟数 |
| Button | Button_Start | 开始按钮 |
| Button | Button_Cancel | 取消按钮 |
| Clock | Clock_Countdown | 倒计时定时器,Interval = 1000 |
变量
初始化全局变量 剩余秒数 为 0
开始倒计时
当 Button_Start.Click 执行:
设置 全局变量 剩余秒数 为
转换为数字(TextBox_Minutes.Text) × 60
如果 剩余秒数 > 0 那么:
设置 Clock_Countdown.TimerEnabled 为 true
设置 Label_Countdown.Text 为 秒数转显示文本(剩余秒数)
否则:
显示提醒对话框("请输入有效的分钟数")
Timer 事件:倒计时逻辑
当 Clock_Countdown.Timer 执行:
设置 全局变量 剩余秒数 为 剩余秒数 - 1
如果 剩余秒数 <= 0 那么:
设置 全局变量 剩余秒数 为 0
设置 Clock_Countdown.TimerEnabled 为 false
设置 Label_Countdown.Text 为 "00:00"
显示提醒对话框("时间到!")
否则:
设置 Label_Countdown.Text 为 秒数转显示文本(剩余秒数)
显示过程
定义 秒数转显示文本(总秒数) 执行:
设置 变量 小时 为 向下取整(总秒数 / 3600)
设置 变量 分钟 为 向下取整((总秒数 模 3600) / 60)
设置 变量 秒数 为 总秒数 模 60
如果 小时 > 0 那么:
返回 连接文本(
格式化两位数(小时), ":",
格式化两位数(分钟), ":",
格式化两位数(秒数)
)
否则:
返回 连接文本(
格式化两位数(分钟), ":",
格式化两位数(秒数)
)
取消倒计时
当 Button_Cancel.Click 执行:
设置 Clock_Countdown.TimerEnabled 为 false
设置 全局变量 剩余秒数 为 0
设置 Label_Countdown.Text 为 "00:00"
5.2 使用 Duration 方法实现精确倒计时
更精确的做法是记录目标时间点,用 Clock1.Duration() 计算剩余时间。这种方式不依赖累计误差:
初始化全局变量 目标时间 为 空
当 Button_Start.Click 执行:
设置 变量 当前时间 为 Clock1.Now()
设置 变量 倒计时秒数 为 转换为数字(TextBox_Minutes.Text) × 60
设置 全局变量 目标时间 为
Clock1.AddSeconds(当前时间, 倒计时秒数)
设置 Clock_Countdown.TimerEnabled 为 true
当 Clock_Countdown.Timer 执行:
设置 变量 当前时间 为 Clock1.Now()
设置 变量 剩余毫秒 为 Clock1.Duration(当前时间, 目标时间)
如果 剩余毫秒 <= 0 那么:
设置 Clock_Countdown.TimerEnabled 为 false
设置 Label_Countdown.Text 为 "00:00"
显示提醒对话框("时间到!")
否则:
设置 变量 剩余秒数 为 向上取整(剩余毫秒 / 1000)
设置 Label_Countdown.Text 为 秒数转显示文本(剩余秒数)
优点: 即使 Timer 事件偶尔延迟或跳过,计算结果仍然精确,因为始终是基于实际时间差计算。
六、多个倒计时同时管理
在实际应用中,你可能需要同时管理多个倒计时(例如烹饪 App 同时计时多道菜)。
6.1 方案:多 Clock 组件 vs 列表管理
方案一:多个 Clock 组件(简单直接)
如果只需要 2-3 个倒计时,直接使用多个 Clock 组件最简单:
| 组件 | 用途 |
|---|---|
| Clock_Timer1 | 管理倒计时 1 |
| Clock_Timer2 | 管理倒计时 2 |
| Clock_Timer3 | 管理倒计时 3 |
每个 Clock 组件各自维护自己的变量和 Timer 事件。
方案二:列表管理(适合更多倒计时)
使用一个 Clock 组件 + 列表管理多个倒计时:
初始化全局变量 倒计时列表 为 [空列表]
当 Button_AddTimer.Click 执行:
设置 变量 新倒计时 为 [建字典]
设置字典键 "名称" 为 TextBox_Name.Text
设置字典键 "剩余秒数" 为 转换为数字(TextBox_Seconds.Text)
设置字典键 "目标时间" 为 Clock1.AddSeconds(Clock1.Now(), 转换为数字(TextBox_Seconds.Text))
添加到列表(全局变量 倒计时列表, 新倒计时)
当 Clock_Manager.Timer 执行:
设置 变量 当前时间 为 Clock1.Now()
设置 变量 索引 为 1
循环 当 索引 <= 列表长度(倒计时列表):
设置 变量 计时项 为 选取列表项(倒计时列表, 索引)
设置 变量 剩余毫秒 为 Clock1.Duration(当前时间, 获取字典值(计时项, "目标时间"))
如果 剩余毫秒 <= 0 那么:
// 倒计时结束,从列表中移除并通知
设置 变量 名称 为 获取字典值(计时项, "名称")
显示提醒对话框(连接文本(名称, " 的时间到了!"))
从列表中移除项(倒计时列表, 索引)
否则:
// 更新显示
设置 变量 剩余秒数 为 向上取整(剩余毫秒 / 1000)
更新对应 Label 显示
设置 索引 为 索引 + 1
提示: 使用列表方案时,如果某个倒计时结束被移除,注意索引不要越界。
6.2 注意事项
- 多个 Clock 组件同时运行会增加系统负担,建议 TimerInterval 不要太小(500ms 以上)
- 如果同时运行的倒计时超过 5 个,建议使用列表方案
- 在
Screen.Pause事件中记录状态,在Screen.Resume事件中恢复计时
七、闹钟提醒
闹钟功能需要在指定时间点触发提醒。核心是不断检查当前时间是否到达(或超过)闹钟设定时间。
7.1 界面设计
| 组件 | 名称 | 用途 |
|---|---|---|
| TimePicker | TimePicker_Alarm | 选择闹钟时间 |
| Button | Button_SetAlarm | 设置闹钟 |
| Button | Button_CancelAlarm | 取消闹钟 |
| Label | Label_AlarmStatus | 显示闹钟状态 |
| Label | Label_TimeRemaining | 显示距闹钟还有多久 |
| Clock | Clock_Alarm | 检测闹钟,Interval = 1000 |
| Player | Player_Alarm | 播放闹铃音 |
| Notifier | Notifier1 | 弹出提醒 |
7.2 变量
初始化全局变量 闹钟时间瞬间 为 空
初始化全局变量 闹钟已设置 为 false
7.3 设置闹钟
当 Button_SetAlarm.Click 执行:
设置 变量 今天 为 Clock1.Now()
设置 变量 目标日期 为 Clock1.FormatDate(今天, "yyyy-MM-dd")
设置 变量 闹钟时间文本 为 连接文本(目标日期, " ", TimePicker_Alarm.Time)
设置 全局变量 闹钟时间瞬间 为 Clock1.MakeInstant(闹钟时间文本)
设置 变量 剩余毫秒 为 Clock1.Duration(Clock1.Now(), 闹钟时间瞬间)
如果 剩余毫秒 <= 0 那么:
// 闹钟时间已过,设为明天
设置 全局变量 闹钟时间瞬间 为 Clock1.AddDays(闹钟时间瞬间, 1)
设置 全局变量 闹钟已设置 为 true
设置 Clock_Alarm.TimerEnabled 为 true
设置 Label_AlarmStatus.Text 为 连接文本("闹钟已设置:", TimePicker_Alarm.Time)
7.4 检测闹钟
当 Clock_Alarm.Timer 执行:
如果 闹钟已设置 = true 那么:
设置 变量 当前时间 为 Clock1.Now()
设置 变量 剩余毫秒 为 Clock1.Duration(当前时间, 闹钟时间瞬间)
如果 剩余毫秒 <= 0 那么:
// 闹钟触发!
设置 全局变量 闹钟已设置 为 false
设置 Clock_Alarm.TimerEnabled 为 false
调用 Player_Alarm.Start()
调用 Notifier1.ShowAlertDialog("闹钟", "时间到了!", "关闭", 取消假)
否则:
// 更新剩余时间显示
设置 变量 剩余秒数 为 向上取整(剩余毫秒 / 1000)
设置 变量 小时 为 向下取整(剩余秒数 / 3600)
设置 变量 分钟 为 向下取整((剩余秒数 模 3600) / 60)
设置 变量 秒数 为 剩余秒数 模 60
设置 Label_TimeRemaining.Text 为 连接文本(
"距闹钟还有 ", 小时, "时", 分钟, "分", 秒数, "秒"
)
7.5 取消闹钟
当 Button_CancelAlarm.Click 执行:
设置 全局变量 闹钟已设置 为 false
设置 Clock_Alarm.TimerEnabled 为 false
设置 Label_AlarmStatus.Text 为 "闹钟已取消"
设置 Label_TimeRemaining.Text 为 ""
7.6 关于后台闹钟
重要限制: App Inventor 的 Clock 组件在 App 被系统杀死后将无法继续工作。如果你的闹钟 App 需要在后台运行或设备重启后仍然有效,有以下替代方案:
- 使用
TimerAlwaysFires = true属性(仅在 App 未被杀死时有效) - 前台服务(需要扩展)
- 通过
ActivityStarter调用系统闹钟
八、实战案例一:番茄钟 App
番茄工作法是一种时间管理方法:专注工作 25 分钟,休息 5 分钟,每 4 个番茄后长休息 15 分钟。
8.1 功能设计
- 支持设置专注时长、短休息时长、长休息时长
- 自动切换专注/休息状态
- 显示当前状态(专注/短休息/长休息)
- 显示番茄计数
- 倒计时结束时播放提示音
- 历史记录(今日完成番茄数)
8.2 界面设计
| 组件 | 名称 | 用途 |
|---|---|---|
| Label | Label_Status | 显示”专注中”/”休息中”等状态 |
| Label | Label_Timer | 显示倒计时(MM:SS) |
| Label | Label_PomodoroCount | 显示番茄计数(第 N 个番茄) |
| Button | Button_Start | 开始/暂停 |
| Button | Button_Skip | 跳过当前阶段 |
| Button | Button_Reset | 重置 |
| Clock | Clock_Pomodoro | 倒计时,Interval = 1000 |
| Player | Player_Notify | 提示音 |
| Notifier | Notifier1 | 弹出通知 |
| Slider | Slider_Focus | 设置专注时长(1-60分钟) |
| Slider | Slider_Break | 设置短休息时长(1-30分钟) |
8.3 变量定义
初始化全局变量 专注分钟数 为 25
初始化全局变量 短休息分钟数 为 5
初始化全局变量 长休息分钟数 为 15
初始化全局变量 番茄计数 为 0
初始化全局变量 剩余秒数 为 0
初始化全局变量 当前状态 为 "idle" // idle / focus / break / longbreak
初始化全局变量 是否暂停 为 false
初始化全局变量 目标时间 为 空
8.4 开始按钮逻辑
当 Button_Start.Click 执行:
如果 当前状态 = "idle" 那么:
// 首次开始 → 进入专注模式
设置 全局变量 当前状态 为 "focus"
设置 全局变量 剩余秒数 为 专注分钟数 × 60
设置 全局变量 目标时间 为 Clock1.AddSeconds(Clock1.Now(), 剩余秒数)
设置 Clock_Pomodoro.TimerEnabled 为 true
设置 Button_Start.Text 为 "暂停"
设置 Label_Status.Text 为 "🍅 专注中..."
否则 如果 是否暂停 = false 那么:
// 暂停
设置 全局变量 是否暂停 为 true
设置 Clock_Pomodoro.TimerEnabled 为 false
设置 Button_Start.Text 为 "继续"
否则:
// 继续
设置 全局变量 是否暂停 为 false
设置 全局变量 目标时间 为 Clock1.AddSeconds(Clock1.Now(), 剩余秒数)
设置 Clock_Pomodoro.TimerEnabled 为 true
设置 Button_Start.Text 为 "暂停"
8.5 Timer 事件核心逻辑
当 Clock_Pomodoro.Timer 执行:
设置 变量 当前时间 为 Clock1.Now()
设置 变量 剩余毫秒 为 Clock1.Duration(当前时间, 目标时间)
如果 剩余毫秒 <= 0 那么:
// 当前阶段结束
调用 Player_Notify.Start()
如果 当前状态 = "focus" 那么:
// 专注结束,进入休息
设置 全局变量 番茄计数 为 番茄计数 + 1
设置 Label_PomodoroCount.Text 为 连接文本("🍅 ", 番茄计数)
如果 番茄计数 模 4 = 0 那么:
// 每4个番茄 → 长休息
设置 全局变量 当前状态 为 "longbreak"
设置 全局变量 剩余秒数 为 长休息分钟数 × 60
设置 Label_Status.Text 为 "☕ 长休息中..."
否则:
// 短休息
设置 全局变量 当前状态 为 "break"
设置 全局变量 剩余秒数 为 短休息分钟数 × 60
设置 Label_Status.Text 为 "🌿 休息中..."
设置 全局变量 目标时间 为 Clock1.AddSeconds(Clock1.Now(), 剩余秒数)
设置 Label_Timer.Text 为 秒数转显示文本(剩余秒数)
否则:
// 休息结束,回到专注
设置 全局变量 当前状态 为 "focus"
设置 全局变量 剩余秒数 为 专注分钟数 × 60
设置 全局变量 目标时间 为 Clock1.AddSeconds(Clock1.Now(), 剩余秒数)
设置 Label_Status.Text 为 "🍅 专注中..."
设置 Label_Timer.Text 为 秒数转显示文本(剩余秒数)
否则:
// 更新倒计时显示
设置 全局变量 剩余秒数 为 向上取整(剩余毫秒 / 1000)
设置 Label_Timer.Text 为 秒数转显示文本(剩余秒数)
8.6 跳过按钮
当 Button_Skip.Click 执行:
设置 全局变量 剩余秒数 为 0
设置 全局变量 目标时间 为 Clock1.Now()
// 这样下一次 Timer 事件会自动处理阶段切换
8.7 重置按钮
当 Button_Reset.Click 执行:
设置 全局变量 当前状态 为 "idle"
设置 全局变量 番茄计数 为 0
设置 全局变量 剩余秒数 为 0
设置 全局变量 是否暂停 为 false
设置 Clock_Pomodoro.TimerEnabled 为 false
设置 Label_Status.Text 为 "准备开始"
设置 Label_Timer.Text 为 连接文本(格式化两位数(专注分钟数), ":00")
设置 Label_PomodoroCount.Text 为 "🍅 0"
设置 Button_Start.Text 为 "开始"
8.8 Slider 设置时长
当 Slider_Focus.PositionChanged 执行:
设置 全局变量 专注分钟数 为 向上取整(Slider_Focus.ThumbPosition)
如果 当前状态 = "idle" 那么:
设置 Label_Timer.Text 为 连接文本(格式化两位数(专注分钟数), ":00")
九、实战案例二:考试倒计时 App
考试倒计时需要显示距某个具体日期时间的天数、小时、分钟和秒数。
9.1 功能设计
- 显示距考试还有 X 天 X 时 X 分 X 秒
- 支持添加多门考试
- 考试当天特殊提醒
- 倒计时结束后显示”考试进行中”
9.2 界面设计
| 组件 | 名称 | 用途 |
|---|---|---|
| Label | Label_ExamName | 显示考试名称 |
| Label | Label_Countdown | 显示倒计时(天:时:分:秒) |
| Label | Label_Status | 显示状态文字 |
| Button | Button_AddExam | 添加考试 |
| Button | Button_SwitchExam | 切换显示不同考试 |
| Clock | Clock_Exam | 倒计时刷新,Interval = 1000 |
| Notifier | Notifier1 | 输入对话框 |
| DatePicker | DatePicker_Exam | 选择考试日期 |
| TimePicker | TimePicker_Exam | 选择考试时间 |
9.3 变量
初始化全局变量 考试列表 为 [空列表]
初始化全局变量 当前显示索引 为 1
9.4 添加考试
当 Button_AddExam.Click 执行:
调用 Notifier1.ShowTextInputDialog(
"添加考试",
"请输入考试名称",
取消假
)
当 Notifier1.AfterTextInput 执行:
设置 变量 考试名称 为 response
设置 变量 考试日期文本 为 DatePicker_Exam.Month & "/" & DatePicker_Exam.DayOfMonth & "/" & DatePicker_Exam.Year
设置 变量 考试时间文本 为 TimePicker_Exam.Time
设置 变量 考试时间瞬间 为 Clock1.MakeInstant(连接文本(考试日期文本, " ", 考试时间文本))
设置 变量 新考试 为 [建字典]
设置字典键 "名称" 为 考试名称
设置字典键 "时间" 为 考试时间瞬间
添加到列表(考试列表, 新考试)
设置 Clock_Exam.TimerEnabled 为 true
9.5 刷新倒计时显示
当 Clock_Exam.Timer 执行:
如果 列表长度(考试列表) = 0 那么:
设置 Label_Countdown.Text 为 "请添加考试"
返回
设置 变量 当前考试 为 选取列表项(考试列表, 当前显示索引)
设置 变量 考试名称 为 获取字典值(当前考试, "名称")
设置 变量 考试时间 为 获取字典值(当前考试, "时间")
设置 变量 当前时间 为 Clock1.Now()
设置 变量 剩余毫秒 为 Clock1.Duration(当前时间, 考试时间)
设置 Label_ExamName.Text 为 考试名称
如果 剩余毫秒 <= 0 那么:
设置 Label_Countdown.Text 为 "考试进行中!"
设置 Label_Status.Text 为 "📝 祝考试顺利!"
设置 Label_Countdown.TextColor 为 红色
否则:
设置 变量 总秒数 为 向上取整(剩余毫秒 / 1000)
设置 变量 天数 为 向下取整(总秒数 / 86400)
设置 变量 小时 为 向下取整((总秒数 模 86400) / 3600)
设置 变量 分钟 为 向下取整((总秒数 模 3600) / 60)
设置 变量 秒数 为 总秒数 模 60
设置 Label_Countdown.Text 为 连接文本(
天数, "天 ",
格式化两位数(小时), ":",
格式化两位数(分钟), ":",
格式化两位数(秒数)
)
// 根据时间紧迫程度显示不同状态
如果 天数 > 7 那么:
设置 Label_Status.Text 为 "📅 还有时间,好好准备"
否则 如果 天数 > 1 那么:
设置 Label_Status.Text 为 "⏰ 即将考试,加油复习!"
否则:
设置 Label_Status.Text 为 "🔥 明天考试,全力以赴!"
9.6 切换考试
当 Button_SwitchExam.Click 执行:
如果 列表长度(考试列表) > 1 那么:
设置 全局变量 当前显示索引 为 (当前显示索引 模 列表长度(考试列表)) + 1
十、进阶技巧
10.1 保持后台计时
App Inventor 的 Clock 组件默认情况下,当 App 切到后台时计时可能不精确(虽然 TimerAlwaysFires = true 时仍会触发,但频率会降低)。
推荐方案:记录关键时间点,而非依赖累计计时。
无论 App 是否在后台,始终使用 Clock1.Now() 和 Clock1.Duration() 计算实际时间差,这样即使 Timer 事件偶尔延迟,结果仍然准确。
10.2 TimerInterval 选择指南
| 间隔 | 精度 | 适用场景 | 注意事项 |
|---|---|---|---|
| 10ms | 0.01秒 | 秒表(毫秒级) | 电量消耗较大 |
| 100ms | 0.1秒 | 秒表(百毫秒级) | 较平衡的选择 |
| 500ms | 0.5秒 | 动画/进度条 | 视觉流畅度可接受 |
| 1000ms | 1秒 | 倒计时、时钟 | 默认值,最常用 |
| 5000ms | 5秒 | 后台轮询 | 省电 |
| 60000ms | 1分钟 | 低频检查 | 非常省电 |
10.3 使用多个 Clock 组件
App Inventor 允许在同一屏幕中使用多个 Clock 组件,各自独立运行。常见的组合:
Clock_Display(Interval=1000)- 更新时间显示Clock_Countdown(Interval=1000)- 倒计时逻辑Clock_Background(Interval=60000)- 后台低频检查
注意: 不建议同时运行超过 5 个 Clock 组件,会影响性能。
10.4 时间格式化的注意事项
Clock1.MakeInstant(text)接受的文本格式取决于设备的区域设置。建议使用MM/dd/yyyy HH:mm:ss格式以确保兼容性。Clock1.Duration(start, end)中参数顺序会影响结果的正负:Duration(start, end)返回end - start的毫秒数。如果end在start之后,结果为正数。
10.5 倒计时精度优化
如果需要非常精确的倒计时(如体育比赛计时),建议:
- 使用
Clock1.SystemTime()而非简单的计数器递减 TimerInterval设置为 100ms 或更小- 记录开始和结束的时间瞬间,始终用
Duration计算差值 - 显示更新时取整到需要的精度
十一、常见问题
Q1:Clock 组件的 Timer 事件不准确怎么办?
A: 这是 Android 系统的正常行为,Timer 事件不是精确的实时中断。解决方法是使用 Clock1.Now() 或 Clock1.SystemTime() 记录真实时间点,在 Timer 事件中计算差值,而不是简单累加 Interval。
Q2:App 切到后台后倒计时还在继续吗?
A: 设置 TimerAlwaysFires = true 后,Clock 会在后台继续触发,但精度可能降低。如果 App 被系统杀死,定时器会停止。建议在 Screen.Pause 时记录时间状态,在 Screen.Resume 时重新计算。
Q3:可以同时运行多个倒计时吗?
A: 可以。使用多个 Clock 组件分别管理,或使用一个 Clock + 列表管理(参见第六节)。
Q4:如何让倒计时结束时播放自定义声音?
A: 使用 Player 组件。将音频文件(.mp3 或 .wav)上传到项目素材中,设置 Player1.Source 为文件名,在倒计时结束时调用 Player1.Start()。
Q5:TimerInterval 最小可以设为多少?
A: 技术上可以设为 1ms,但实际上 Android 系统无法保证 1ms 的精度。低于 50ms 的间隔在大多数设备上并不可靠。建议最低设为 10ms(用于秒表),普通倒计时用 1000ms 即可。
Q6:如何实现”X 分钟后提醒”功能?
A: 核心代码:
设置 变量 提醒时间 为 Clock1.AddMinutes(Clock1.Now(), X)
设置 Clock1.TimerEnabled 为 true
// 在 Timer 事件中:
如果 Clock1.Duration(Clock1.Now(), 提醒时间) <= 0 那么:
播放提醒音
设置 Clock1.TimerEnabled 为 false
十二、总结
Clock 组件是 App Inventor 2 中处理时间相关功能的核心组件。通过本文介绍的技巧,你可以实现:
| 功能 | 核心方法 |
|---|---|
| 实时时钟 | Timer 事件 + FormatDateTime() |
| 秒表(正计时) | SystemTime() 差值累计 |
| 倒计时 | Duration() 计算剩余时间 |
| 多计时器 | 列表 + 字典管理 |
| 闹钟 | MakeInstant() + Duration() 比较 |
| 番茄钟 | 状态机 + 自动阶段切换 |
| 考试倒计时 | Duration() + 天/时/分/秒拆分 |
关键原则:
- 始终基于真实时间计算,而非依赖累计计数
- 使用
Duration()而非手动递减,确保精度 - 在 Screen.Pause/Resume 中保存和恢复状态
- 选择合适的 TimerInterval,平衡精度和电量
参考资料
版权声明
本文档基于 MIT App Inventor 官方文档及社区资源整理,版权归原作者所有:
- MIT App Inventor 官方文档采用 CC BY-SA 4.0 授权
- MIT App Inventor Community 帖子版权归原作者所有
本文档由 ai2claw 🐝 整理,仅供学习参考,如有侵权请联系删除。
扫码添加客服咨询