1. 动画基础:TouchGFX动画引擎概述、帧率与刷新机制、动画生命周期管理
各位好,欢迎来到《TouchGFX动画效果实战技巧》的第一章。咱们今天不聊虚的,直接切入正题——动画引擎到底是怎么工作的?
我刚开始接触TouchGFX的时候,其实也踩过不少坑。那时候总觉得动画不就是让控件动起来嘛,结果一跑起来掉帧、卡顿、内存泄漏全来了。后来我才明白,搞懂动画引擎的底层逻辑,比背一百个API都管用。
1.1 TouchGFX动画引擎概述
TouchGFX的动画引擎,说白了就是一个专门处理UI元素位置、大小、透明度、颜色等属性随时间变化的调度器。它不依赖操作系统的定时器,而是自己维护了一套高精度的帧调度系统。
我个人习惯把动画引擎拆成三个核心模块来看:
- 属性插值器:负责计算每一帧的属性中间值
- 时间控制器:管理动画的启动、暂停、停止和循环
- 渲染触发器:在每一帧通知UI系统重新绘制
你想想看,这三个模块配合好了,动画才能丝滑。我在一个智能家居面板项目里遇到过,就是因为时间控制器没处理好,导致动画在低电量模式下直接跳帧——嗯,后来加了个帧率自适应逻辑才解决。
核心要点:TouchGFX动画引擎运行在UI线程中,所有动画更新都在主循环的 tick() 函数里完成。这意味着动画不能做耗时操作,否则会阻塞渲染。
1.2 帧率与刷新机制
帧率这东西,很多初学者觉得越高越好。其实不然。TouchGFX默认的帧率是60fps,也就是每16.67ms刷新一次。但你的硬件能不能撑住60fps,这才是关键。
我曾经在一个Cortex-M7的板子上跑动画,满屏都是波形图,结果帧率直接掉到30fps。后来一查,是DMA2D没开,所有像素都是CPU硬刷的。
| 帧率 | 每帧时间 | 适用场景 | 硬件要求 |
|---|---|---|---|
| 60 fps | 16.67 ms | 流畅UI、滑动列表 | 中高端MCU + DMA2D |
| 30 fps | 33.33 ms | 静态页面切换、简单动画 | 入门级MCU |
| 15 fps | 66.67 ms | 低功耗待机界面 | 极低功耗MCU |
TouchGFX的刷新机制其实分两层:
- 软件帧率:由
Application::setFrameRate()控制,决定每秒调用多少次 tick() - 硬件刷新:由LTDC或DSI接口驱动,决定屏幕实际刷新次数
这里有个坑——软件帧率不能超过硬件刷新率,否则会造成帧缓冲撕裂。我建议你保持两者一致,或者软件帧率略低于硬件刷新率。
注意:不要在主循环里用 HAL_Delay() 或 osDelay() 来控制动画节奏。TouchGFX有自己的时间基准,外部延时会导致动画时间轴错乱。
1.3 动画生命周期管理
每个动画从创建到销毁,都会经历几个阶段。我习惯用状态机来理解它:
- 创建:实例化动画对象,设置起始值和结束值
- 启动:调用
start(),动画加入调度队列 - 运行:每一帧更新插值,触发回调
- 暂停/恢复:临时停止或继续动画
- 结束:到达终点值,触发完成回调
- 销毁:释放动画对象资源
下面这张图是我自己总结的动画生命周期流程,你可以对照着看:
在实际编码中,我建议你这样管理动画生命周期:
// 创建动画
MoveAnimator<Image> anim;
anim.setTarget(image);
anim.setDuration(300); // 300ms
anim.setDelay(0);
anim.setEasing(EasingEquations::cubicEaseInOut);
// 启动
anim.start();
// 监听结束
anim.setAnimationEndedCallback([this](const MoveAnimator<Image>& a) {
// 动画结束后的处理
// 我曾经在这里忘记释放资源,导致内存泄漏
// 所以现在习惯在回调里做清理
this->onAnimationFinished();
});
小技巧:如果你需要循环播放动画,不要手动在结束回调里重新 start()。TouchGFX提供了 setRepeatCount() 和 setRepeatMode(),用它们更安全,也更容易管理。
关于生命周期,还有一个容易被忽略的点——动画对象的生命周期必须长于动画执行时间。如果你在栈上创建动画对象,函数返回后对象就销毁了,但动画还在调度队列里,这时候就会触发野指针。我建议所有动画对象都用成员变量或者堆分配。
好了,这一章的内容就到这里。动画引擎的框架搭好了,后面咱们才能玩出花来。记住:帧率是基础,生命周期是保障,理解这两点,你的动画就成功了一半。