4、屏幕与导航:Screen概念、Screen生命周期、Screen切换动画、导航管理器

屏幕与导航,是嵌入式GUI里最基础也最容易踩坑的部分。我刚开始用TouchGFX做项目时,以为屏幕就是简单的页面跳转,结果在切换动画上栽了好几次跟头。今天咱们就把这块彻底捋清楚。

4.1 Screen到底是什么?

在TouchGFX里,Screen不是简单的“一页UI”。它是一整套资源的集合:

  • 一个View类(负责界面绘制和交互)
  • 一个Presenter类(负责逻辑和数据处理)
  • 一个Model类(共享数据模型)
  • 对应的UI设计文件(.touchgfx文件生成的代码)

说白了,每个Screen就是一个独立的功能模块。你想想看,一个温控器界面,温度显示、设置按钮、历史曲线,这些都可以放在同一个Screen里。但如果你要做菜单和子菜单的切换,那就得用多个Screen。

核心要点:Screen = View + Presenter + Model + UI资源。这四个部分缺一不可。

4.2 Screen生命周期——你必须要懂的四个阶段

每个Screen从创建到销毁,会经历四个关键阶段。我在项目中见过不少同事因为没搞懂生命周期,导致内存泄漏或者界面闪烁。

阶段 触发时机 典型操作
setupScreen() Screen即将显示时 初始化控件、加载数据、设置初始状态
tearDownScreen() Screen即将销毁时 释放资源、取消定时器、保存状态
handleTickEvent() 每个tick周期(通常16ms) 周期性更新、动画驱动、数据轮询
handleKeyEvent() 物理按键触发时 按键响应、焦点切换

嗯,这里要注意:setupScreen()和tearDownScreen()是成对出现的。如果你在setupScreen()里new了对象,一定要在tearDownScreen()里delete掉。我曾经在一个项目里忘了释放动态分配的纹理,结果跑了三天后系统直接崩了。

我的习惯:在tearDownScreen()里统一做清理,不管是不是动态分配的,把指针置空、定时器停掉、回调解注册,一步到位。

4.3 Screen切换动画——别让用户觉得卡顿

切换动画是提升用户体验的关键。TouchGFX提供了几种内置的切换方式,但很多人不知道它们背后的原理。

4.3.1 内置切换类型

  • SlideTransition:左右滑动切换,适合菜单导航
  • FadeTransition:淡入淡出,适合弹窗或提示
  • WipeTransition:擦除效果,适合进度展示
  • NoTransition:无动画,适合调试或快速切换

代码示例——如何设置一个滑动切换:

// 在Application类中设置切换动画
ScreenTransition slideTransition = SlideTransition(SlideTransition::LEFT, 
                                                    transitionDuration);
application().switchScreen(&nextScreen, slideTransition);

这里有个坑:transitionDuration的单位是毫秒,我建议设置在200-400ms之间。太短了用户看不清,太长了用户觉得卡。我之前做过一个300ms的滑动切换,测试反馈说“很丝滑”。

4.3.2 自定义动画——进阶玩法

内置动画不够用怎么办?你可以自己写。TouchGFX的动画底层是基于EasingEquations的,说白了就是缓动函数。

// 自定义缓动动画示例
class CustomAnimation : public AnimationListener {
public:
    void animationEnded() override {
        // 动画结束后的处理
        application().switchScreen(&nextScreen, NoTransition());
    }
    
    void startCustomAnimation() {
        // 创建一个缓动动画,持续500ms
        EasingEquations::backEaseOut animation(500);
        animation.setListener(this);
        animation.start();
    }
};

避坑指南:我曾经在自定义动画里直接操作了已经被销毁的Screen的控件,结果触发了HardFault。记住:动画回调里一定要检查Screen是否还活着。

4.4 导航管理器——别让屏幕跳转变成一团乱麻

当你的项目有十几个Screen时,手动管理跳转逻辑会变得非常痛苦。导航管理器就是来解决这个问题的。

4.4.1 什么是导航管理器?

导航管理器是一个全局的单例,负责维护Screen的堆栈。你可以把它想象成一个“历史记录”:

  • pushScreen():跳转到新Screen,旧Screen入栈
  • popScreen():返回上一个Screen,当前Screen出栈
  • replaceScreen():替换当前Screen,不改变堆栈深度
  • clearStack():清空所有历史,回到根Screen

4.4.2 实际项目中的用法

我习惯在FrontendApplication里封装导航方法:

// FrontendApplication.hpp
class FrontendApplication : public touchgfx::Application {
public:
    void gotoMainScreen();
    void gotoSettingScreen();
    void gotoAboutScreen();
    void goBack();
    
private:
    void navigateTo(Screen& screen, ScreenTransition transition);
};

// FrontendApplication.cpp
void FrontendApplication::gotoMainScreen() {
    MainScreenView* view = new MainScreenView();
    MainScreenPresenter* presenter = new MainScreenPresenter(*view);
    navigateTo(*presenter->getScreen(), SlideTransition(SlideTransition::RIGHT, 300));
}

你想想看,这样做的好处是什么?所有跳转逻辑集中在一个地方,后期要改动画或者加参数,只需要改一个文件。

4.4.3 堆栈管理的最佳实践

场景 推荐操作 原因
菜单→子菜单 pushScreen() 用户期望能返回
登录→主页 replaceScreen() 登录后不应返回登录页
设置→关于 pushScreen() 保持导航层级
退出到主菜单 clearStack() + pushScreen() 避免堆栈过深

我的经验:堆栈深度不要超过5层。超过5层后,内存占用会明显增加,而且用户也很难记住自己在哪里。如果发现堆栈太深,考虑用replaceScreen()或者重新设计导航结构。

4.5 本章知识体系总览

下面这张图把Screen和导航的核心关系梳理清楚了,建议你保存下来对照着看。

Screen与导航核心架构图 Screen与导航核心架构 Screen A Screen B Screen C setupScreen() handleTickEvent() tearDownScreen() 导航管理器 pushScreen | popScreen | replaceScreen | clearStack 切换动画:SlideTransition | FadeTransition | WipeTransition | NoTransition 自定义动画:基于EasingEquations实现缓动效果

4.6 实战中的几个关键点

最后,分享几个我在实际项目中总结的经验:

  1. Screen复用:如果多个页面布局相似,考虑用同一个Screen配合参数切换,而不是创建多个Screen。我做过一个仪表盘项目,三个子页面共用同一个Screen,只切换数据源,内存节省了40%。
  2. 动画性能:在低端MCU上,避免同时做多个复杂动画。我建议一次只做一个Screen级别的切换动画,其他控件动画等切换完成后再启动。
  3. 调试技巧:在setupScreen()和tearDownScreen()里加日志输出,可以帮你快速定位生命周期相关的问题。我习惯用touchgfx_printf()打印当前Screen的地址和状态。
  4. 内存管理:每个Screen的View和Presenter都是动态分配的,记得在tearDownScreen()里释放。我见过一个项目因为忘记释放,运行两天后内存耗尽。

重要提醒:不要在handleTickEvent()里做耗时操作,比如文件读写或者网络请求。这个函数每16ms调用一次,阻塞了就会导致界面卡顿。我曾经犯过这个错,结果动画一卡一卡的,用户反馈说“像幻灯片”。

好了,屏幕与导航这块的核心内容就这些。记住:搞懂生命周期,用好导航管理器,选对切换动画,你的嵌入式GUI项目就能稳如泰山。