第四章 样式系统入门:样式是什么、样式属性、样式状态、样式继承与层叠
好,咱们今天聊聊LVGL的样式系统。说实话,我刚接触LVGL那会儿,最头疼的就是样式。为什么?因为它的设计思路跟传统的CSS很像,但又不太一样。你如果之前做过Web开发,会觉得亲切;如果没做过,嗯,可能会有点懵。不过别担心,我带你一步步捋清楚。
4.1 样式到底是什么?
简单说,样式就是控件的「皮肤」。它决定了按钮长什么样、文字用什么颜色、边框有多粗。在LVGL里,样式是一个结构体——lv_style_t。你创建一个样式对象,往里面塞各种属性,然后把它赋给某个控件。
我个人习惯把样式想象成「一套配置」。比如我做一个红色按钮,我就定义一个样式,里面设置背景色为红色、圆角为4像素、文字为白色。然后把这个样式加到按钮上,按钮就变红了。就这么简单。
核心概念:样式 = 属性集合。一个样式可以包含多个属性,一个控件可以应用多个样式。
4.2 样式属性有哪些?
LVGL的样式属性非常多,但常用的也就那么二三十个。我按类别给你列一下:
| 类别 | 常用属性 | 说明 |
|---|---|---|
| 背景 | bg_color, bg_opa, bg_grad_color |
背景颜色、透明度、渐变色 |
| 边框 | border_color, border_width, border_side |
边框颜色、宽度、显示哪几条边 |
| 文字 | text_color, text_font, text_align |
文字颜色、字体、对齐方式 |
| 尺寸 | width, height, pad_top, pad_left |
宽高、内边距 |
| 阴影 | shadow_color, shadow_width, shadow_ofs_x |
阴影颜色、宽度、偏移 |
| 圆角 | radius |
所有角的圆角半径 |
| 变换 | transform_angle, transform_zoom |
旋转角度、缩放比例 |
你看,是不是很直观?每个属性都有对应的setter函数。比如设置背景色:
lv_style_set_bg_color(&style, lv_color_hex(0xFF0000));
这里要注意一点:属性必须通过专门的setter函数来设置,不能直接修改结构体成员。为什么?因为LVGL内部有一些缓存和标记机制,直接改结构体可能会导致样式不生效。我曾经踩过这个坑,调试了半天才发现是直接赋值了。
4.3 样式状态:不只是「正常」和「按下」
一个按钮有不同状态:正常、按下、聚焦、禁用……LVGL把这些状态叫做「样式状态」。每个状态可以单独设置样式属性。
举个例子,按钮按下去时颜色变深:
/* 正常状态 */
lv_style_set_bg_color(&style_pr, lv_palette_main(LV_PALETTE_BLUE));
/* 按下状态 */
lv_style_set_bg_color(&style_pr, lv_palette_darken(LV_PALETTE_BLUE, 2));
/* 将样式添加到控件的不同状态 */
lv_obj_add_style(btn, &style_def, LV_STATE_DEFAULT);
lv_obj_add_style(btn, &style_pr, LV_STATE_PRESSED);
状态是用位掩码表示的。常用的状态有:
LV_STATE_DEFAULT(0x00) — 默认状态LV_STATE_PRESSED(0x01) — 按下LV_STATE_FOCUSED(0x02) — 聚焦LV_STATE_DISABLED(0x08) — 禁用LV_STATE_CHECKED(0x10) — 选中(比如复选框)
你可以组合多个状态,比如 LV_STATE_PRESSED | LV_STATE_FOCUSED。不过说实话,我很少用组合状态,大部分场景下单个状态就够了。
小技巧:如果你想让某个状态下的样式「不生效」,可以设置该状态的样式为NULL。LVGL会自动回退到默认状态。
4.4 样式继承:子控件会「学」父控件
这个特性很有意思。LVGL的样式可以继承——子控件会自动使用父控件的某些样式属性。但不是所有属性都能继承,只有那些标记为「可继承」的属性才行。
哪些属性可以继承?主要是文字相关的:text_color、text_font、text_align 等。背景色、边框这些是不会继承的。
举个例子:
/* 父容器设置文字颜色为红色 */
lv_style_set_text_color(&style_parent, lv_color_hex(0xFF0000));
lv_obj_add_style(parent, &style_parent, LV_STATE_DEFAULT);
/* 子按钮没有单独设置文字颜色 */
lv_obj_t *btn = lv_btn_create(parent);
lv_obj_t *label = lv_label_create(btn);
lv_label_set_text(label, "Hello");
/* 这个label的文字会自动变成红色 */
为什么会这样?因为label没有设置自己的文字颜色,它就问父控件要。父控件也没有?那就继续往上问,直到找到为止。如果一直找不到,就用默认值。
我在项目中遇到过一个问题:一个弹窗里的所有文字都变成了蓝色,排查了半天,发现是弹窗的父容器设置了蓝色文字,而弹窗里的label没有单独设置颜色。嗯,这就是继承「惹的祸」。解决方案很简单:要么在label上显式设置颜色,要么在父容器上把继承关掉。
4.5 样式层叠:谁说了算?
层叠,说白了就是「优先级」。当一个控件有多个样式时,到底用哪个?LVGL有一套明确的规则:
- 优先级从低到高: 默认样式 < 局部样式 < 本地样式
- 同优先级下: 后添加的样式覆盖先添加的
- 状态优先级: 更具体的状态覆盖更通用的状态
具体来说,样式有三种来源:
- 默认样式: LVGL内置的,比如按钮默认是灰色的
- 局部样式: 通过
lv_obj_add_style()添加到控件上的 - 本地样式: 通过
lv_obj_set_style_xxx()直接设置的
本地样式的优先级最高。如果你用 lv_obj_set_style_bg_color(btn, lv_color_hex(0x00FF00), LV_STATE_DEFAULT),那不管你怎么加样式,背景色都是绿色。
注意: 本地样式和局部样式不要混用。我见过有人用 lv_obj_add_style() 加了一个样式,又用 lv_obj_set_style_bg_color() 单独设置背景色,结果调试时搞不清哪个生效。建议统一用一种方式。
4.6 实战:做一个带状态的按钮
光说不练假把式。咱们写一个完整的例子:一个按钮,正常时蓝色,按下时深蓝,禁用时灰色。
/* 定义样式 */
static lv_style_t style_def;
static lv_style_t style_pr;
static lv_style_t style_dis;
void my_button_demo(void)
{
/* 初始化样式 */
lv_style_init(&style_def);
lv_style_init(&style_pr);
lv_style_init(&style_dis);
/* 正常状态:蓝色背景,白色文字,圆角 */
lv_style_set_bg_color(&style_def, lv_palette_main(LV_PALETTE_BLUE));
lv_style_set_text_color(&style_def, lv_color_white());
lv_style_set_radius(&style_def, 4);
/* 按下状态:深蓝色 */
lv_style_set_bg_color(&style_pr, lv_palette_darken(LV_PALETTE_BLUE, 2));
/* 禁用状态:灰色背景,文字变淡 */
lv_style_set_bg_color(&style_dis, lv_palette_darken(LV_PALETTE_GREY, 1));
lv_style_set_text_color(&style_dis, lv_color_hex(0xAAAAAA));
/* 创建按钮 */
lv_obj_t *btn = lv_btn_create(lv_scr_act());
lv_obj_set_size(btn, 120, 40);
lv_obj_center(btn);
/* 添加样式到不同状态 */
lv_obj_add_style(btn, &style_def, LV_STATE_DEFAULT);
lv_obj_add_style(btn, &style_pr, LV_STATE_PRESSED);
lv_obj_add_style(btn, &style_dis, LV_STATE_DISABLED);
/* 添加标签 */
lv_obj_t *label = lv_label_create(btn);
lv_label_set_text(label, "点击我");
lv_obj_center(label);
}
这段代码跑起来,你就能看到一个完整的、带状态切换的按钮。你想想看,如果没有样式系统,你要怎么实现?可能得在事件回调里手动改颜色,代码会变得很乱。有了样式系统,一切都清晰了。
4.7 避坑指南
最后,分享几个我踩过的坑:
- 样式一定要初始化: 用
lv_style_init(),否则会崩溃。我记得第一次用LVGL时忘了这步,程序直接hard fault。 - 样式变量要静态或全局: 不要在函数内定义局部样式变量,函数退出后样式就没了。控件会变成「无样式」状态。
- 状态掩码不要搞错:
LV_STATE_PRESSED是0x01,不是0x02。我曾经把聚焦和按下搞反了,按钮按下去没反应,聚焦时反而变色。 - 继承不是万能的: 只有文字相关属性会继承。如果你想继承背景色,得自己手动设置。
好了,样式系统的基础就这些。下一章咱们聊聊更高级的样式用法——主题和动画。到时候你会发现,样式系统其实很强大,只是需要一点时间熟悉它的「脾气」。