App Inventor 2 倒计时与计时器教程 - Clock组件详解

« 返回首页

一、概述

在 App 开发中,时间相关功能无处不在:秒表计时、倒计时提醒、闹钟、番茄钟、考试倒计时…… App Inventor 2 提供了内置的 Clock(时钟)组件,它是所有时间相关功能的核心。

Clock 组件本质上是一个定时器(Timer),能够以固定的时间间隔反复触发事件,同时也提供了丰富的时间处理方法。掌握 Clock 组件,是开发实用工具类 App 的必备技能。

本文涵盖:

  • Clock 组件属性与事件详解
  • 时间获取与格式化
  • 正计时(秒表)实现
  • 倒计时(定时器)实现
  • 多个倒计时同时管理
  • 闹钟提醒功能
  • 实战案例:番茄钟 App、考试倒计时 App

二、Clock 组件介绍

Clock 组件位于 App Inventor 2 组件面板的 Sensors(传感器) 分类中,是一个不可见组件(运行时在屏幕上不可见)。

2.1 添加 Clock 组件

  1. 在设计视图(Designer)左侧组件面板中,找到 “Sensors” 分类
  2. Clock 组件拖入屏幕
  3. 默认名称为 计时器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 需要的变量

初始化全局变量 已经过去的毫秒数 为 0
初始化全局变量 是否正在计时 为 false
初始化全局变量 暂停时的系统时间 为 0

4.3 开始计时

when 按钮_开始.Click() {
  if 是否正在计时 == false {
    var 是否正在计时 = true
    var 暂停时的系统时间 = 计时器1.SystemTime()
    计时器_秒表.TimerEnabled = true
  }
}

说明: 记录开始/恢复时刻的系统时间,之后每 10ms 计算与这个时间点的差值。

4.4 Timer 事件:更新显示

当 计时器_秒表.Timer 执行:
  设置 全局变量 已经过去的毫秒数 为 
    已经过去的毫秒数 + (计时器1.SystemTime() - 暂停时的系统时间)
  设置 全局变量 暂停时的系统时间 为 计时器1.SystemTime()
  设置 标签_显示.Text 为 毫秒转显示文本(已经过去的毫秒数)

过程:毫秒转显示文本

定义 毫秒转显示文本(总毫秒数) 执行:
  设置 变量 总秒数 为 向下取整(总毫秒数 / 1000)
  设置 变量 分钟 为 向下取整(总秒数 / 60)
  设置 变量 秒数 为 总秒数 模 60
  设置 变量 毫秒 为 向下取整((总毫秒数 模 1000) / 10)
  返回 连接文本(
    格式化两位数(分钟), ":",
    格式化两位数(秒数), ".",
    格式化两位数(毫秒)
  )

过程:格式化两位数(补零)

定义 格式化两位数(数值) 执行:
  如果 数值 < 10 那么:
    返回 连接文本("0", 数值)
  否则:
    返回 连接文本("", 数值)

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

变量

初始化全局变量 剩余秒数 为 0

开始倒计时

当 按钮_开始.Click 执行:
  设置 全局变量 剩余秒数 为 
    转换为数字(TextBox_Minutes.Text) × 60
  如果 剩余秒数 > 0 那么:
    设置 计时器_倒计时.TimerEnabled 为 true
    设置 标签_倒计时.Text 为 秒数转显示文本(剩余秒数)
  否则:
    显示提醒对话框("请输入有效的分钟数")

Timer 事件:倒计时逻辑

当 计时器_倒计时.Timer 执行:
  设置 全局变量 剩余秒数 为 剩余秒数 - 1
  如果 剩余秒数 <= 0 那么:
    设置 全局变量 剩余秒数 为 0
    设置 计时器_倒计时.TimerEnabled 为 false
    设置 标签_倒计时.Text 为 "00:00"
    显示提醒对话框("时间到!")
  否则:
    设置 标签_倒计时.Text 为 秒数转显示文本(剩余秒数)

显示过程

定义 秒数转显示文本(总秒数) 执行:
  设置 变量 小时 为 向下取整(总秒数 / 3600)
  设置 变量 分钟 为 向下取整((总秒数 模 3600) / 60)
  设置 变量 秒数 为 总秒数 模 60
  如果 小时 > 0 那么:
    返回 连接文本(
      格式化两位数(小时), ":",
      格式化两位数(分钟), ":",
      格式化两位数(秒数)
    )
  否则:
    返回 连接文本(
      格式化两位数(分钟), ":",
      格式化两位数(秒数)
    )

取消倒计时

when 按钮_取消.Click() {
  计时器_倒计时.TimerEnabled = false
  var 剩余秒数 = 0
  标签_倒计时.Text = "00:00"
}

5.2 使用 Duration 方法实现精确倒计时

更精确的做法是记录目标时间点,用 计时器1.Duration() 计算剩余时间。这种方式不依赖累计误差:

初始化全局变量 目标时间 为 空

当 按钮_开始.Click 执行:
  设置 变量 当前时间 为 计时器1.Now()
  设置 变量 倒计时秒数 为 转换为数字(TextBox_Minutes.Text) × 60
  设置 全局变量 目标时间 为 
    计时器1.AddSeconds(当前时间, 倒计时秒数)
  设置 计时器_倒计时.TimerEnabled 为 true
当 计时器_倒计时.Timer 执行:
  设置 变量 当前时间 为 计时器1.Now()
  设置 变量 剩余毫秒 为 计时器1.Duration(当前时间, 目标时间)
  如果 剩余毫秒 <= 0 那么:
    设置 计时器_倒计时.TimerEnabled 为 false
    设置 标签_倒计时.Text 为 "00:00"
    显示提醒对话框("时间到!")
  否则:
    设置 变量 剩余秒数 为 向上取整(剩余毫秒 / 1000)
    设置 标签_倒计时.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 执行:
  设置 变量 新倒计时 为 [建字典]
    设置字典键 "名称" 为 文本输入框_名称.Text
    设置字典键 "剩余秒数" 为 转换为数字(TextBox_Seconds.Text)
    设置字典键 "目标时间" 为 计时器1.AddSeconds(计时器1.Now(), 转换为数字(TextBox_Seconds.Text))
  添加到列表(全局变量 倒计时列表, 新倒计时)
当 Clock_Manager.Timer 执行:
  设置 变量 当前时间 为 计时器1.Now()
  设置 变量 索引 为 1
  循环 当 索引 <= 列表长度(倒计时列表):
    设置 变量 计时项 为 选取列表项(倒计时列表, 索引)
    设置 变量 剩余毫秒 为 计时器1.Duration(当前时间, 获取字典值(计时项, "目标时间"))
    如果 剩余毫秒 <= 0 那么:
      // 倒计时结束,从列表中移除并通知
      设置 变量 名称 为 获取字典值(计时项, "名称")
      显示提醒对话框(连接文本(名称, " 的时间到了!"))
      从列表中移除项(倒计时列表, 索引)
    否则:
      // 更新显示
      设置 变量 剩余秒数 为 向上取整(剩余毫秒 / 1000)
      更新对应 Label 显示
      设置 索引 为 索引 + 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 检测闹钟

当 计时器_闹钟.Timer 执行:
  如果 闹钟已设置 = true 那么:
    设置 变量 当前时间 为 计时器1.Now()
    设置 变量 剩余毫秒 为 计时器1.Duration(当前时间, 闹钟时间瞬间)
    
    如果 剩余毫秒 <= 0 那么:
      // 闹钟触发!
      设置 全局变量 闹钟已设置 为 false
      设置 计时器_闹钟.TimerEnabled 为 false
      调用 Player_Alarm.Start()
      调用 对话框1.ShowAlertDialog("闹钟", "时间到了!", "关闭", 取消假)
    否则:
      // 更新剩余时间显示
      设置 变量 剩余秒数 为 向上取整(剩余毫秒 / 1000)
      设置 变量 小时 为 向下取整(剩余秒数 / 3600)
      设置 变量 分钟 为 向下取整((剩余秒数 模 3600) / 60)
      设置 变量 秒数 为 剩余秒数 模 60
      设置 标签_剩余时间.Text 为 连接文本(
        "距闹钟还有 ", 小时, "时", 分钟, "分", 秒数, "秒"
      )

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 变量定义

初始化全局变量 专注分钟数 为 25
初始化全局变量 短休息分钟数 为 5
初始化全局变量 长休息分钟数 为 15
初始化全局变量 番茄计数 为 0
初始化全局变量 剩余秒数 为 0
初始化全局变量 当前状态 为 "idle"    // idle / focus / break / longbreak
初始化全局变量 是否暂停 为 false
初始化全局变量 目标时间 为 空

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 事件核心逻辑

当 计时器_番茄钟.Timer 执行:
  设置 变量 当前时间 为 计时器1.Now()
  设置 变量 剩余毫秒 为 计时器1.Duration(当前时间, 目标时间)
  
  如果 剩余毫秒 <= 0 那么:
    // 当前阶段结束
    调用 Player_Notify.Start()
    
    如果 当前状态 = "focus" 那么:
      // 专注结束,进入休息
      设置 全局变量 番茄计数 为 番茄计数 + 1
      设置 Label_PomodoroCount.Text 为 连接文本("🍅 ", 番茄计数)
      
      如果 番茄计数 模 4 = 0 那么:
        // 每4个番茄 → 长休息
        设置 全局变量 当前状态 为 "longbreak"
        设置 全局变量 剩余秒数 为 长休息分钟数 × 60
        设置 标签_状态.Text 为 "☕ 长休息中..."
      否则:
        // 短休息
        设置 全局变量 当前状态 为 "break"
        设置 全局变量 剩余秒数 为 短休息分钟数 × 60
        设置 标签_状态.Text 为 "🌿 休息中..."
      
      设置 全局变量 目标时间 为 计时器1.AddSeconds(计时器1.Now(), 剩余秒数)
      设置 Label_Timer.Text 为 秒数转显示文本(剩余秒数)
    
    否则:
      // 休息结束,回到专注
      设置 全局变量 当前状态 为 "focus"
      设置 全局变量 剩余秒数 为 专注分钟数 × 60
      设置 全局变量 目标时间 为 计时器1.AddSeconds(计时器1.Now(), 剩余秒数)
      设置 标签_状态.Text 为 "🍅 专注中..."
      设置 Label_Timer.Text 为 秒数转显示文本(剩余秒数)
  
  否则:
    // 更新倒计时显示
    设置 全局变量 剩余秒数 为 向上取整(剩余毫秒 / 1000)
    设置 Label_Timer.Text 为 秒数转显示文本(剩余秒数)

8.6 跳过按钮

when 按钮_跳过.Click() {
  var 剩余秒数 = 0
  var 目标时间 = 计时器1.Now()
  // 这样下一次 Timer 事件会自动处理阶段切换
}

8.7 重置按钮

当 按钮_复位.Click 执行:
  设置 全局变量 当前状态 为 "idle"
  设置 全局变量 番茄计数 为 0
  设置 全局变量 剩余秒数 为 0
  设置 全局变量 是否暂停 为 false
  设置 计时器_番茄钟.TimerEnabled 为 false
  设置 标签_状态.Text 为 "准备开始"
  设置 Label_Timer.Text 为 连接文本(格式化两位数(专注分钟数), ":00")
  设置 Label_PomodoroCount.Text 为 "🍅 0"
  设置 按钮_开始.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 标签_状态 显示状态文字
Button 按钮_添加考试 添加考试
Button Button_SwitchExam 切换显示不同考试
Clock Clock_Exam 倒计时刷新,Interval = 1000
Notifier 对话框1 输入对话框
DatePicker DatePicker_Exam 选择考试日期
TimePicker TimePicker_Exam 选择考试时间

9.3 变量

初始化全局变量 考试列表 为 [空列表]
初始化全局变量 当前显示索引 为 1

9.4 添加考试

when 按钮_添加考试.Click() {
  对话框1.ShowTextDialog("添加考试", "请输入考试名称", 取消假)
}
当 对话框1.AfterTextInput 执行:
  设置 变量 考试名称 为 response
  设置 变量 考试日期文本 为 DatePicker_Exam.Month & "/" & DatePicker_Exam.DayOfMonth & "/" & DatePicker_Exam.Year
  设置 变量 考试时间文本 为 TimePicker_Exam.Time
  设置 变量 考试时间瞬间 为 计时器1.MakeInstant(连接文本(考试日期文本, " ", 考试时间文本))
  
  设置 变量 新考试 为 [建字典]
    设置字典键 "名称" 为 考试名称
    设置字典键 "时间" 为 考试时间瞬间
  添加到列表(考试列表, 新考试)
  
  设置 Clock_Exam.TimerEnabled 为 true

9.5 刷新倒计时显示

当 Clock_Exam.Timer 执行:
  如果 列表长度(考试列表) = 0 那么:
    设置 标签_倒计时.Text 为 "请添加考试"
    返回
  
  设置 变量 当前考试 为 选取列表项(考试列表, 当前显示索引)
  设置 变量 考试名称 为 获取字典值(当前考试, "名称")
  设置 变量 考试时间 为 获取字典值(当前考试, "时间")
  设置 变量 当前时间 为 计时器1.Now()
  设置 变量 剩余毫秒 为 计时器1.Duration(当前时间, 考试时间)
  
  设置 Label_ExamName.Text 为 考试名称
  
  如果 剩余毫秒 <= 0 那么:
    设置 标签_倒计时.Text 为 "考试进行中!"
    设置 标签_状态.Text 为 "📝 祝考试顺利!"
    设置 标签_倒计时.TextColor 为 红色
  否则:
    设置 变量 总秒数 为 向上取整(剩余毫秒 / 1000)
    设置 变量 天数 为 向下取整(总秒数 / 86400)
    设置 变量 小时 为 向下取整((总秒数 模 86400) / 3600)
    设置 变量 分钟 为 向下取整((总秒数 模 3600) / 60)
    设置 变量 秒数 为 总秒数 模 60
    
    设置 标签_倒计时.Text 为 连接文本(
      天数, "天 ",
      格式化两位数(小时), ":",
      格式化两位数(分钟), ":",
      格式化两位数(秒数)
    )
    
    // 根据时间紧迫程度显示不同状态
    如果 天数 > 7 那么:
      设置 标签_状态.Text 为 "📅 还有时间,好好准备"
    否则 如果 天数 > 1 那么:
      设置 标签_状态.Text 为 "⏰ 即将考试,加油复习!"
    否则:
      设置 标签_状态.Text 为 "🔥 明天考试,全力以赴!"

9.6 切换考试

当 Button_SwitchExam.Click 执行:
  如果 列表长度(考试列表) > 1 那么:
    设置 全局变量 当前显示索引 为 (当前显示索引 模 列表长度(考试列表)) + 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 的毫秒数。如果 endstart 之后,结果为正数。

10.5 倒计时精度优化

如果需要非常精确的倒计时(如体育比赛计时),建议:

  1. 使用 计时器1.SystemTime() 而非简单的计数器递减
  2. TimerInterval 设置为 100ms 或更小
  3. 记录开始和结束的时间瞬间,始终用 Duration 计算差值
  4. 显示更新时取整到需要的精度

十一、常见问题

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: 核心代码:

设置 变量 提醒时间 为 计时器1.AddMinutes(计时器1.Now(), X)
设置 计时器1.TimerEnabled 为 true

// 在 Timer 事件中:
如果 计时器1.Duration(计时器1.Now(), 提醒时间) <= 0 那么:
  播放提醒音
  设置 计时器1.TimerEnabled 为 false

十二、总结

Clock 组件是 App Inventor 2 中处理时间相关功能的核心组件。通过本文介绍的技巧,你可以实现:

功能 核心方法
实时时钟 Timer 事件 + FormatDateTime()
秒表(正计时) SystemTime() 差值累计
倒计时 Duration() 计算剩余时间
多计时器 列表 + 字典管理
闹钟 MakeInstant() + Duration() 比较
番茄钟 状态机 + 自动阶段切换
考试倒计时 Duration() + 天/时/分/秒拆分

关键原则:

  1. 始终基于真实时间计算,而非依赖累计计数
  2. 使用 Duration() 而非手动递减,确保精度
  3. 在 Screen.Pause/Resume 中保存和恢复状态
  4. 选择合适的 TimerInterval,平衡精度和电量

参考资料

版权声明

本文档基于 MIT App Inventor 官方文档及社区资源整理,版权归原作者所有:

  • MIT App Inventor 官方文档采用 CC BY-SA 4.0 授权
  • MIT App Inventor Community 帖子版权归原作者所有

本文档由 ai2claw 🐝 整理,仅供学习参考,如有侵权请联系删除。

文档反馈