4. UI组件基础(下):ListLayout、ScrollableContainer、Slider、Gauge的使用与交互
上一节我们把Button、TextArea、Image这些基础组件过了一遍。今天聊的几个组件,说实话,在真实项目里出镜率更高——尤其是做参数设置、数据监控这类界面时,几乎天天跟它们打交道。
ListLayout、ScrollableContainer、Slider、Gauge,这四个组件各有各的脾气。我刚开始用TouchGFX那会儿,在ListLayout上栽过跟头,在Slider上也被坑过。今天把这些经验掰开揉碎了讲给你听。
本章核心:掌握列表布局的自动排列机制、滚动容器的裁剪与嵌套规则、滑块的双向绑定、仪表盘的动态刷新逻辑。
4.1 ListLayout:自动排列的容器
ListLayout说白了就是一个会自动排孩子的容器。你把子控件往里一丢,它按你设定的方向(水平或垂直)自动排好,间距均匀。手动调位置?不存在的。
我个人习惯用ListLayout来做菜单列表、参数选项这类场景。比如一个设置页面,有5个选项,每个选项高度固定,用ListLayout垂直排列,代码量少,维护也方便。
使用技巧:ListLayout的排列方向在构造函数里通过参数指定。水平方向用HORIZONTAL,垂直方向用VERTICAL。默认间距是0,记得调一下。
看个实际例子。假设我们要做一个垂直排列的菜单,每个菜单项是一个带图标的按钮:
// 在setupScreen()中创建ListLayout
ListLayout listLayout;
listLayout.setDirection(ListLayout::VERTICAL);
listLayout.setXY(10, 10);
listLayout.setWidth(220);
listLayout.setHeight(300);
// 添加三个菜单项
for (int i = 0; i < 3; i++) {
ButtonWithLabel* btn = new ButtonWithLabel();
btn->setWidth(200);
btn->setHeight(50);
btn->setLabelText("菜单项 " + Unicode::strFromInt(i + 1));
listLayout.add(*btn);
}
这里有个坑——ListLayout不会自动调整子控件的大小。你得自己给每个子控件设好宽高。我刚开始用的时候以为它会自适应,结果所有按钮叠在一起,画面惨不忍睹。
注意:ListLayout的子控件必须提前设置好宽高。它只负责排列位置,不负责缩放。另外,添加子控件后如果修改了子控件大小,需要调用invalidate()刷新布局。
4.2 ScrollableContainer:可滚动的视口
ScrollableContainer,名字挺长,功能其实就一个——提供一个可滚动的裁剪区域。内容超出容器范围时,用户可以通过触摸或鼠标拖拽来滚动查看。
我在项目中遇到过这样一个需求:一个设备日志页面,日志行数不确定,可能几十行也可能几百行。用ScrollableContainer包一下,完美解决。
关键参数就两个:
| 参数 | 说明 | 建议值 |
|---|---|---|
| setScrollThreshold | 触发滚动的触摸位移阈值 | 10-20像素 |
| setScrollSpeed | 滚动速度系数 | 1.0-3.0 |
| setScrollbarVisible | 是否显示滚动条 | 根据UI风格决定 |
使用步骤其实很简单:
- 创建一个ScrollableContainer,设置好它的位置和大小(这就是可视区域)
- 把需要滚动的内容作为子控件添加到容器里
- 子控件的总尺寸可以大于容器尺寸
- 容器会自动处理触摸滚动逻辑
举个例子:
ScrollableContainer scrollContainer;
scrollContainer.setPosition(10, 10, 240, 320);
scrollContainer.setScrollThreshold(15);
scrollContainer.setScrollSpeed(2.0f);
// 添加一个很长的文本区域
TextAreaWithOneWildcard textArea;
textArea.setPosition(0, 0, 220, 800); // 高度远超容器
textArea.setTypedText(TypedText(T_LOG_CONTENT));
textArea.setWildcard("这是一段很长的日志内容...");
scrollContainer.add(textArea);
我曾经踩过的坑:ScrollableContainer的裁剪区域必须明确设置。如果你忘了setWidth和setHeight,容器默认是0,内容根本显示不出来。另外,嵌套使用ScrollableContainer时,内层容器的触摸事件可能会被外层拦截,建议尽量避免嵌套。
4.3 Slider:滑块调节器
Slider就是那个可以拖动的条,用来调节数值。亮度调节、音量控制、参数微调,这些场景都离不开它。
TouchGFX的Slider支持水平和垂直两种方向。核心属性包括最小值、最大值、当前值、步进值。你想想看,一个音量滑块,范围0-100,步进1,拖动时实时更新音量值,这就是典型的应用。
看代码:
Slider slider;
slider.setPosition(20, 100, 200, 30);
slider.setRange(0, 100); // 最小值0,最大值100
slider.setValue(50); // 初始值50
slider.setStep(1); // 步进1
slider.setDirection(Slider::HORIZONTAL);
// 注册回调
slider.setNewValueCallback(newValueCallback);
// 或者用Lambda
slider.setNewValueCallback([this](int value) {
// 更新音量
audioPlayer.setVolume(value);
});
回调函数里拿到的value就是当前滑块的值。注意,这个值是int类型,如果你需要浮点数,得自己做映射。
个人经验:Slider的步进值设得越小,拖动越平滑,但CPU开销也越大。一般场景设1就够了。如果是调节温度这种精度要求不高的,步进设5或10,用户体验反而更好——不会因为太灵敏而调不准。
4.4 Gauge:仪表盘组件
Gauge,仪表盘。说实话,我第一次在TouchGFX里看到这个组件时挺惊喜的。做工业控制、车载仪表、设备监控这类界面,仪表盘几乎是标配。
Gauge的核心原理是把数值映射到角度上。比如一个半圆仪表,范围0-100,对应角度0°到180°。你给个值50,指针就停在正中间。
关键参数:
| 参数 | 说明 |
|---|---|
| setRange | 设置数值范围(最小值,最大值) |
| setAngleRange | 设置角度范围(起始角度,结束角度) |
| setValue | 设置当前值,指针自动旋转到对应角度 |
| setCenter | 设置仪表盘中心点坐标 |
实际用法:
Gauge gauge;
gauge.setPosition(50, 50, 200, 200);
gauge.setRange(0, 100);
gauge.setAngleRange(-90, 90); // 从-90°到90°,形成半圆
gauge.setValue(75); // 指针指向75%
gauge.setCenter(100, 100); // 中心点
// 动态更新
gauge.setValue(newValue);
gauge.invalidate();
重点:Gauge的指针是自动计算的,你只需要调用setValue,它会根据数值范围和角度范围自动算出指针位置。但要注意,每次更新值后要调用invalidate()刷新显示,否则画面不会更新。
我在一个电机监控项目里用过Gauge。当时需要实时显示电机转速,数据每秒刷新10次。Gauge配合定时器,每100ms调用一次setValue,效果很流畅。不过有个细节——如果数值变化太快,指针会跳,建议加个简单的线性插值,让指针平滑过渡。
4.5 组合实战:一个参数调节面板
光讲单个组件没意思,咱们把它们组合起来,做一个简单的参数调节面板。这个面板包含:
- 一个ListLayout排列的菜单列表
- 一个ScrollableContainer包裹的详细参数区
- 一个Slider用来调节参数值
- 一个Gauge实时显示当前参数状态
结构大概是这样的:
// 主容器
Container mainContainer;
mainContainer.setPosition(0, 0, 480, 272);
// 左侧菜单
ListLayout menuList;
menuList.setDirection(ListLayout::VERTICAL);
menuList.setXY(10, 10);
menuList.setWidth(120);
// 添加菜单项...
// 右侧参数区
ScrollableContainer paramArea;
paramArea.setPosition(140, 10, 330, 250);
// 参数区内部:Slider + Gauge
Slider paramSlider;
paramSlider.setPosition(10, 10, 200, 30);
paramSlider.setRange(0, 100);
Gauge paramGauge;
paramGauge.setPosition(10, 60, 150, 150);
paramGauge.setRange(0, 100);
paramGauge.setAngleRange(-90, 90);
// 联动:Slider改变时更新Gauge
paramSlider.setNewValueCallback([&](int value) {
paramGauge.setValue(value);
paramGauge.invalidate();
});
paramArea.add(paramSlider);
paramArea.add(paramGauge);
mainContainer.add(menuList);
mainContainer.add(paramArea);
建议:实际项目中,Slider和Gauge的联动很常见。但要注意,如果Gauge的刷新频率太高(比如每1ms更新一次),CPU占用会飙升。我一般控制在50ms以上更新一次,人眼看起来已经很流畅了。
好了,这四个组件的基本用法就这些。ListLayout帮你省去手动排版的麻烦,ScrollableContainer解决内容超长的问题,Slider和Gauge则是参数调节和数据展示的好搭档。下回做界面时,想想哪个场景该用哪个组件,别一上来就堆代码。