4、样式系统(Style)入门:样式的概念、创建与应用样式、本地样式与全局样式
好,咱们进入第四章。说实话,样式系统是 LVGL 里最容易被低估的部分。很多人觉得「不就是改个颜色嘛」,结果做到后面发现控件长得千奇百怪,改一个地方要翻好几处代码。我刚开始带项目的时候也吃过这个亏——一个按钮的圆角改了三次,每次都要跑到三个不同的文件里去同步。后来我才明白,样式不是「贴膜」,它是控件的骨架。
这一章,咱们就把样式这件事彻底聊透。
4.1 样式的本质:不是「皮肤」,是「属性包」
在 LVGL 里,样式(lv_style_t)说白了就是一个属性集合。它不绑定任何控件,只是一个「描述」。你可以把它理解成一张配方:颜色、边框、间距、阴影……全写在这张配方里。控件拿到配方,照着画自己。
核心概念:样式 = 属性容器。一个样式可以同时描述背景色、文字颜色、边框宽度、圆角半径等几十个属性。控件不直接存这些值,而是通过「样式指针」去引用。
为什么会这样设计?你想想看,如果每个按钮都自己存一份颜色、一份边框、一份阴影,那 100 个按钮就得存 100 份。但如果它们共用同一个样式,内存只占一份。这在嵌入式环境里,差别非常大。
4.2 创建样式:从零到一
创建一个样式很简单,三步走:
- 定义一个
lv_style_t变量 - 调用
lv_style_init()初始化 - 用
lv_style_set_*()系列函数设置属性
看代码:
/* 1. 定义样式变量 */
static lv_style_t style_btn;
/* 2. 初始化 */
lv_style_init(&style_btn);
/* 3. 设置属性 */
lv_style_set_bg_color(&style_btn, lv_color_hex(0x2196F3));
lv_style_set_bg_opa(&style_btn, LV_OPA_COVER);
lv_style_set_radius(&style_btn, 8);
lv_style_set_border_width(&style_btn, 2);
lv_style_set_border_color(&style_btn, lv_color_hex(0x1976D2));
lv_style_set_pad_all(&style_btn, 10);
嗯,这里要注意:lv_style_init() 必须调用。我见过有人直接 memset 清零,结果样式属性没被正确标记为「未设置」,后面应用时会出现奇怪的默认值。
我的习惯:所有样式变量都声明为 static,放在文件顶部。这样既避免栈溢出,也方便其他函数引用。如果你在函数内部定义样式,记得用 lv_style_init() 重新初始化——局部变量不会自动清零。
4.3 应用样式:给控件「穿上」
样式创建好了,怎么给控件用?LVGL 提供了两种方式:
| 方式 | 函数 | 说明 |
|---|---|---|
| 添加样式 | lv_obj_add_style() |
将一个样式添加到控件的样式列表中 |
| 替换样式 | lv_obj_replace_style() |
用新样式替换已有的某个样式 |
| 移除样式 | lv_obj_remove_style() |
从控件中移除指定样式 |
最常用的是 lv_obj_add_style()。它的第三个参数是「选择器」,用来指定这个样式在控件的哪个状态下生效。比如:
/* 默认状态下应用样式 */
lv_obj_add_style(btn, &style_btn, 0);
/* 按下状态下应用另一个样式 */
lv_obj_add_style(btn, &style_btn_pressed, LV_STATE_PRESSED);
这里的选择器可以是 LV_STATE_DEFAULT(即 0)、LV_STATE_PRESSED、LV_STATE_FOCUSED 等。你可以为同一个控件的不同状态设置完全不同的外观。
我曾经踩过的坑:给按钮添加了按下样式,但忘记设置 LV_STATE_PRESSED 选择器,结果默认样式和按下样式同时生效,按钮按下去颜色没变化。后来排查了半天才发现是选择器写成了 0,而不是 LV_STATE_PRESSED。记住:选择器决定了样式在「什么时候」生效。
4.4 本地样式 vs 全局样式:两种思维
这是本章的重点。LVGL 的样式体系里,样式可以作用在三个层级:
- 本地样式:直接加在单个控件上,只影响这一个控件。
- 全局样式:加在屏幕上或主题中,影响所有未覆盖该属性的控件。
- 继承样式:从父控件继承下来的样式属性。
它们的优先级是这样的:
本地样式 > 全局样式 > 继承样式 > 默认样式
说白了,你给按钮单独设的颜色,会覆盖主题里定义的颜色。而主题里的颜色,又会覆盖父容器传下来的颜色。
我画了一张图,帮你理清这个关系:
4.5 实战:什么时候用本地,什么时候用全局?
这个问题没有标准答案,但我可以分享我的经验:
- 全局样式:适合定义「品牌色系」「基础字体」「默认间距」。比如整个产品的主题色是蓝色,那就把蓝色写在全局样式里。所有按钮、标签、滑块默认都继承这个蓝色。
- 本地样式:适合「特例」。比如某个警告按钮需要红色,某个禁用按钮需要灰色。这些「偏离主题」的情况,用本地样式覆盖。
举个例子:
/* 全局样式:定义主题色 */
static lv_style_t style_theme;
lv_style_init(&style_theme);
lv_style_set_bg_color(&style_theme, lv_color_hex(0x2196F3));
lv_style_set_text_color(&style_theme, lv_color_hex(0xFFFFFF));
/* 应用到屏幕 */
lv_obj_add_style(lv_scr_act(), &style_theme, 0);
/* 本地样式:警告按钮覆盖为红色 */
static lv_style_t style_warning;
lv_style_init(&style_warning);
lv_style_set_bg_color(&style_warning, lv_color_hex(0xFF5722));
/* 只加在这个按钮上 */
lv_obj_add_style(btn_warning, &style_warning, 0);
你看,全局样式定义了蓝色主题,但警告按钮通过本地样式变成了橙色。其他按钮仍然是蓝色。这就是「全局统一,局部灵活」的思路。
我的建议:在项目初期,先定义一套全局样式。等开发过程中发现某个控件需要「特殊处理」,再单独加本地样式。不要一开始就每个控件都写一堆样式代码——那样后期维护起来会非常痛苦。我见过一个项目,200 多个控件,每个控件都写了 5 行以上的样式代码,结果改一个圆角要改 200 个地方……
4.6 样式的「未设置」与「继承」
这里有个很重要的细节:样式属性有「已设置」和「未设置」两种状态。如果你只设置了背景色,没设置边框宽度,那么边框宽度会从父控件或主题中继承。如果父控件也没设置,那就用 LVGL 的默认值。
这个机制的好处是:你不需要把所有属性都写全。只写你需要改的,剩下的自动继承。
但坏处也很明显:如果你不小心漏掉了某个属性,它可能会继承到你不想要的值。比如你给按钮设置了红色背景,但忘了设置文字颜色,结果文字颜色从父控件继承成了白色,在红色背景上根本看不清。
我曾经犯过的错:做一个设置页面,所有标签都用了全局样式,文字颜色是深灰色。后来加了一个特殊标签,单独设置了背景色为深蓝色,但忘了设置文字颜色。结果文字继承为深灰色,在深蓝色背景上几乎看不见。测试的时候没发现,直到用户反馈才知道。从那以后,我每次设置背景色都会顺手检查一下文字颜色。
4.7 小结
这一章我们聊了样式的本质、创建方法、应用方式,以及本地样式和全局样式的区别。核心就一句话:样式是属性包,本地覆盖全局,继承兜底。