3. LVGL 基础对象与样式:从控件到皮肤
好,咱们进入第三章。这一章我打算把 LVGL 里最核心的两个概念讲透——对象和样式。
说白了,对象就是屏幕上你能看到的那些东西:按钮、标签、滑块……而样式,就是这些东西的“皮肤”——颜色、大小、圆角、阴影,全归它管。
我记得刚接触 LVGL 时,总觉得样式系统有点绕。后来做项目踩了几个坑,才摸清楚门道。今天我把这些经验直接给你,省得你再走弯路。
3.1 lv_obj:一切控件的根
在 LVGL 里,所有可视元素都继承自 lv_obj_t。你可以把它理解成“基础控件”,或者叫“裸控件”。
它本身就是一个矩形块,能设置位置、大小、背景色、边框……但没有任何交互逻辑。按钮、标签这些高级控件,都是在它上面加了一层行为。
核心概念:lv_obj 是 LVGL 对象树的根节点。每个屏幕、每个控件,本质上都是一个 lv_obj。
创建一个基础对象很简单:
lv_obj_t * obj = lv_obj_create(lv_scr_act()); // 在当前活动屏幕上创建
lv_obj_set_size(obj, 200, 100); // 宽200,高100
lv_obj_set_pos(obj, 50, 30); // 距离左上角(50,30)
lv_obj_set_style_bg_color(obj, lv_color_hex(0x2196F3), 0); // 蓝色背景
这里有个细节:lv_obj_create 的参数是父对象。如果你传 lv_scr_act(),它就放在当前屏幕;如果你传另一个控件,它就变成那个控件的子控件。
我在项目里见过有人把所有控件都直接挂在屏幕上,结果界面一复杂,管理起来特别痛苦。我的建议是:按功能分组,用容器(lv_obj)做父节点。比如一个仪表盘页面,可以分成“标题区”、“数据区”、“控制区”三个容器,每个容器里再放具体控件。这样后期改布局,改一个容器就行。
3.2 样式系统入门
样式,就是控件的“外观配置集合”。LVGL 的样式系统非常灵活,但也容易让人懵。咱们先抓住几个关键点。
3.2.1 样式是什么?
一个 lv_style_t 结构体,里面存了一堆属性:背景色、边框宽度、圆角半径、文字颜色、透明度……你可以把它理解成一个“样式快照”。
lv_style_t style;
lv_style_init(&style);
lv_style_set_bg_color(&style, lv_color_hex(0x4CAF50));
lv_style_set_border_width(&style, 2);
lv_style_set_radius(&style, 10);
然后把这个样式应用到控件上:
lv_obj_add_style(obj, &style, 0);
第三个参数 selector 是“状态选择器”,0 表示默认状态。后面我们会讲到状态相关的样式。
小技巧:样式可以复用。同一个 style 对象,可以加到多个控件上。改一次 style,所有关联的控件都会更新。这在做主题切换时特别有用。
3.2.2 样式的层级
LVGL 的样式不是简单的“一个控件一个样式”。它有层级关系。我画了张图帮你理解:
你看,样式是从上往下叠加的。默认样式在最底层,普通样式在中间,本地样式在最顶层。高优先级的样式会覆盖低优先级的同名属性。
3.3 本地样式 vs 普通样式
这两个概念容易搞混。我直接给你对比一下:
| 对比项 | 本地样式 | 普通样式 |
|---|---|---|
| 定义方式 | 直接通过 lv_obj_set_style_xxx() 设置 |
先定义 lv_style_t,再通过 lv_obj_add_style() 添加 |
| 复用性 | 不可复用,只影响单个控件 | 可复用,一个样式可加给多个控件 |
| 优先级 | 高(覆盖普通样式) | 低(被本地样式覆盖) |
| 适用场景 | 临时调整、单个控件的特殊外观 | 主题统一、批量控件样式管理 |
| 内存占用 | 无额外结构体,直接存在控件内部 | 需要单独定义 lv_style_t 结构体 |
3.3.1 本地样式实战
本地样式就是“即用即设”。比如你有一个按钮,想单独改它的背景色:
lv_obj_t * btn = lv_btn_create(lv_scr_act());
lv_obj_set_style_bg_color(btn, lv_color_hex(0xFF5722), 0); // 本地样式
lv_obj_set_style_radius(btn, 20, 0); // 圆角20
注意最后一个参数还是 0,表示默认状态。如果你传 LV_STATE_PRESSED,那就是按下时的样式。
我曾经在做一个智能家居面板时,需要让某个紧急按钮在按下时变成红色闪烁。用本地样式加状态选择器,几行代码就搞定了:
// 按下时变红
lv_obj_set_style_bg_color(emergency_btn, lv_color_hex(0xF44336), LV_STATE_PRESSED);
// 按下时加阴影
lv_obj_set_style_shadow_width(emergency_btn, 10, LV_STATE_PRESSED);
3.3.2 普通样式实战
普通样式适合做“主题”。比如你的应用里有 10 个按钮,希望它们风格统一:
// 定义一个主题样式
lv_style_t theme_btn_style;
lv_style_init(&theme_btn_style);
lv_style_set_bg_color(&theme_btn_style, lv_color_hex(0x2196F3));
lv_style_set_text_color(&theme_btn_style, lv_color_hex(0xFFFFFF));
lv_style_set_radius(&theme_btn_style, 8);
lv_style_set_border_width(&theme_btn_style, 0);
// 应用到所有按钮
for(int i = 0; i < 10; i++) {
lv_obj_add_style(btn_array[i], &theme_btn_style, 0);
}
如果后期想改主题色,只需要改 theme_btn_style 里的颜色,所有按钮自动更新。这就是普通样式的威力。
注意:普通样式定义后,lv_style_t 变量必须保持有效。如果你在函数里定义了一个局部样式,函数退出后样式就失效了,控件会显示异常。我建议把样式定义成全局变量或静态变量。
3.4 样式状态与选择器
LVGL 的样式支持状态。常见的状态有:
LV_STATE_DEFAULT(0):默认状态LV_STATE_PRESSED:按下状态LV_STATE_DISABLED:禁用状态LV_STATE_FOCUSED:聚焦状态LV_STATE_CHECKED:选中状态(用于复选框等)
你可以为不同状态设置不同样式:
// 默认样式
lv_obj_set_style_bg_color(btn, lv_color_hex(0x2196F3), LV_STATE_DEFAULT);
// 按下时变暗
lv_obj_set_style_bg_color(btn, lv_color_hex(0x1976D2), LV_STATE_PRESSED);
// 禁用时变灰
lv_obj_set_style_bg_color(btn, lv_color_hex(0xBDBDBD), LV_STATE_DISABLED);
状态还可以组合。比如 LV_STATE_PRESSED | LV_STATE_FOCUSED 表示“按下且聚焦”。
经验之谈:我建议至少为每个可交互控件定义 DEFAULT 和 PRESSED 两种状态。用户按下按钮时,如果没有任何视觉反馈,体验会很差。哪怕只是颜色变深一点点,用户都会觉得“嗯,这个界面有响应”。
3.5 样式继承与层叠
样式还有一个特性:继承。子控件会继承父控件的某些样式属性,比如文字颜色、字体。
举个例子:
lv_obj_t * cont = lv_obj_create(lv_scr_act());
lv_obj_set_style_text_color(cont, lv_color_hex(0xFF0000), 0); // 父容器文字红色
lv_obj_t * label = lv_label_create(cont); // 子标签
lv_label_set_text(label, "我是红色文字"); // 自动继承红色
但注意,不是所有属性都继承。背景色、边框这些“几何属性”默认不继承。只有文字相关的属性(颜色、字体、对齐方式)会继承。
我遇到过一个问题:在一个容器里放了多个标签,想统一改文字颜色。一开始我一个个标签去设,后来发现直接在容器上设 text_color,所有子标签自动生效。省了不少代码。
3.6 样式性能小贴士
嵌入式设备资源有限,样式用不好会影响性能。我总结了几条经验:
- 少用本地样式:每个本地样式都会在控件内部占用额外空间。能用普通样式就用普通样式。
- 复用样式对象:同一个
lv_style_t可以加给多个控件,别重复定义。 - 避免频繁修改样式:每次
lv_obj_set_style_xxx()都会触发重绘。如果有一批样式要改,先禁用刷新,改完再启用。
lv_obj_set_style_bg_color(obj1, color1, 0);
lv_obj_set_style_bg_color(obj2, color2, 0);
// 上面每行都会触发重绘
// 优化:批量修改
lv_obj_invalidate(obj1); // 标记需要刷新
lv_obj_invalidate(obj2);
// 改完后系统统一重绘
核心总结:
- lv_obj 是所有控件的根,学会用它做容器管理
- 样式分三层:默认样式 < 普通样式 < 本地样式
- 本地样式用于临时调整,普通样式用于主题复用
- 状态选择器让控件在不同交互下呈现不同外观
- 样式继承能减少重复代码,但要注意哪些属性会继承