3、UI组件基础(上):Box、Button、TextArea、Image的使用与属性配置
好,咱们今天开始啃UI组件这块硬骨头。说实话,TouchGFX里组件不少,但真正天天打交道的,就是Box、Button、TextArea和Image这四个。我刚开始带项目的时候,总觉得组件嘛,拖上去就能用,结果真到了移植阶段,才发现每个组件的属性配置里全是坑。
这一章,咱们就把这四个基础组件掰开揉碎。你跟着我走一遍,以后碰到任何界面需求,心里都有底。
核心观点:UI组件不是画出来的,是配出来的。属性配置决定了组件在目标硬件上的最终表现,尤其是移植阶段,一个像素的偏差都可能导致界面错位。
3.1 Box:最朴素的色块,最容易被忽视
Box说白了就是一个纯色矩形。你可能觉得它太简单了,但我告诉你,背景、分割线、遮罩层,全得靠它。我在一个医疗设备项目里,就因为Box的透明度没配好,导致整个界面背景发灰,折腾了两天才找到原因。
3.1.1 核心属性
| 属性 | 类型 | 说明 | 我的建议 |
|---|---|---|---|
| 颜色 | colortype | 填充色,支持24位RGB | 尽量用调色板里的颜色,避免运行时动态改色 |
| 透明度 | uint8_t (0-255) | 0全透明,255不透明 | 移植时先设255,确认位置后再调透明度 |
| 位置与大小 | int16_t | x, y, width, height | 我习惯用相对布局,避免硬编码像素值 |
小技巧:调试阶段可以把Box的透明度设成128,这样能同时看到背景和前景,方便对齐位置。我曾经用这个方法半小时就定位了三个界面错位问题。
3.1.2 代码示例
// 创建一个半透明的黑色遮罩层
Box boxMask;
boxMask.setPosition(0, 0, 480, 272);
boxMask.setColor(Color::getColorFrom24Bit(0x000000));
boxMask.setAlpha(180); // 约70%不透明度
// 添加到容器
container.add(boxMask);
嗯,这里要注意:setAlpha必须在setColor之后调用,否则颜色会被重置。我踩过这个坑,查了半天的文档才发现顺序问题。
3.2 Button:不只是“点一下”那么简单
Button是用户交互的核心。你想想看,用户跟设备打交道,大部分时间就是在按按钮。但Button的属性配置,远比你想象的要复杂。
3.2.1 三种状态,三种样式
每个Button都有三种视觉状态:Released(弹起)、Pressed(按下)、Disabled(禁用)。我见过很多新手只配了Released状态的图,结果按下时按钮毫无反馈,用户体验极差。
| 状态 | 触发条件 | 典型配置 |
|---|---|---|
| Released | 手指离开屏幕 | 正常颜色/图片 |
| Pressed | 手指按下瞬间 | 颜色加深或图片切换 |
| Disabled | 按钮不可用 | 灰色或半透明 |
警告:移植到不同分辨率的屏幕时,Button的点击热区(touch area)不会自动缩放。你必须手动调整setTouchArea,否则会出现“点不到”或“误触”的问题。我在一个手持设备项目里就因为这个被测试部门打了回来。
3.2.2 回调机制
Button按下去之后,总得干点什么事吧?这就涉及到回调函数。
// 在头文件中声明回调
Callback<MyView, const AbstractButton&> buttonClickedCallback;
// 在构造函数中绑定
buttonClickedCallback = Callback<MyView, const AbstractButton&>(this, &MyView::onButtonClicked);
btnStart.setAction(buttonClickedCallback);
// 实现回调函数
void MyView::onButtonClicked(const AbstractButton& source) {
if (&source == &btnStart) {
// 启动设备
startDevice();
}
}
我个人习惯用switch-case配合getType来处理多个按钮,代码更清晰。但如果你只有一两个按钮,直接用if-else也够了。
3.3 TextArea:文字显示的门道
TextArea看起来就是显示一段文字,但实际用起来,字体、对齐、换行、多语言,每一个都能让你头疼。我记得有一次,就因为字体文件没包含中文字符,整个界面上的中文全变成了方框,客户当场就炸了。
3.3.1 字体与文本属性
| 属性 | 说明 | 避坑指南 |
|---|---|---|
| 字体 | 通过TypedText指定 | 必须确保字体文件已生成并包含所需字符集 |
| 对齐方式 | LEFT, CENTER, RIGHT | 移植后检查对齐,不同分辨率下表现可能不同 |
| 换行模式 | SINGLE_LINE, MULTI_LINE | 多行文本必须设置高度,否则显示不全 |
| 颜色 | colortype | 与背景色对比度要足够,我一般用深色背景配白字 |
经验之谈:在TouchGFX Designer里,TextArea默认是单行模式。如果你要显示多行文本,记得手动把WildcardBuffer设大一点,否则文字会被截断。我曾经设了个10字节的buffer,结果显示“连接成功”四个字都显示不全,因为UTF-8编码下每个中文字符占3个字节。
3.3.2 动态文本更新
// 定义文本ID(在TextsAndLanguages中)
TypedTextId T_SENSOR_VALUE = TypedTextId(5);
// 更新文本内容
Unicode::snprintf(textAreaBuffer, BUFFER_SIZE, "%d.%02d", value / 100, value % 100);
textAreaValue.setTypedText(TypedText(T_SENSOR_VALUE));
textAreaValue.invalidate(); // 强制重绘
这里有个细节:invalidate()一定要调用,否则界面不会刷新。我刚开始做的时候经常忘了这步,盯着屏幕看了半天,心想“怎么数值不变呢?”
3.4 Image:图片显示的艺术
Image组件用来显示位图。在嵌入式系统里,图片资源是非常宝贵的,一张大图可能就占掉几百KB的Flash。所以,图片的格式和压缩策略至关重要。
3.4.1 支持的图片格式
| 格式 | 色深 | 内存占用 | 适用场景 |
|---|---|---|---|
| RGB565 | 16位 | 中等 | 照片、渐变色背景 |
| RGB888 | 24位 | 高 | 高精度显示(较少用) |
| L8(索引色) | 8位 | 低 | 图标、按钮、简单图形 |
| BW(黑白) | 1位 | 极低 | 文字、简单符号 |
我个人强烈建议:能用L8就别用RGB565。图标和按钮用L8格式,内存占用能减少一半以上。我在一个资源紧张的STM32F4项目里,把所有图标从RGB565转成L8,Flash占用直接降了300多KB。
3.4.2 图片的缩放与裁剪
// 创建一个图片组件
Image imgLogo;
imgLogo.setBitmap(Bitmap(BITMAP_LOGO_ID));
imgLogo.setXY(10, 10);
// 如果需要缩放(慎用)
imgLogo.setScalingMode(Image::SCALE_BILINEAR);
imgLogo.setWidth(200);
imgLogo.setHeight(100);
性能警告:图片缩放非常消耗CPU资源。在低端MCU上,尽量不要用SCALE_BILINEAR模式,它会逐像素插值,一帧下来可能掉十几帧。我建议在PC端就把图片缩放到目标尺寸,运行时直接显示原始大小。
3.5 组件关系与布局逻辑
这四个组件不是孤立的。在实际界面中,它们经常组合使用。比如一个按钮,背景是Box,上面覆盖Image图标,旁边再放一个TextArea显示文字。
你看这张图就清楚了。Container是老大,下面管着四个小弟。每个小弟各司其职,组合起来就是一个完整的交互单元。我在做工业控制面板时,一个界面里这样的组合单元有十几个,全靠Container来管理层级关系。
3.6 属性配置的通用原则
最后,我总结几条通用原则,你移植的时候照着做,能省不少事:
- 先定位,后美化:先把所有组件的位置和大小调对,再考虑颜色、字体、图片。我见过太多人花半天调颜色,结果位置全错了,又得重来。
- 用相对坐标,别用绝对坐标:
setXY虽然方便,但移植到不同分辨率时,绝对坐标会让你改到崩溃。我习惯用setPosition配合父容器的宽高来计算。 - 每个组件都要invalidate:修改属性后,记得调用
invalidate()。这是TouchGFX的刷新机制,不调用就不会重绘。 - 调试时多用Box:不确定某个区域的位置?放一个半透明的Box上去,一目了然。这个技巧帮我节省了无数调试时间。