4、LVGL样式系统:样式结构体、创建与应用样式、样式继承、本地样式与全局样式
样式系统,说白了就是LVGL的「皮肤」和「性格」。
我刚开始接触LVGL时,觉得样式不就是改改颜色、调调大小嘛。后来做项目才发现,样式系统的设计直接决定了代码的复用性和维护成本。你想想看,一个界面几十个按钮,如果每个按钮都单独写样式,那改起来得多痛苦?
4.1 样式结构体:一切的基础
在LVGL里,样式不是直接写在对象上的。它有一个专门的「样式结构体」——lv_style_t。你可以把它想象成一个「属性包」,里面装着颜色、边框、阴影、间距等等。
/* 声明一个样式结构体 */
lv_style_t style_btn;
/* 初始化它,这步不能省 */
lv_style_init(&style_btn);
/* 设置属性 */
lv_style_set_bg_color(&style_btn, lv_color_hex(0x2196F3));
lv_style_set_radius(&style_btn, 8);
lv_style_set_pad_all(&style_btn, 10);
嗯,这里要注意:lv_style_init() 必须调用。我见过有人忘了初始化,结果样式死活不生效,排查了半天才发现是这个问题。
核心要点:lv_style_t 是一个「描述符」,它本身不绑定任何对象。你可以把它应用到多个对象上,实现「一处定义,多处使用」。
4.2 创建与应用样式:把皮肤穿上去
样式定义好了,怎么穿到控件上?很简单,用 lv_obj_add_style() 这个函数。
/* 创建一个按钮 */
lv_obj_t *btn = lv_btn_create(lv_scr_act());
lv_obj_set_size(btn, 120, 50);
/* 应用样式 */
lv_obj_add_style(btn, &style_btn, 0);
第三个参数是「选择器」,0 表示默认状态。你还可以指定 LV_STATE_PRESSED(按下状态)、LV_STATE_DISABLED(禁用状态)等。
我个人习惯把不同状态的样式分开定义,这样代码更清晰:
lv_style_t style_btn_pr; /* 按下时的样式 */
lv_style_init(&style_btn_pr);
lv_style_set_bg_color(&style_btn_pr, lv_color_hex(0x1565C0));
lv_obj_add_style(btn, &style_btn_pr, LV_STATE_PRESSED);
小技巧:如果你想让按钮按下时有「下沉」效果,可以同时修改 pad_top 和 pad_bottom,模拟物理按钮的按压感。我在做智能家居面板时就用过这招,用户反馈手感很好。
4.3 样式继承:父传子,子传孙
LVGL的样式继承机制,我觉得是它最聪明的地方之一。
默认情况下,子控件会继承父控件的样式。比如你给屏幕设置了一个背景色,屏幕上的所有控件都会默认使用这个背景色——除非你显式覆盖。
/* 给屏幕设置背景 */
lv_style_t style_scr;
lv_style_init(&style_scr);
lv_style_set_bg_color(&style_scr, lv_color_hex(0x2C3E50));
lv_obj_add_style(lv_scr_act(), &style_scr, 0);
/* 按钮会自动继承屏幕的背景?不会! */
/* 因为按钮有自己的默认样式,继承只对「未设置」的属性生效 */
为什么会这样?因为LVGL的继承是「按属性」的。子控件只继承父控件中「自己没设置」的属性。你想想看,如果按钮完全继承父控件的背景色,那所有按钮都变成屏幕色了,这显然不合理。
避坑指南:我曾经在做一个多级菜单时,给父容器设置了字体大小,以为子标签会自动继承。结果发现标签的字体没变。排查后发现,标签控件有自己的默认字体设置,覆盖了继承。解决办法是:要么在标签上显式设置字体,要么用「本地样式」覆盖默认值。
4.4 本地样式 vs 全局样式
这是样式系统的「双刃剑」,用好了事半功倍,用不好代码一团糟。
| 类型 | 作用范围 | 修改影响 | 典型用途 |
|---|---|---|---|
| 本地样式 | 单个对象 | 只影响该对象 | 特殊按钮、个性化控件 |
| 全局样式 | 所有同类对象 | 一改全改 | 主题统一、品牌色 |
本地样式:直接用 lv_obj_set_style_xxx() 系列函数设置。它只影响当前对象,不影响其他同类型控件。
/* 本地样式:只对这个按钮生效 */
lv_obj_set_style_bg_color(btn_special, lv_color_hex(0xFF5722), 0);
全局样式:通过 lv_theme_t 或者给「样式类」添加样式来实现。最常用的方法是给控件添加 CSS 类名(LVGL 里叫 lv_style_t 的类),然后统一设置。
/* 定义一个「危险按钮」样式类 */
lv_style_t style_danger;
lv_style_init(&style_danger);
lv_style_set_bg_color(&style_danger, lv_color_hex(0xF44336));
/* 给多个按钮添加这个类 */
lv_obj_add_style(btn_delete, &style_danger, 0);
lv_obj_add_style(btn_reset, &style_danger, 0);
lv_obj_add_style(btn_logout, &style_danger, 0);
/* 想改所有危险按钮的颜色?改 style_danger 就行了! */
lv_style_set_bg_color(&style_danger, lv_color_hex(0xD32F2F));
/* 所有应用了这个样式的按钮都会自动更新 */
我的建议:项目初期就规划好「全局样式」和「本地样式」的边界。全局样式用来定义品牌色、基础字体、默认间距。本地样式只处理特殊情况。这样后期改主题时,你只需要改几个全局样式,而不是翻遍整个代码库。
4.5 实战经验:样式优先级
样式多了,难免有冲突。LVGL 的优先级规则其实很简单:
- 本地样式 > 全局样式:显式设置的样式优先级最高
- 具体状态 > 通用状态:比如
LV_STATE_PRESSED的样式优先级高于默认状态 - 后添加的样式 > 先添加的样式:如果同一个属性被多次设置,最后一次生效
我记得有一次调试,按钮颜色怎么都不对。明明设置了红色,显示出来却是蓝色。后来发现是全局样式里有一个蓝色背景,而且全局样式的添加顺序在本地样式之后,把本地样式覆盖了。调整了一下添加顺序,问题解决。
调试小技巧:如果样式不生效,可以用 lv_obj_get_style_bg_color(btn, 0) 这样的函数来读取实际生效的样式值。这比肉眼猜靠谱多了。
好了,样式系统的基础就这些。记住一句话:样式是控件的衣服,继承是家族的基因,全局是公司的制服,本地是个人的搭配。下一章我们聊聊「样式动画」,让控件动起来!