4、UI组件基础(上):Box容器、TextArea文本显示、Button按钮、Image图像控件

好,咱们今天开始啃 TouchGFX 里最基础、也最常用的四个 UI 组件。说实话,不管多花哨的界面,拆开来看,底子就是这些玩意儿——容器、文字、按钮、图片。我刚开始用 TouchGFX 做项目时,就是靠这四个组件搭出了第一个能跑的原型。

你想想看,一个嵌入式设备的屏幕,无非就是:把东西摆好(Box),写上说明(TextArea),让用户点一下(Button),再放个图标装饰一下(Image)。就这么简单。但简单归简单,每个组件都有它的脾气,用不对照样翻车。

本章核心脉络:四个组件,各自独立又互相配合。Box 负责布局和背景,TextArea 负责信息输出,Button 负责交互输入,Image 负责视觉增强。掌握它们,你就掌握了 TouchGFX 界面搭建的 80%。

UI组件基础(上)知识体系 UI组件基础(上) Box 容器 TextArea 文本 Button 按钮 Image 图像 背景/占位/容器 支持颜色和渐变 静态/动态文本 支持多字体和换行 触发/状态/回调 支持自定义皮肤 位图/图标/背景图 支持缩放和裁剪 组合使用 → 搭建完整界面

4.1 Box 容器——界面里的「隐形骨架」

Box 在 TouchGFX 里是最不起眼的组件,但也是我用得最多的。说白了,它就是一个矩形区域,可以设置颜色、透明度、位置和大小。没有交互,没有事件,就是个「安静的美男子」。

那它有什么用?我举几个实际场景:

  • 背景色块:给整个屏幕或某个区域铺底色,比如深色模式下的黑色背景。
  • 分割线:拉一个细长的 Box,颜色调浅,就是一条完美的分隔线。
  • 占位符:在布局阶段先用 Box 占个位置,等后期再替换成实际组件。
  • 容器分组:把多个组件放在一个 Box 上,统一移动或隐藏。

我的小技巧:调试布局时,我会给每个 Box 设置不同的半透明颜色,比如红色 30%、蓝色 30%。这样一眼就能看出每个组件的边界和重叠情况。调完再统一改成透明或最终颜色。

在 TouchGFX Designer 里拖一个 Box 出来,你会看到它的属性面板里有几个关键参数:

属性 说明 我的建议
Color 填充颜色,支持 24 位 RGB 用色板选,别手写十六进制,除非你记性好
Alpha 透明度,0~255 做遮罩层时常用 100~150 的半透明值
Position x, y 坐标 建议用相对布局,别写死绝对坐标
Size 宽度和高度 可以设为 0,运行时再动态调整

代码里操作 Box 也很直接。比如我想在运行时改变背景颜色:

// 在 setupScreen() 或 handleTickEvent() 中
boxBackground.setColor(touchgfx::Color::getColorFrom24BitRGB(0x2C, 0x3E, 0x50));
boxBackground.setAlpha(200);
boxBackground.invalidate(); // 别忘了刷新

嗯,这里要注意 invalidate() 这个函数。我刚开始做的时候经常忘记调,结果改了颜色屏幕没反应,还以为是 Bug。其实 TouchGFX 的绘制是增量式的,你改了属性必须告诉框架「这块区域需要重绘」。

4.2 TextArea 文本显示——让屏幕「说话」

TextArea 就是用来显示文字的。别小看它,嵌入式设备上的文本处理比 PC 上麻烦得多。为什么?因为字体是预编译的,不是系统自带的。

TouchGFX 的 TextArea 有几个核心概念你必须搞懂:

  • TypedText:文字和字体样式的绑定。你在 Designer 里定义的每个文本样式,都会生成一个 TypedText ID。
  • Wildcard(通配符):用于显示动态内容,比如温度值、时间、计数。
  • TextAreaWithOneWildcard:带一个动态占位符的文本区域,最常用。

我记得第一次做温控器界面时,需要实时显示当前温度。一开始我傻傻地每次重新创建 TextArea,后来才发现有 Wildcard 这种好东西。

用法很简单:

// 在头文件中声明
touchgfx::TextAreaWithOneWildcard textTemperature;
touchgfx::Unicode::UnicodeChar temperatureBuffer[10];

// 在实现文件中更新
Unicode::snprintf(temperatureBuffer, 10, "%d°C", sensorValue);
textTemperature.setWildcard(temperatureBuffer);
textTemperature.invalidate();

踩坑提醒:Wildcard 缓冲区的大小一定要预留足够。我曾经只给了 5 个字符的空间,结果温度显示到三位数时就溢出了,屏幕出现乱码。排查了半天才发现是缓冲区越界。建议至少留 10~20 个字符的空间。

关于字体,TouchGFX 使用预渲染位图字体。你在 Designer 里添加的每个字体文件(.ttf 或 .otf),都会被转换成二进制字体资源。这意味着:

  • 字体大小是固定的,不能像 PC 那样随意缩放
  • 中文字体文件很大,建议只包含用到的字符(子集化)
  • 字体颜色在渲染时就定好了,运行时改颜色需要重新生成

我个人习惯在项目初期就把所有可能用到的中文字符列出来,生成一个精简字体。不然一个完整的宋体字库能吃掉几百 KB 的 Flash,对于资源紧张的 MCU 来说太奢侈了。

4.3 Button 按钮——用户交互的「入口」

按钮是用户和界面交互最直接的方式。TouchGFX 提供了好几种按钮类型,但最常用的是 Button 和 TextButton。

一个按钮的核心就三件事:

  1. 外观:按下前和按下后显示不同的图片或颜色
  2. 触发:点击、长按、释放等事件
  3. 回调:事件发生后执行什么代码

在 Designer 里添加一个 Button,你会看到它需要两张图片:一张是「弹起状态」(Released),一张是「按下状态」(Pressed)。这两张图通常只有颜色或阴影不同,用来给用户视觉反馈。

代码里绑定按钮事件是这样写的:

// 在 view 的构造函数或 setupScreen() 中
buttonStart.setAction(buttonClickedHandler);

// 定义回调函数
void MyView::buttonClickedHandler(const touchgfx::AbstractButton& src) {
    if (&src == &buttonStart) {
        // 启动设备
        startDevice();
    } else if (&src == &buttonStop) {
        // 停止设备
        stopDevice();
    }
}

你可能会问:为什么用 AbstractButton 引用?因为 TouchGFX 的按钮回调机制是基于基类的,这样同一个回调函数可以处理多个按钮的事件。我在项目中就经常用一个回调函数管理十几个按钮,通过比较地址来区分是哪个按钮被按了。

避坑指南:按钮的图片资源不要太大。我曾经用了一张 200x100 的 PNG 做按钮图,结果在 STM32F429 上点击响应有明显的延迟。后来把图片压缩到 80x40,并且转成 RGB565 格式,响应就快多了。嵌入式设备上,图片大小直接影响交互体验。

另外,TouchGFX 还支持 RepeatButton(长按连续触发)和 ToggleButton(开关按钮)。前者适合音量加减这种需要连续调节的场景,后者适合开关切换。

4.4 Image 图像控件——视觉的「点睛之笔」

Image 控件用来显示位图。在 TouchGFX 里,所有图片资源都要先通过 Designer 导入,转换成内部格式(通常是 RGB565 或 ARGB8888)。

Image 的用法很直观:

// 在代码中动态设置图片
imageLogo.setBitmap(Bitmap(BITMAP_LOGO_ID));
imageLogo.setXY(10, 10);
imageLogo.setVisible(true);
imageLogo.invalidate();

但有几个关键点我要强调一下:

  • 图片格式:RGB565 占内存小,但颜色精度低;ARGB8888 颜色丰富,但内存翻倍。根据你的屏幕色深来选择。
  • 图片大小:尽量使用 2 的幂次尺寸(如 64x64、128x128),这样 DMA 传输效率更高。
  • 透明通道:PNG 图片的透明区域在 TouchGFX 里是支持的,但需要启用 Alpha 通道。

注意:不要直接在 Image 上叠加太多透明层。我记得有一次为了做一个毛玻璃效果,叠了三层半透明图片,结果帧率直接掉到 15fps。对于没有 GPU 的 MCU,每多一层透明混合,CPU 的负担就重一分。

Image 还有一个很实用的功能——Scale 和 Rotation。不过这两个操作比较耗资源,我一般只在初始化时做一次,不会在每一帧都去缩放旋转。

4.5 四个组件的协同工作

实际项目中,这四个组件很少单独使用。我举个例子:一个典型的设置页面,可能是这样的结构:

  • 最底层是一个 Box,铺满全屏,颜色深灰
  • 中间层是一个 Image,显示设置页面的标题图标
  • 图标旁边是一个 TextArea,显示「系统设置」字样
  • 下方排列着几个 Button,分别是「网络设置」「显示设置」「关于」

你看,四个组件各司其职,组合起来就是一个完整的界面。我在做智能家居面板时,这种结构用了不下二十次。

最后说一句:别急着在代码里堆组件。先在 Designer 里把布局拖好,确认位置和大小没问题了,再写交互逻辑。这样效率最高,也最不容易出错。