- 一、概述
- 二、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 组件拖入屏幕
- 默认名称为
计时器1,可以在组件列表中重命名
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 组件事件
| 事件 | 说明 |
|---|---|
计时器1.Timer() |
定时器事件,每隔 TimerInterval 毫秒自动触发一次 |
这是 Clock 组件的唯一事件,也是最核心的事件。所有基于时间的逻辑都写在这个事件的处理程序中。
2.4 Clock 组件方法
Clock 组件提供了丰富的时间处理方法:
时间获取方法
| 方法 | 返回值 | 说明 |
|---|---|---|
计时器1.Now() |
InstantInTime | 获取当前时间(时间瞬间对象) |
计时器1.MakeInstant(text) |
InstantInTime | 将文本日期时间转换为时间瞬间对象 |
计时器1.MakeInstantFromMillis(millis) |
InstantInTime | 将毫秒数转换为时间瞬间对象 |
时间格式化方法
| 方法 | 返回值 | 说明 |
|---|---|---|
计时器1.FormatDateTime(instant, pattern) |
文本 | 按指定格式输出日期时间 |
计时器1.FormatDate(instant, pattern) |
文本 | 按指定格式输出日期 |
计时器1.FormatTime(instant, pattern) |
文本 | 按指定格式输出时间 |
时间计算方法
| 方法 | 返回值 | 说明 |
|---|---|---|
计时器1.AddYears(instant, years) |
InstantInTime | 在指定时间上加/减年数 |
计时器1.AddMonths(instant, months) |
InstantInTime | 在指定时间上加/减月数 |
计时器1.AddWeeks(instant, weeks) |
InstantInTime | 在指定时间上加/减周数 |
计时器1.AddDays(instant, days) |
InstantInTime | 在指定时间上加/减天数 |
计时器1.AddHours(instant, hours) |
InstantInTime | 在指定时间上加/减小时数 |
计时器1.AddMinutes(instant, minutes) |
InstantInTime | 在指定时间上加/减分钟数 |
计时器1.AddSeconds(instant, seconds) |
InstantInTime | 在指定时间上加/减秒数 |
计时器1.Duration(start, end) |
数值(毫秒) | 计算两个时间点之间的毫秒差 |
时间提取方法
| 方法 | 返回值 | 说明 |
|---|---|---|
计时器1.Year(instant) |
数值 | 获取年份 |
计时器1.Month(instant) |
数值 | 获取月份(1-12) |
计时器1.DayOfMonth(instant) |
数值 | 获取日(1-31) |
计时器1.Weekday(instant) |
数值 | 获取星期几(1=周日,7=周六) |
计时器1.Hour(instant) |
数值 | 获取小时(0-23) |
计时器1.Minute(instant) |
数值 | 获取分钟(0-59) |
计时器1.Second(instant) |
数值 | 获取秒(0-59) |
时间转换方法
| 方法 | 返回值 | 说明 |
|---|---|---|
计时器1.GetMillis(instant) |
数值 | 将时间瞬间对象转换为毫秒数 |
计时器1.SystemTime() |
数值 | 获取系统时间的毫秒数 |
三、时间获取与格式化
3.1 获取当前时间
获取当前时间使用 计时器1.Now() 方法,它返回一个 InstantInTime(时间瞬间)对象。
when Screen1.Initialize() {
标签_时间.Text = 计时器1.FormatDateTime(计时器1.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 提取时间的各个部分
有时你需要分别获取时、分、秒来显示或计算:
when 计时器1.Timer() {
var 当前时间 = 计时器1.Now()
标签_小时.Text = join(计时器1.Hour(当前时间), "时")
标签_分钟.Text = join(计时器1.Minute(当前时间), "分")
标签_秒.Text = join(计时器1.Second(当前时间), "秒")
}
3.3 显示实时时钟
将 Clock 组件的 TimerInterval 设为 1000(1 秒),在 Timer 事件中更新显示,即可实现实时时钟:
when 计时器1.Timer() {
标签_时钟.Text = 计时器1.FormatDateTime(计时器1.Now(), "HH:mm:ss")
}
四、正计时(秒表)
秒表是 Clock 组件最经典的应用之一。核心思路是记录开始时间,然后不断计算已经过去的时间。
4.1 界面设计
| 组件 | 名称 | 用途 |
|---|---|---|
| Label | 标签_显示 | 显示计时时间(如 00:05.23) |
| Button | 按钮_开始 | 开始/继续按钮 |
| Button | 按钮_停止 | 暂停按钮 |
| Button | 按钮_复位 | 重置按钮 |
| Clock | 计时器_秒表 | 计时器,Interval = 10(10毫秒) |
4.2 需要的变量
global 已经过去的毫秒数 = 0
global 是否正在计时 = false
global 暂停时的系统时间 = 0
4.3 开始计时
when 按钮_开始.Click() {
if 是否正在计时 == false {
var 是否正在计时 = true
var 暂停时的系统时间 = 计时器1.SystemTime()
计时器_秒表.TimerEnabled = true
}
}
说明: 记录开始/恢复时刻的系统时间,之后每 10ms 计算与这个时间点的差值。
4.4 Timer 事件:更新显示
when 计时器_秒表.Timer() {
已经过去的毫秒数 =
已经过去的毫秒数 + (计时器1.SystemTime() - 暂停时的系统时间)
暂停时的系统时间 = 计时器1.SystemTime()
标签_显示.Text = 毫秒转显示文本(已经过去的毫秒数)
}
过程:毫秒转显示文本
function 毫秒转显示文本(总毫秒数) {
var 总秒数 = Math.floor(总毫秒数 / 1000)
var 分钟 = Math.floor(总秒数 / 60)
var 秒数 = 总秒数 % 60
var 毫秒 = Math.floor((总毫秒数 % 1000) / 10)
return join(
格式化两位数(分钟), ":",
格式化两位数(秒数), ".",
格式化两位数(毫秒)
)
}
过程:格式化两位数(补零)
function 格式化两位数(数值) {
return choose(数值 < 10, join("0", 数值), join("", 数值))
}
4.5 暂停计时
when 按钮_停止.Click() {
var 是否正在计时 = false
计时器_秒表.TimerEnabled = false
}
4.6 重置
when 按钮_复位.Click() {
var 是否正在计时 = false
var 已经过去的毫秒数 = 0
计时器_秒表.TimerEnabled = false
标签_显示.Text = "00:00.00"
}
4.7 精度说明
由于 Android 系统的定时器精度限制,TimerInterval = 10 实际上可能不是精确的 10ms。但我们使用 SystemTime() 计算实际差值,因此显示时间是精确的——即使 Timer 事件有抖动,累计时间仍然准确。
五、倒计时(定时器)
倒计时是另一个非常常见的功能。核心思路是设定目标时间,然后每秒更新剩余时间。
5.1 基础倒计时
界面设计
| 组件 | 名称 | 用途 |
|---|---|---|
| Label | 标签_倒计时 | 显示剩余时间 |
| TextBox | TextBox_Minutes | 输入倒计时分钟数 |
| Button | 按钮_开始 | 开始按钮 |
| Button | 按钮_取消 | 取消按钮 |
| Clock | 计时器_倒计时 | 倒计时定时器,Interval = 1000 |
变量
global 剩余秒数 = 0
开始倒计时
when 按钮_开始.Click() {
剩余秒数 = TextBox_Minutes.Text * 60
if 剩余秒数 > 0 {
计时器_倒计时.TimerEnabled = true
标签_倒计时.Text = 秒数转显示文本(剩余秒数)
} else {
显示提醒对话框("请输入有效的分钟数")
}
}
Timer 事件:倒计时逻辑
when 计时器_倒计时.Timer() {
剩余秒数 = 剩余秒数 - 1
if 剩余秒数 <= 0 {
剩余秒数 = 0
计时器_倒计时.TimerEnabled = false
标签_倒计时.Text = "00:00"
显示提醒对话框("时间到!")
} else {
标签_倒计时.Text = 秒数转显示文本(剩余秒数)
}}
显示过程
function 秒数转显示文本(总秒数) {
var 小时 = Math.floor(总秒数 / 3600)
var 分钟 = Math.floor((总秒数 % 3600) / 60)
var 秒数 = 总秒数 % 60
return choose(小时 > 0,
join(格式化两位数(小时), ":", 格式化两位数(分钟), ":", 格式化两位数(秒数)),
join(格式化两位数(分钟), ":", 格式化两位数(秒数)))
}
取消倒计时
when 按钮_取消.Click() {
计时器_倒计时.TimerEnabled = false
var 剩余秒数 = 0
标签_倒计时.Text = "00:00"
}
5.2 使用 Duration 方法实现精确倒计时
更精确的做法是记录目标时间点,用 计时器1.Duration() 计算剩余时间。这种方式不依赖累计误差:
global 目标时间 = 空
when 按钮_开始.Click() {
var 当前时间 = 计时器1.Now()
var 倒计时秒数 = TextBox_Minutes.Text * 60
目标时间 =
计时器1.AddSeconds(当前时间, 倒计时秒数)
计时器_倒计时.TimerEnabled = true
}
when 计时器_倒计时.Timer() {
var 当前时间 = 计时器1.Now()
var 剩余毫秒 = 计时器1.Duration(当前时间, 目标时间)
if 剩余毫秒 <= 0 {
计时器_倒计时.TimerEnabled = false
标签_倒计时.Text = "00:00"
显示提醒对话框("时间到!")
} else {
var 剩余秒数 = Math.ceil(剩余毫秒 / 1000)
标签_倒计时.Text = 秒数转显示文本(剩余秒数)
}}
优点: 即使 Timer 事件偶尔延迟或跳过,计算结果仍然精确,因为始终是基于实际时间差计算。
六、多个倒计时同时管理
在实际应用中,你可能需要同时管理多个倒计时(例如烹饪 App 同时计时多道菜)。
6.1 方案:多 Clock 组件 vs 列表管理
方案一:多个 Clock 组件(简单直接)
如果只需要 2-3 个倒计时,直接使用多个 Clock 组件最简单:
| 组件 | 用途 |
|---|---|
| Clock_Timer1 | 管理倒计时 1 |
| Clock_Timer2 | 管理倒计时 2 |
| Clock_Timer3 | 管理倒计时 3 |
每个 Clock 组件各自维护自己的变量和 Timer 事件。
方案二:列表管理(适合更多倒计时)
使用一个 Clock 组件 + 列表管理多个倒计时:
global 倒计时列表 = [空列表]
when Button_AddTimer.Click() {
var 新倒计时 = makeDict(pair("名称", 文本输入框_名称.Text), pair("剩余秒数", TextBox_Seconds.Text), pair("目标时间", 计时器1.Now()))
listAdd(倒计时列表, 新倒计时)
}
when Clock_Manager.Timer() {
var 当前时间 = 计时器1.Now()
var 索引 = 1
while 索引 <= length(倒计时列表) {
var 计时项 = selectItem(倒计时列表, 索引)
var 剩余毫秒 = 计时器1.Duration(当前时间, dictLookup(计时项, "目标时间", 0))
if 剩余毫秒 <= 0 {
removeListItem(倒计时列表, 索引)
} else {
var 剩余秒数 = Math.ceil(剩余毫秒 / 1000)
索引 = 索引 + 1
}
}
}
提示: 使用列表方案时,如果某个倒计时结束被移除,注意索引不要越界。
6.2 注意事项
- 多个 Clock 组件同时运行会增加系统负担,建议 TimerInterval 不要太小(500ms 以上)
- 如果同时运行的倒计时超过 5 个,建议使用列表方案
- 在
Screen.Pause事件中记录状态,在Screen.Resume事件中恢复计时
七、闹钟提醒
闹钟功能需要在指定时间点触发提醒。核心是不断检查当前时间是否到达(或超过)闹钟设定时间。
7.1 界面设计
| 组件 | 名称 | 用途 |
|---|---|---|
| TimePicker | 时间选择框_闹钟 | 选择闹钟时间 |
| Button | 按钮_设置闹钟 | 设置闹钟 |
| Button | 按钮_取消闹钟 | 取消闹钟 |
| Label | 标签_闹钟状态 | 显示闹钟状态 |
| Label | 标签_剩余时间 | 显示距闹钟还有多久 |
| Clock | 计时器_闹钟 | 检测闹钟,Interval = 1000 |
| Player | Player_Alarm | 播放闹铃音 |
| Notifier | 对话框1 | 弹出提醒 |
7.2 变量
global 闹钟时间瞬间 = 空
global 闹钟已设置 = false
7.3 设置闹钟
when 按钮_设置闹钟.Click() {
var 今天 = 计时器1.Now()
var 目标日期 = 计时器1.FormatDate(今天, "yyyy-MM-dd")
var 闹钟时间文本 = join(目标日期, " ", 时间选择框_闹钟.Time)
var 闹钟时间瞬间 = 计时器1.MakeInstant(闹钟时间文本)
var 剩余毫秒 = 计时器1.Duration(计时器1.Now(), 闹钟时间瞬间)
if 剩余毫秒 <= 0 {
// 闹钟时间已过,设为明天
var 闹钟时间瞬间 = 计时器1.AddDays(闹钟时间瞬间, 1)
}
var 闹钟已设置 = true
计时器_闹钟.TimerEnabled = true
标签_闹钟状态.Text = join("闹钟已设置:", 时间选择框_闹钟.Time)
}
7.4 检测闹钟
when 计时器_闹钟.Timer() {
if 闹钟已设置 = true {
var 当前时间 = 计时器1.Now()
var 剩余毫秒 = 计时器1.Duration(当前时间, 闹钟时间瞬间)
if 剩余毫秒 <= 0 {
// 闹钟触发!
闹钟已设置 = false
计时器_闹钟.TimerEnabled = false
Player_Alarm.Start()
对话框1.ShowAlertDialog("闹钟", "时间到了!", "关闭", 取消假)
} else {
// 更新剩余时间显示
var 剩余秒数 = Math.ceil(剩余毫秒 / 1000)
var 小时 = Math.floor(剩余秒数 / 3600)
var 分钟 = Math.floor((剩余秒数 % 3600) / 60)
var 秒数 = 剩余秒数 % 60
标签_剩余时间.Text = join(
"距闹钟还有 ", 小时, "时", 分钟, "分", 秒数, "秒"
)
}}}
7.5 取消闹钟
when 按钮_取消闹钟.Click() {
var 闹钟已设置 = false
计时器_闹钟.TimerEnabled = false
标签_闹钟状态.Text = "闹钟已取消"
标签_剩余时间.Text = ""
}
7.6 关于后台闹钟
重要限制: App Inventor 的 Clock 组件在 App 被系统杀死后将无法继续工作。如果你的闹钟 App 需要在后台运行或设备重启后仍然有效,有以下替代方案:
- 使用
TimerAlwaysFires = true属性(仅在 App 未被杀死时有效) - 前台服务(需要扩展)
- 通过
ActivityStarter调用系统闹钟
八、实战案例一:番茄钟 App
番茄工作法是一种时间管理方法:专注工作 25 分钟,休息 5 分钟,每 4 个番茄后长休息 15 分钟。
8.1 功能设计
- 支持设置专注时长、短休息时长、长休息时长
- 自动切换专注/休息状态
- 显示当前状态(专注/短休息/长休息)
- 显示番茄计数
- 倒计时结束时播放提示音
- 历史记录(今日完成番茄数)
8.2 界面设计
| 组件 | 名称 | 用途 |
|---|---|---|
| Label | 标签_状态 | 显示”专注中”/”休息中”等状态 |
| Label | Label_Timer | 显示倒计时(MM:SS) |
| Label | Label_PomodoroCount | 显示番茄计数(第 N 个番茄) |
| Button | 按钮_开始 | 开始/暂停 |
| Button | 按钮_跳过 | 跳过当前阶段 |
| Button | 按钮_复位 | 重置 |
| Clock | 计时器_番茄钟 | 倒计时,Interval = 1000 |
| Player | Player_Notify | 提示音 |
| Notifier | 对话框1 | 弹出通知 |
| Slider | Slider_Focus | 设置专注时长(1-60分钟) |
| Slider | Slider_Break | 设置短休息时长(1-30分钟) |
8.3 变量定义
global 专注分钟数 = 25
global 短休息分钟数 = 5
global 长休息分钟数 = 15
global 番茄计数 = 0
global 剩余秒数 = 0
global 当前状态 = "idle" // idle / focus / break / longbreak
global 是否暂停 = false
global 目标时间 = 空
8.4 开始按钮逻辑
when 按钮_开始.Click() {
if 当前状态 == "idle" {
// 首次开始 → 进入专注模式
var 当前状态 = "focus"
var 剩余秒数 = 专注分钟数 * 60
var 目标时间 = 计时器1.AddSeconds(计时器1.Now(), 剩余秒数)
计时器_番茄钟.TimerEnabled = true
按钮_开始.Text = "暂停"
标签_状态.Text = "🍅 专注中..."
} else if 是否暂停 == false {
// 暂停
var 是否暂停 = true
计时器_番茄钟.TimerEnabled = false
按钮_开始.Text = "继续"
} else {
// 继续
var 是否暂停 = false
var 目标时间 = 计时器1.AddSeconds(计时器1.Now(), 剩余秒数)
计时器_番茄钟.TimerEnabled = true
按钮_开始.Text = "暂停"
}
}
8.5 Timer 事件核心逻辑
when 计时器_番茄钟.Timer() {
var 当前时间 = 计时器1.Now()
var 剩余毫秒 = 计时器1.Duration(当前时间, 目标时间)
if 剩余毫秒 <= 0 {
Player_Notify.Start()
当前状态 = "break"
剩余秒数 = 短休息分钟数 * 60
标签_状态.Text = "🌿 休息中..."
目标时间 = 计时器1.AddSeconds(计时器1.Now(), 剩余秒数)
} else {
剩余秒数 = Math.ceil(剩余毫秒 / 1000)
Label_Timer.Text = 秒数转显示文本(剩余秒数)
}
}
8.6 跳过按钮
when 按钮_跳过.Click() {
var 剩余秒数 = 0
var 目标时间 = 计时器1.Now()
// 这样下一次 Timer 事件会自动处理阶段切换
}
8.7 重置按钮
when 按钮_复位.Click() {
当前状态 = "idle"
番茄计数 = 0
剩余秒数 = 0
是否暂停 = false
计时器_番茄钟.TimerEnabled = false
标签_状态.Text = "准备开始"
Label_Timer.Text = join(格式化两位数(专注分钟数), ":00")
Label_PomodoroCount.Text = "🍅 0"
按钮_开始.Text = "开始"
}
8.8 Slider 设置时长
when Slider_Focus.PositionChanged() {
专注分钟数 = Math.ceil(Slider_Focus.ThumbPosition)
if 当前状态 = "idle" {
Label_Timer.Text = join(格式化两位数(专注分钟数), ":00")
}}
九、实战案例二:考试倒计时 App
考试倒计时需要显示距某个具体日期时间的天数、小时、分钟和秒数。
9.1 功能设计
- 显示距考试还有 X 天 X 时 X 分 X 秒
- 支持添加多门考试
- 考试当天特殊提醒
- 倒计时结束后显示”考试进行中”
9.2 界面设计
| 组件 | 名称 | 用途 |
|---|---|---|
| Label | Label_ExamName | 显示考试名称 |
| Label | 标签_倒计时 | 显示倒计时(天:时:分:秒) |
| Label | 标签_状态 | 显示状态文字 |
| Button | 按钮_添加考试 | 添加考试 |
| Button | Button_SwitchExam | 切换显示不同考试 |
| Clock | Clock_Exam | 倒计时刷新,Interval = 1000 |
| Notifier | 对话框1 | 输入对话框 |
| DatePicker | DatePicker_Exam | 选择考试日期 |
| TimePicker | TimePicker_Exam | 选择考试时间 |
9.3 变量
global 考试列表 = [空列表]
global 当前显示索引 = 1
9.4 添加考试
when 按钮_添加考试.Click() {
对话框1.ShowTextDialog("添加考试", "请输入考试名称", 取消假)
}
when 对话框1.AfterTextInput() {
var 考试名称 = response
var 考试日期文本 = DatePicker_Exam.Month + "/" + DatePicker_Exam.DayOfMonth + "/" + DatePicker_Exam.Year
var 考试时间文本 = TimePicker_Exam.Time
var 考试时间瞬间 = 计时器1.MakeInstant(join(考试日期文本, " ", 考试时间文本))
var 新考试 = makeDict(pair("名称", 考试名称), pair("时间", 考试时间瞬间))
listAdd(考试列表, 新考试)
Clock_Exam.TimerEnabled = true
}
9.5 刷新倒计时显示
when Clock_Exam.Timer() {
if length(考试列表) = 0 {
标签_倒计时.Text = "请添加考试"
} else {
var 当前考试 = selectItem(考试列表, 当前显示索引)
var 考试名称 = dictLookup(当前考试, "名称", "")
var 考试时间 = dictLookup(当前考试, "时间", 0)
var 当前时间 = 计时器1.Now()
var 剩余毫秒 = 计时器1.Duration(当前时间, 考试时间)
Label_ExamName.Text = 考试名称
if 剩余毫秒 <= 0 {
标签_倒计时.Text = "考试进行中!"
标签_状态.Text = "📝 祝考试顺利!"
标签_倒计时.TextColor = 红色
} else {
var 总秒数 = Math.ceil(剩余毫秒 / 1000)
var 天数 = Math.floor(总秒数 / 86400)
var 小时 = Math.floor((总秒数 % 86400) / 3600)
var 分钟 = Math.floor((总秒数 % 3600) / 60)
var 秒数 = 总秒数 % 60
标签_倒计时.Text = join(
天数, "天 ",
格式化两位数(小时), ":",
格式化两位数(分钟), ":",
格式化两位数(秒数)
)
// 根据时间紧迫程度显示不同状态
if 天数 > 7 {
标签_状态.Text = "📅 还有时间,好好准备"
} else {
标签_状态.Text = "⏰ 即将考试,加油复习!"
}
}
}
}
9.6 切换考试
when Button_SwitchExam.Click() {
if length(考试列表) > 1 {
当前显示索引 = (当前显示索引 % length(考试列表)) + 1
}
}
十、进阶技巧
10.1 保持后台计时
App Inventor 的 Clock 组件默认情况下,当 App 切到后台时计时可能不精确(虽然 TimerAlwaysFires = true 时仍会触发,但频率会降低)。
推荐方案:记录关键时间点,而非依赖累计计时。
无论 App 是否在后台,始终使用 计时器1.Now() 和 计时器1.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)- 更新时间显示计时器_倒计时(Interval=1000)- 倒计时逻辑Clock_Background(Interval=60000)- 后台低频检查
注意: 不建议同时运行超过 5 个 Clock 组件,会影响性能。
10.4 时间格式化的注意事项
计时器1.MakeInstant(text)接受的文本格式取决于设备的区域设置。建议使用MM/dd/yyyy HH:mm:ss格式以确保兼容性。计时器1.Duration(start, end)中参数顺序会影响结果的正负:Duration(start, end)返回end - start的毫秒数。如果end在start之后,结果为正数。
10.5 倒计时精度优化
如果需要非常精确的倒计时(如体育比赛计时),建议:
- 使用
计时器1.SystemTime()而非简单的计数器递减 TimerInterval设置为 100ms 或更小- 记录开始和结束的时间瞬间,始终用
Duration计算差值 - 显示更新时取整到需要的精度
十一、常见问题
Q1:Clock 组件的 Timer 事件不准确怎么办?
A: 这是 Android 系统的正常行为,Timer 事件不是精确的实时中断。解决方法是使用 计时器1.Now() 或 计时器1.SystemTime() 记录真实时间点,在 Timer 事件中计算差值,而不是简单累加 Interval。
Q2:App 切到后台后倒计时还在继续吗?
A: 设置 TimerAlwaysFires = true 后,Clock 会在后台继续触发,但精度可能降低。如果 App 被系统杀死,定时器会停止。建议在 Screen.Pause 时记录时间状态,在 Screen.Resume 时重新计算。
Q3:可以同时运行多个倒计时吗?
A: 可以。使用多个 Clock 组件分别管理,或使用一个 Clock + 列表管理(参见第六节)。
Q4:如何让倒计时结束时播放自定义声音?
A: 使用 Player 组件。将音频文件(.mp3 或 .wav)上传到项目素材中,设置 音频播放器1.Source 为文件名,在倒计时结束时调用 音频播放器1.Start()。
Q5:TimerInterval 最小可以设为多少?
A: 技术上可以设为 1ms,但实际上 Android 系统无法保证 1ms 的精度。低于 50ms 的间隔在大多数设备上并不可靠。建议最低设为 10ms(用于秒表),普通倒计时用 1000ms 即可。
Q6:如何实现”X 分钟后提醒”功能?
A: 核心代码:
global 提醒时间 = 计时器1.AddMinutes(计时器1.Now(), X)
when 计时器1.Timer() {
if 计时器1.Duration(计时器1.Now(), 提醒时间) <= 0 {
计时器1.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 🐝 整理,仅供学习参考,如有侵权请联系删除。
扫码添加客服咨询