TextTimer 组件深入知识库
文档定位:本文档是对
texttimer/CLAUDE.md的深入补充,专注于设计决策、实现原理、问题排查等高级内容 快速参考:请参考 TextTimer/CLAUDE.md 版本:v3.0 | 更新:2026-02-03
目录
1. 设计决策分析
1.1 为什么 TextTimer 使用 Text 子节点而不是直接绘制?
背景:TextTimer 需要显示格式化的计时文本(如 "01:23:45.67")
决策:TextTimer 内部创建一个 Text 节点作为子节点来显示计时值
源码位置:text_timer_model_ng.cpp:27-55
// 创建 Text 子节点用于计时显示
auto textId = textTimerPattern->GetTextId();
auto textNode = FrameNode::GetOrCreateFrameNode(
V2::TEXT_ETS_TAG, textId,
[]() { return AceType::MakeRefPtr<TextPattern>(); });
textNode->MountToParent(textTimerNode);
设计理由:
| 优点 | 说明 |
|---|---|
| 复用 Text 能力 | 自动获得文字测量、渲染、样式支持(字体、颜色、阴影等) |
| 时间格式化 | 复用 Localization::FormatDuration() 时间格式化能力 |
| 主题适配 | 继承 Text 的主题系统(字体颜色、大小、粗细等) |
| 无障碍支持 | 复用 Text 的 Accessibility 实现,屏幕阅读器可直接读出计时值 |
| 维护成本低 | Text 组件的优化自动惠及 TextTimer |
替代方案考虑:
| 方案 | 优点 | 缺点 | 未选择原因 |
|---|---|---|---|
| 直接绘制(Rosen Canvas) | 性能略好,控制更精细 | 需自己实现文字测量、渲染、无障碍 | 维护成本高 |
| 使用 Image 节点 | 灵活,支持任意图形 | 不支持动态内容,需要每帧预渲染 | 性能开销大 |
| 使用 RichEditor | 富文本支持 | 过于重量级,不必要的复杂度 | 大材小用 |
权衡结论:选择 Text 子节点方案,虽然轻微性能损耗,但大幅降低复杂度和维护成本
1.2 为什么使用 Scheduler 而不是 setTimeout/setInterval?
背景:TextTimer 需要周期性更新计时显示(约每 16ms 触发一次)
决策:使用 ACE 框架的 Scheduler 组件
源码位置:text_timer_pattern.cpp:83-107
void TextTimerPattern::InitTimerDisplay()
{
auto weak = AceType::WeakClaim(this);
auto&& callback = [weak](uint64_t duration) {
auto timer = weak.Upgrade();
if (timer) {
timer->Tick(duration); // 周期性回调
}
};
auto context = host->GetContextRefPtr();
scheduler_ = SchedulerBuilder::Build(callback, context);
}
设计理由:
┌─────────────────────────────────────────────────────────┐
│ Web 风格 (setTimeout/setInterval) │
│ - 基于 V8 消息循环 │
│ - 时间精度受 JS 执行影响 │
│ - 无法感知组件可见性 │
│ - 与 UI 渲染帧不同步 │
└─────────────────────────────────────────────────────────┘
vs
┌─────────────────────────────────────────────────────────┐
│ ACE Scheduler │
│ - 与 UI 渲染帧同步 │
│ - 可感知可见性变化 │
│ - 支持暂停/恢复 │
│ - 时间精度更高 │
└─────────────────────────────────────────────────────────┘
Scheduler 的优势:
| 特性 | 说明 | 好处 |
|---|---|---|
| 帧同步 | Scheduler 与 VSync 信号同步 | 避免不必要的中间帧渲染 |
| 可见性感知 | 组件不可见时自动暂停 | 节省 CPU 和电池 |
| 统一管理 | 与框架内其他定时任务统一调度 | 更好的资源调度 |
替代方案考虑:
| 方案 | 优点 | 缺点 | 未选择原因 |
|---|---|---|---|
| setTimeout/setInterval | 简单,Web 标准 | 与渲染帧不同步,精度低 | 性能和体验不佳 |
| C++ std::chrono + thread | 精度高 | 跨线程复杂性,无法感知可见性 | 架构不匹配 |
| Rosen 动画回调 | 与渲染最同步 | 依赖渲染引擎,耦合度高 | 抽象层次不当 |
权衡结论:使用 Scheduler 是 NG 组件的标准做法,在性能、可维护性和架构一致性上最优
1.3 为什么倒计时结束时要自动暂停?
源码位置:text_timer_pattern.cpp:109-127
void TextTimerPattern::Tick(uint64_t duration)
{
// ... 计算剩余时间 ...
// 倒计时结束处理
if (isCountDown_ && tmpValue <= 0) {
UpdateTextTimer(0);
HandlePause(); // 自动暂停
return;
}
}
设计理由:
| 场景 | 用户预期 | TextTimer 行为 |
|---|---|---|
| 倒计时到 0 | 停止计时,显示 00:00:00 | 自动调用 HandlePause(),停止调度器 |
| 正计时 | 持续计时直到用户暂停 | 持续运行 |
自动暂停的必要性:
- 资源节约:避免无意义的计时计算
- 状态明确:倒计时结束时状态应该是"已停止"
- 用户体验:符合用户对倒计时的直觉预期
设计权衡:
// 选项1:循环倒计时(未采用)
if (isCountDown_ && tmpValue <= 0) {
elapsedTime_ = 0; // 重置并循环
// 继续计时
}
// 选项2:一次性倒计时(采用)✅
if (isCountDown_ && tmpValue <= 0) {
UpdateTextTimer(0);
HandlePause(); // 停止
return;
}
1.4 为什么支持 ContentModifier?
背景:ContentModifier 是 ArkUI 的高级特性,允许开发者完全自定义组件内容
决策:TextTimer 支持 ContentModifier 模式
源码位置:text_timer_pattern.cpp:360-409
void TextTimerPattern::FireBuilder()
{
if (makeFunc_ && contentModifierNode_) {
// 构建 ContentModifier 内容
makeFunc_(contentModifierNode_); // 用户提供的构建函数
}
}
设计理由:
| 使用场景 | 示例 |
|---|---|
| 自定义样式 | 数字时钟、环形进度条、渐变色计时器 |
| 特殊效果 | 翻页动画、缩放效果、粒子效果 |
| 复杂布局 | 计时器 + 控制按钮组合显示 |
ContentModifier 与标准模式对比:
标准模式:
TextTimer → Text子节点 → 显示 "01:23:45.67"
ContentModifier模式:
TextTimer → 用户自定义 UI → 任意复杂界面
权衡考虑:
| 因素 | 标准模式 | ContentModifier |
|---|---|---|
| 复杂度 | 低 | 高 |
| 灵活性 | 中 | 极高 |
| 性能 | 优 | 略低 |
| 学习曲线 | 平缓 | 陡峭 |
2. 实现原理深挖
2.1 Scheduler 工作原理
Scheduler 创建流程:
┌─────────────────────────────────────────────────────────┐
│ 1. 获取 UI 上下文 │
│ │
│ auto context = host->GetContextRefPtr(); │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ 2. 创建回调闭包 │
│ │
│ auto weak = AceType::WeakClaim(this); │
│ auto callback = [weak](uint64_t duration) { │
│ if (auto timer = weak.Upgrade()) { │
│ timer->Tick(duration); │
│ } │
│ }; │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ 3. 构建 Scheduler │
│ │
│ scheduler_ = SchedulerBuilder::Build(callback, │
│ context);│
└─────────────────────────────────────────────────────────┘
Scheduler 控制流程:
| 方法 | 功能 | 源码位置 |
|---|---|---|
scheduler_->Start() |
启动定时触发 | text_timer_pattern.cpp:290 |
scheduler_->Stop() |
停止定时触发 | text_timer_pattern.cpp:297 |
scheduler_->IsActive() |
检查是否正在运行 | text_timer_pattern.cpp:291 |
时间精度:
Scheduler 触发间隔:约 16ms(60 FPS)
实际精度:取决于 VSync 信号和主线程负载
2.2 时间计算与格式化
正计时模式:
// 累加逝去时间
elapsedTime_ += duration;
// 显示值 = 累加时间
uint32_t displayValue = static_cast<uint32_t>(elapsedTime_);
倒计时模式:
// 计算剩余时间
double remainingTime = inputCount_ - static_cast<double>(elapsedTime_);
// 确保不小于0
if (remainingTime <= 0) {
remainingTime = 0;
HandlePause(); // 倒计时结束
}
uint32_t displayValue = static_cast<uint32_t>(remainingTime);
时间格式化:
void TextTimerPattern::UpdateTextTimer(uint32_t elapsedTime)
{
// 使用 Localization 格式化
std::string timerText = Localization::GetInstance()->FormatDuration(
elapsedTime, // 毫秒数
GetFormat() // 格式字符串,如 "HH:mm:ss.SS"
);
// 格式化失败时使用默认格式
if (timerText.empty()) {
timerText = Localization::GetInstance()->FormatDuration(
elapsedTime,
DEFAULT_FORMAT // "HH:mm:ss.SS"
);
}
// 更新 Text 子节点内容
textLayoutProperty->UpdateContent(timerText);
}
格式字符对照:
| 字符 | 含义 | 示例值 |
|---|---|---|
H |
小时(带前导零) | 01, 12 |
h |
小时(不带前导零) | 1, 12 |
m |
分钟 | 00-59 |
s |
秒 | 00-59 |
S |
厘秒(2位) | 00-99 |
SS |
厘秒(2位) | 00-99 |
SSS |
毫秒(3位) | 000-999 |
2.3 控制器回调机制
控制器初始化流程:
┌─────────────────────────────────────────────────────────┐
│ 1. 检查控制器是否已初始化 │
│ │
│ if (textTimerController_->HasInitialized()) { │
│ return; // 避免重复注册 │
│ } │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ 2. 注册 Start 回调 │
│ │
│ textTimerController_->OnStart([weak]() { │
│ if (auto timer = weak.Upgrade()) { │
│ timer->HandleStart(); // 启动调度器 │
│ } │
│ }); │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ 3. 注册 Pause 回调 │
│ │
│ textTimerController_->OnPause([weak]() { │
│ if (auto timer = weak.Upgrade()) { │
│ timer->HandlePause(); // 停止调度器 │
│ } │
│ }); │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ 4. 注册 Reset 回调 │
│ │
│ textTimerController_->OnReset([weak]() { │
│ if (auto timer = weak.Upgrade()) { │
│ timer->HandleReset(); // 重置计时 │
│ } │
│ }); │
└─────────────────────────────────────────────────────────┘
HandleStart 实现:
void TextTimerPattern::HandleStart()
{
if (scheduler_ && !scheduler_->IsActive()) {
scheduler_->Start(); // 启动调度器
}
}
HandlePause 实现:
void TextTimerPattern::HandlePause()
{
if (scheduler_ && scheduler_->IsActive()) {
scheduler_->Stop(); // 停止调度器
}
}
HandleReset 实现:
void TextTimerPattern::HandleReset()
{
if (scheduler_ && scheduler_->IsActive()) {
scheduler_->Stop(); // 先停止
}
elapsedTime_ = 0; // 重置累加时间
// 重新显示初始值
auto count = isCountDown_ ? inputCount_ : 0;
UpdateTextTimer(static_cast<uint32_t>(count));
}
2.4 可见区域优化
可见区域变化处理:
源码位置:text_timer_pattern.cpp:204-242
void TextTimerPattern::OnVisibleAreaChange(bool visible)
{
auto host = GetHost();
CHECK_NULL_VOID(host);
if (visible) {
// 可见:添加 Text 子节点
if (!childNode) {
host->AddChild(textNode_);
host->RebuildRenderContextTree();
}
} else {
// 不可见:移除 Text 子节点
if (childNode) {
host->RemoveChild(textNode_);
host->RebuildRenderContextTree();
}
}
}
优化效果:
| 场景 | 优化前 | 优化后 |
|---|---|---|
| 滚动出屏幕 | 继续渲染 Text 子节点 | 移除 Text 子节点,节省渲染资源 |
| 应用进入后台 | 继续计时和渲染 | 可选择性暂停(实现依赖) |
| 被其他组件遮挡 | 继续渲染 | 可见性变化时优化 |
2.5 主题更新机制
主题变化响应:
源码位置:text_timer_pattern.cpp:504-533
void TextTimerPattern::OnColorConfigurationUpdate()
{
auto layoutProperty = GetLayoutProperty<TextTimerLayoutProperty>();
auto textLayoutProperty = textNode_->GetLayoutProperty<TextLayoutProperty>();
auto textTheme = themeManager->GetTheme<TextTheme>();
// 检查用户是否设置了颜色
if (!layoutProperty->GetTextColorSetByUser().value_or(false)) {
// 未设置,使用主题值
textLayoutProperty->UpdateTextColor(textTheme->GetTextColor());
}
// 否则,保留用户设置
// 类似处理字体大小、粗细、字体族
if (!layoutProperty->GetTextFontSizeSetByUser().value_or(false)) {
textLayoutProperty->UpdateFontSize(textTheme->GetFontSize());
}
// ...
}
用户设置标志:
| 标志 | 含义 | 作用 |
|---|---|---|
TextColorSetByUser |
用户显式设置了文字颜色 | 防止主题更新覆盖 |
TextFontSizeSetByUser |
用户显式设置了字体大小 | 防止主题更新覆盖 |
TextFontWeightSetByUser |
用户显式设置了字体粗细 | 防止主题更新覆盖 |
TextFontFamilySetByUser |
用户显式设置了字体族 | 防止主题更新覆盖 |
设置流程:
ArkTS: TextTimer().fontColor(Color.Red)
↓
TextTimerModelNG::SetTextColor()
↓
textLayoutProperty->UpdateTextColor(Color.RED)
↓
textLayoutProperty->UpdateTextColorSetByUser(true) // 设置标志
↓
后续主题变化时保留用户设置的红色
3. 问题排查手册
3.1 计时器不更新
症状:调用 controller.start() 后,计时值不变化
诊断流程:
开始
↓
计时器是否显示?
├─ 否 → 检查是否调用了 controller.start()
│ ├─ 未调用 → 调用 controller.start()
│ └─ 已调用 → 检查 Scheduler 是否初始化
│ ├─ 未初始化 → 检查 OnModifyDone 是否执行
│ └─ 已初始化 → 检查 IsActive() 状态
│
├─ 显示但不更新 → 检查 Tick() 是否被调用
│ └─ 未调用 → 检查 Scheduler 回调注册
│
└─ 更新但显示异常 → 检查格式化逻辑
常见原因与解决方案:
| 原因 | 现象 | 解决方案 |
|---|---|---|
未调用 start() |
显示初始值不变 | 确保调用 controller.start() |
| 组件未挂载到树 | 没有任何显示 | 确保 TextTimer 已添加到组件树 |
| Scheduler 未初始化 | 不更新 | 检查 OnModifyDone 执行流程 |
| 格式字符串错误 | 显示空或乱码 | 验证 format 格式 |
调试代码:
@Entry
@Component
struct DebugTextTimer {
@State elapsedTime: number = 0
controller: TextTimerController = new TextTimerController()
build() {
Column({ space: 10 }) {
// 显示状态
Text(`Elapsed: ${this.elapsedTime}ms`)
// TextTimer
TextTimer({ controller: this.controller })
.format('HH:mm:ss.SS')
.onTimer((utc: number, elapsedTime: number) => {
this.elapsedTime = utc
console.log(`[TextTimer Debug] utc=${utc}, elapsedTime=${elapsedTime}`)
})
// 控制按钮
Row({ space: 10 }) {
Button('Start').onClick(() => {
this.controller.start()
console.log('[TextTimer Debug] Start called')
})
Button('Pause').onClick(() => {
this.controller.pause()
console.log('[TextTimer Debug] Pause called')
})
Button('Reset').onClick(() => {
this.controller.reset()
console.log('[TextTimer Debug] Reset called')
})
}
}
}
}
3.2 倒计时不准确
症状:倒计时的剩余时间与预期不符
可能原因:
| 原因 | 说明 | 解决方案 |
|---|---|---|
| InputCount 单位错误 | 应为毫秒,可能传入了秒 | 确保 count 单位是毫秒 |
| 格式显示问题 | 显示精度与实际值不一致 | 检查 format 字符串 |
| 系统时间跳变 | 系统时间调整导致 elapsedTime 异常 | Scheduler 基于相对时间,通常不受影响 |
验证代码:
@Entry
@Component
struct CountdownDebug {
controller: TextTimerController = new TextTimerController()
build() {
Column() {
// 60秒倒计时
TextTimer({
isCountDown: true,
count: 60000, // 60000毫秒 = 60秒
controller: this.controller
})
.format('mm:ss') // 显示为 "01:00"
.fontSize(48)
.onTimer((utc: number, elapsedTime: number) => {
console.log(`Remaining: ${elapsedTime}ms = ${Math.floor(elapsedTime/1000)}s`)
})
}
}
}
3.3 主题更新后样式未变化
原因:用户设置了 *SetByUser 标志
检查方法:
// C++ 层检查
auto layoutProperty = frameNode->GetLayoutProperty<TextTimerLayoutProperty>();
bool isUserColor = layoutProperty->GetTextColorSetByUser().value_or(false);
TAG_LOGD(ACE_TEXTTIMER, "TextColorSetByUser: %d", isUserColor);
解决方案:
// 方案1:移除显式颜色设置,使用主题
TextTimer({ controller: this.controller })
.format('HH:mm:ss.SS')
// 不调用 .fontColor(),使用主题默认颜色
// 方案2:响应主题变化
@State timerColor: ResourceColor = $r('app.color.timer_text_color')
TextTimer({ controller: this.controller })
.format('HH:mm:ss.SS')
.fontColor(this.timerColor) // 使用资源引用
3.4 性能问题:大量 TextTimer 导致卡顿
场景:列表中有 100+ 个 TextTimer
优化策略:
- 条件渲染:
// 仅在需要时显示 TextTimer
if (this.showTimer) {
TextTimer({ controller: this.controller })
.format('HH:mm:ss.SS')
} else {
Text('00:00:00.00') // 静态文本替代
}
- 使用 LazyForEach:
List() {
LazyForEach(this.dataSource, (item: TimerItem) => {
ListItem() {
TextTimer({ controller: item.controller })
.format('mm:ss')
}
})
}
- 控制更新频率:
// 使用较高的显示精度
TextTimer()
.format('HH:mm:ss') // 不显示毫秒,减少触发频率
4. 跨组件交互
4.1 TextTimer 与 Text 的关系
依赖:TextTimer 包含 Text 节点
TextTimer FrameNode
├── Text FrameNode (内部创建,用于显示计时值)
└── ContentModifier FrameNode (可选,用户自定义)
数据流向:
ArkTS: TextTimer({ isCountDown: true, count: 60000 })
↓
TextTimerModelNG::Create()
↓
TextTimerPattern::OnModifyDone()
↓
创建内部 Text 节点
└── UpdateContent("01:00") // 倒计时格式化
└── UpdateTextColor(white)
└── UpdateFontSize(24vp)
注意事项:
- TextTimer 内部的 Text 节点 ID 存储在
TextTimerPattern::GetTextId() - 更新 TextTimer 时实际是更新内部 Text 的
content属性 - 不要直接访问 TextTimer 内部的 Text 节点(实现细节可能变化)
4.2 TextTimer 与 Scheduler 的交互
Scheduler 生命周期:
TextTimer 创建
↓
InitTimerDisplay()
├── 创建 Scheduler
└── 注册 Tick 回调
↓
controller.start()
↓
scheduler_->Start()
↓
[周期性触发] → Tick(duration)
↓
controller.pause() / 倒计时结束
↓
scheduler_->Stop()
时间累计机制:
void TextTimerPattern::Tick(uint64_t duration)
{
// duration 是自上次 Tick 以来经过的时间(毫秒)
elapsedTime_ += duration; // 累加
// elapsedTime_ 是总累计时间,不受暂停影响
// 暂停时不调用 Tick,elapsedTime_ 不会增加
}
4.3 TextTimer 与 TimeClock 的对比
| 特性 | TextTimer | TimeClock |
|---|---|---|
| 用途 | 计时器(秒表/倒计时) | 时钟(显示当前时间) |
| 时间源 | Scheduler (累加) | 系统 UTC 时间 |
| 控制 | Start/Pause/Reset | 自动跟随系统时间 |
| 时区 | 不涉及 | 支持时区设置 |
| 格式化 | Localization::FormatDuration() | Localization::FormatTime() |
| 精度 | 毫秒级 | 秒级 |
5. 性能优化
5.1 渲染性能
TextTimer 渲染路径:
TextTimer FrameNode
↓
Text 子节点(内容)
↓
TextLayoutProperty::UpdateContent()
↓
RenderContext 绘制
↓
Rosen/Skia 渲染
优化建议:
| 操作 | 性能影响 | 优化建议 |
|---|---|---|
| 创建 TextTimer | 低(~1ms) | 正常使用 |
| 每次 Tick 更新 | 低(~0.5ms) | 减少格式化复杂度 |
| 测量布局 | 中(~2ms) | 使用固定字体大小 |
| 绘制渲染 | 低(~1ms) | 避免频繁改变样式 |
5.2 内存占用
单个 TextTimer 内存:
| 组件 | 内存占用 |
|---|---|
| TextTimer FrameNode | ~1.2 KB |
| 内部 Text FrameNode | ~0.5 KB |
| LayoutProperty | ~0.3 KB |
| Scheduler | ~0.4 KB |
| TextTimerController | ~0.2 KB |
| 总计 | ~2.6 KB |
100 个 TextTimer:~260 KB
优化建议:
- 不需要时条件渲染
- 使用对象池(适用于频繁创建销毁场景)
- 不可见时自动移除 Text 子节点
5.3 调度优化
Scheduler 触发频率:
标准频率:约 16ms(60 FPS)
优化方案:根据 format 调整触发频率
触发频率优化(理论):
| Format | 精度 | 建议触发频率 |
|---|---|---|
| "HH:mm:ss" | 秒 | 1000ms(1秒) |
| "HH:mm:ss.S" | 厘秒 | 10ms |
| "HH:mm:ss.SS" | 厘秒 | 10ms |
| "HH:mm:ss.SSS" | 毫秒 | 1ms |
注意:当前实现统一使用 Scheduler 的默认频率(约 16ms)
附录
相关文件清单
| 文件 | 行数 | 主要功能 |
|---|---|---|
text_timer_pattern.cpp |
~430 | Pattern 核心逻辑 |
text_timer_model_ng.cpp |
~150 | Model 层 |
text_timer_layout_property.h |
~90 | 属性定义 |
texttimer_controller.h |
~50 | 控制器接口 |
调试命令
# 查看 TextTimer 组件信息
hidumper -s WindowManagerService -a '-a TextTimer'
# 查看组件树
hidumper -s WindowManagerService -a '-a ComponentTree'
# 查看日志
hilog -T ACE_TEXTTIMER
参考文档
- TextTimer/CLAUDE.md - 快速参考
- Text_Knowledge_Base_CN.md - Text 组件深入(TextTimer 内部使用 Text)
- text_theme.h - 主题定义
- Scheduler 源码 - 调度器实现
文档版本历史
| 版本 | 日期 | 更新内容 |
|---|---|---|
| v3.0 | 2026-02-03 | 重组为深度知识库:设计决策、实现原理、问题排查、跨组件交互、性能优化 |
| v2.0 | 2026-02-03 | 深度扩充版 |
| v1.0 | 2026-02-02 | 初始版本 |