4、页面切换动画:SlideTransition、FadeTransition、自定义Transition实现
页面切换动画,说白了就是让用户觉得「嗯,这界面挺跟手」。双缓冲只是给了你一块干净的画布,怎么在画布上「演」出流畅的转场,就得靠 Transition 了。TouchGFX 内置了几种常用的切换动画,也允许你自己写。我个人习惯是,能用内置的就用内置的,实在满足不了需求再自己造轮子。
4.1 内置 Transition 概览
TouchGFX 的 ScreenManager 里预置了三种 Transition:
| Transition 类型 | 类名 | 效果描述 | 适用场景 |
|---|---|---|---|
| 滑动切换 | SlideTransition | 新页面从指定方向滑入,旧页面被推出 | 列表页 → 详情页、设置页切换 |
| 淡入淡出 | FadeTransition | 旧页面逐渐透明,新页面逐渐显现 | 弹窗、全屏图片浏览、模式切换 |
| 无动画 | NoTransition | 瞬间切换,无任何过渡 | 调试、低性能平台、启动页 |
你可能会问:「就这三种?够用吗?」其实大部分场景都够了。我在一个工业手持设备项目里,90% 的页面切换只用 SlideTransition 和 FadeTransition 就搞定了。剩下的 10%,嗯,就是自定义 Transition 的用武之地。
4.2 SlideTransition 详解
SlideTransition 是最常用的。它的核心逻辑是:新页面从屏幕外「滑」进来,旧页面被「推」出去。你可以控制滑入的方向。
关键点:SlideTransition 的滑动方向由 SlideTransition::Direction 枚举控制,支持 LEFT、RIGHT、UP、DOWN。默认是 LEFT(新页面从右侧滑入)。
看一段实际代码:
// 在 Screen1 中切换到 Screen2,使用 SlideTransition 从右侧滑入
Screen2& screen2 = screenManager->getNextScreen<Screen2>();
screenManager->setTransition(&screen2, SlideTransition(LEFT, Duration(300)));
screenManager->switchScreen();
这里有个细节:Duration(300) 表示动画持续 300 毫秒。我建议不要低于 200ms,否则动画太生硬;也不要超过 500ms,用户会觉得卡顿。300ms 是个黄金值,我几乎所有项目都用这个值。
小技巧:如果你想让旧页面「不动」,新页面「覆盖」进来,可以用 SlideTransition(LEFT, Duration(300), false)。第三个参数 moveOld 设为 false,旧页面就会停在原地,只有新页面滑动。适合做「抽屉式」菜单。
4.3 FadeTransition 详解
FadeTransition 就简单多了——透明度从 0 到 1(新页面),从 1 到 0(旧页面)。它不涉及位置变化,所以性能开销比 SlideTransition 小一点。
// 使用 FadeTransition 切换到 Screen3
Screen3& screen3 = screenManager->getNextScreen<Screen3>();
screenManager->setTransition(&screen3, FadeTransition(Duration(400)));
screenManager->switchScreen();
我曾经在一个医疗设备项目里,所有弹窗都用 FadeTransition。为什么?因为弹窗从屏幕中间出现,滑动反而显得突兀。淡入淡出更符合「出现/消失」的直觉。
注意:FadeTransition 在双缓冲模式下,如果帧率低于 30fps,会出现明显的「闪烁」现象。这是因为透明度变化需要频繁刷新缓冲区。如果你的平台性能较弱,建议把 Duration 拉长到 500ms 以上,或者改用 SlideTransition。
4.4 自定义 Transition 实现
内置的 Transition 满足不了你?那就自己写。自定义 Transition 的核心是继承 Transition 基类,重写 tick() 方法。
先看一个最简单的自定义 Transition——缩放切换:
class ZoomTransition : public Transition {
public:
ZoomTransition(uint16_t duration)
: Transition(duration) {}
virtual void tick() override {
// 计算当前进度,范围 0.0 ~ 1.0
float progress = getCurrentTick() / (float)getDuration();
// 新页面从 0.5 倍缩放到 1.0 倍
float newScale = 0.5f + 0.5f * progress;
getNewScreen()->setScale(newScale);
// 旧页面从 1.0 倍缩放到 0.5 倍
float oldScale = 1.0f - 0.5f * progress;
getOldScreen()->setScale(oldScale);
// 透明度也一起变化
getNewScreen()->setAlpha((uint8_t)(255 * progress));
getOldScreen()->setAlpha((uint8_t)(255 * (1.0f - progress)));
}
};
使用方式跟内置的一样:
screenManager->setTransition(&targetScreen, ZoomTransition(400));
screenManager->switchScreen();
你看,核心就是 tick() 方法。TouchGFX 每帧都会调用它,你只需要根据进度(0~1)去修改新页面和旧页面的属性就行。支持修改的属性包括:位置、缩放、旋转、透明度、颜色等。
核心原则:自定义 Transition 里,getNewScreen() 和 getOldScreen() 分别代表新页面和旧页面。你可以在 tick() 里任意修改它们的属性,TouchGFX 会自动处理双缓冲的刷新。
4.5 自定义 Transition 的避坑指南
我曾经踩过一个坑:在自定义 Transition 里直接调用了 invalidate()。结果动画卡成 PPT。为什么?因为 tick() 每帧调用,你再手动触发重绘,等于让双缓冲机制「打架」。正确的做法是:只修改属性,不主动调用重绘函数,TouchGFX 的渲染引擎会自动处理。
还有一点:自定义 Transition 里不要做耗时操作,比如文件读写、网络请求。动画是每帧执行的,你卡一帧,用户就能感觉到掉帧。我一般会在 Transition 开始前就把数据准备好,Transition 只负责「演」。
| 常见错误 | 后果 | 正确做法 |
|---|---|---|
| 在 tick() 里调用 invalidate() | 双缓冲冲突,动画卡顿 | 只修改属性,不主动重绘 |
| 在 tick() 里做耗时计算 | 帧率下降,动画掉帧 | 预计算,tick() 只做赋值 |
| 忘记处理进度溢出 | 动画结束后属性异常 | 用 clamp 限制 progress 在 0~1 |
| 修改了屏幕外的 Widget | 无效果,浪费性能 | 只修改可见 Widget |
4.6 知识体系总览
下面这张图把本章的核心逻辑串起来了。你可以看到,Transition 的底层依赖双缓冲,上层对接 ScreenManager。内置的 Slide 和 Fade 覆盖了大部分场景,自定义 Transition 则提供了无限可能。
4.7 实战建议
最后,给你几个我在项目中沉淀下来的建议:
- 优先用内置 Transition——Slide 和 Fade 已经覆盖了 80% 的场景,而且经过 TouchGFX 团队优化,性能有保障。
- 自定义 Transition 要「轻」——tick() 里只做属性赋值,任何计算都提前做好。我见过有人在 tick() 里算三角函数,结果动画直接掉到 20fps。
- 测试要覆盖低帧率场景——你的开发板可能跑 60fps,但客户的产品可能只有 30fps。在低帧率下测试 Transition,确保不掉帧、不闪烁。
- 动画时长要统一——我习惯把项目中所有 Transition 的 Duration 统一成 300ms。用户会觉得「这个系统很一致」,而不是「这个页面快,那个页面慢」。
嗯,Transition 这部分就讲到这里。记住,动画是给用户看的,不是给你自己炫技的。简洁、流畅、一致,比花里胡哨更重要。