4. UI设计基础:Widgets(控件)体系、Box/Button/TextArea/Image的使用、属性面板详解
好,咱们进入第四讲。前面几章我们把TouchGFX的环境搭好了,也跑通了第一个Demo。但说实话,那个Demo里的界面是人家写好的,你只是点了个编译。从这章开始,我们要亲手往屏幕上“摆”东西了。
在嵌入式GUI里,屏幕上每一个能看见、能交互的元素,都叫Widget,也就是控件。我刚开始接触TouchGFX时,第一反应是:“这不就是画图板吗?” 后来踩了几个坑才发现——它比画图板讲究得多。
核心认知:Widget不是静态图片,它们是“活”的对象。每个Widget都有自己的坐标、大小、颜色、状态,甚至能响应触摸事件。你操作的是C++对象,不是像素。
4.1 Widget体系总览
TouchGFX的Widget继承关系,说白了就是一棵树。根是Drawable,所有能画在屏幕上的东西都从它派生。再往下分两支:容器类和控件类。
你想想看,为什么要有容器?我刚开始做项目时,把所有Button直接扔在Screen上,结果要整体移动一组控件时,得一个一个改坐标,改到崩溃。后来学乖了——先把控件放进Container,再移动Container,一句话搞定。
4.2 四个最常用的基础控件
咱们一个一个来过。这四个控件,你以后做任何界面都会用到,几乎逃不掉。
4.2.1 Box —— 最纯粹的色块
Box是TouchGFX里最简单的控件。它不显示文字,不显示图片,只显示一个纯色矩形。听起来很鸡肋?其实不然。
- 用途1:背景色 —— 整个屏幕的背景,用一个Box铺满就行
- 用途2:分割线 —— 细长的Box当分隔线,比画线方便
- 用途3:遮罩层 —— 半透明Box盖在弹窗后面,实现“灰掉”效果
- 用途4:进度条背景 —— 配合另一个Box做填充动画
我的小技巧:Box的alpha通道是可以单独设置的。我在做智能家居面板时,用三个半透明Box叠加,调出了渐变色背景效果——纯代码实现,不用任何图片资源。
4.2.2 Button —— 交互的核心
Button是用户触摸屏幕时最直接的交互对象。TouchGFX的Button有几种风格:
| Button类型 | 特点 | 适用场景 |
|---|---|---|
Button |
使用图片,按下/松开两张图切换 | 自定义外观的按钮 |
TextButton |
显示文字,可设置背景色 | 纯文字按钮 |
ToggleButton |
按下后保持状态,再按才弹起 | 开关、锁定/解锁 |
RepeatButton |
按住不放持续触发点击事件 | 音量加减、数值微调 |
我记得第一次用Button时,犯了个低级错误——把按钮图片尺寸设得比控件区域小,结果触摸区域比视觉区域大一圈,用户点旁边空白处也能触发。嗯,控件的宽高和图片的宽高是两回事,这个后面属性面板会细说。
4.2.3 TextArea —— 显示文字
TextArea就是用来显示文本的。它有几个关键属性:
- TypedText —— 指向一个在Texts语言文件中定义的文本ID
- 颜色 —— 直接设置RGB值
- 对齐方式 —— 左对齐、居中、右对齐
- 基线调整 —— 微调文字在控件内的垂直位置
注意:TextArea不能自动换行!如果你需要多行文本,请使用TextAreaWithOneWildcard配合换行符,或者用多个TextArea拼起来。我曾经在显示传感器数据时,发现文字跑到控件外面去了——就是因为没手动换行。
4.2.4 Image —— 显示图片
Image控件用来展示位图。TouchGFX支持多种图片格式,但最终都会转换成内部位图格式。使用Image时要注意:
- 图片必须提前导入 —— 通过TouchGFX Designer的Images选项卡导入
- 图片尺寸最好和控件一致 —— 否则会拉伸或裁剪
- 支持Alpha通道 —— PNG透明背景完全OK
- 可以设置缩放算法 —— NEAREST_NEIGHBOR最快,BILINEAR更平滑
性能提醒:Image控件每帧都会重新绘制整张图片。如果你在界面上放了10张大图,帧率会明显下降。我的经验是:能用Box+文字搞定的,就别用图片。尤其是背景,一个Box比一张PNG省几十倍的渲染开销。
4.3 属性面板详解
在TouchGFX Designer里,选中任意一个Widget,右侧就会出现属性面板。很多人觉得这面板没啥好学的,拖拖拽拽就行了。其实不然——属性面板里的每一项,最终都会生成对应的C++代码。你理解了属性,就等于理解了API。
咱们以Button为例,拆解一下属性面板:
| 属性分组 | 属性名 | 说明 | 我踩过的坑 |
|---|---|---|---|
| 位置与大小 | X / Y | 控件左上角相对于父容器的坐标 | 父容器移动后,子控件坐标不变,但显示位置会变 |
| Width / Height | 控件的宽高(像素) | Button的宽高决定了触摸响应区域 | |
| Visible | 是否可见 | 设为false后不绘制也不响应触摸 | |
| 外观 | Box / Bitmap | 选择纯色填充还是图片填充 | 选Bitmap后要指定图片ID |
| Alpha | 透明度(0-255) | 255不透明,0完全透明 | |
| 交互 | Touchable | 是否响应触摸事件 | 设为false后,按钮看起来还在,但点不了 |
| Click Action | 点击时触发的回调函数 | 需要在Screen的setupScreen()里绑定 | |
| 样式 | Style | 按钮的按下/松开状态对应的图片 | 两张图尺寸必须一致,否则会变形 |
你看,每个属性都不是摆设。我见过有人把Button的Width设成0,结果怎么点都没反应——因为触摸区域是0像素。也见过把Alpha设成0,然后问“为什么按钮不见了?” 嗯,这些坑我都踩过。
4.4 实战:组合使用这四个控件
光说不练假把式。咱们用Box、Button、TextArea、Image做一个简单的“设备控制卡片”:
// 伪代码逻辑,展示控件组合方式
// 1. 创建一个Container作为卡片容器
Container cardContainer;
cardContainer.setPosition(20, 20, 200, 120);
// 2. 添加Box作为卡片背景
Box cardBackground;
cardBackground.setPosition(0, 0, 200, 120);
cardBackground.setColor(Color::getColorFrom24BitRGB(0x2C, 0x3E, 0x50));
cardBackground.setAlpha(200);
cardContainer.add(cardBackground);
// 3. 添加Image显示设备图标
Image deviceIcon;
deviceIcon.setBitmap(Bitmap(BITMAP_FAN_ID));
deviceIcon.setXY(15, 20);
cardContainer.add(deviceIcon);
// 4. 添加TextArea显示设备名称
TextArea deviceName;
deviceName.setTypedText(TypedText(T_DEVICE_NAME));
deviceName.setColor(Color::getColorFrom24BitRGB(0xFF, 0xFF, 0xFF));
deviceName.setXY(60, 25);
cardContainer.add(deviceName);
// 5. 添加Button作为开关
ToggleButton powerButton;
powerButton.setBitmaps(Bitmap(BITMAP_BTN_OFF_ID), Bitmap(BITMAP_BTN_ON_ID));
powerButton.setXY(150, 70);
cardContainer.add(powerButton);
// 6. 将卡片添加到Screen
add(cardContainer);
这段代码虽然简单,但包含了UI开发的核心思路:容器嵌套 + 控件组合 + 坐标布局。你以后做任何复杂的界面,本质上都是在重复这个模式。
我的习惯:在Designer里拖好布局后,我会去生成的代码里看一眼坐标值。有时候Designer里看着对齐了,但代码里X坐标差1个像素——强迫症表示不能忍,直接改代码微调。
4.5 避坑指南
最后,分享几个我这些年积累的经验:
- 控件重叠时,后添加的在上层 —— 如果想调整层级,用
insert()方法指定位置 - TextArea的文字颜色在Designer里看着和实际不一样 —— 因为Designer的背景色和实际设备不同,建议在目标设备上验证
- Button的图片资源不要太大 —— 一张200x200的按钮图片,在低端MCU上解码就要好几毫秒
- Box的setColor()用的是24位RGB —— 别传32位值,否则颜色会偏
我曾经在一个项目里,为了追求视觉效果,用了大量半透明Box叠加。结果在STM32F429上跑,帧率直接掉到15fps。后来把所有半透明层合并成一张预渲染图片,帧率回到了55fps。所以——漂亮和流畅之间,你得学会取舍。
好了,这一章的内容就到这儿。Widget体系是TouchGFX的基石,把这四个控件玩熟了,后面的ListView、Graph、CustomWidget学起来会轻松很多。去打开Designer,亲手拖几个控件试试吧。