4. LVGL对象与样式:从零搭建你的第一个控件
LVGL 里的一切,都是对象。你看到的按钮、标签、滑块、进度条……底层全是 lv_obj_t。我刚开始接触 LVGL 时,总觉得这概念有点虚,直到自己动手画了一个按钮,才明白——对象就是一块画布,样式就是画笔和颜料。
这一章,咱们就把这两件事彻底说清楚。
4.1 lv_obj_t 对象模型
每个控件都是一个 lv_obj_t 结构体。它不关心你画的是什么,只负责三件事:
- 父子关系:对象可以嵌套,子对象跟随父对象移动、裁剪。
- 位置与大小:坐标、宽高、对齐方式。
- 事件与样式:谁点了它?它长什么样?
创建对象的套路很简单:
lv_obj_t * btn = lv_btn_create(lv_scr_act()); // 在活动屏幕上创建按钮
lv_obj_set_size(btn, 120, 50); // 设置宽高
lv_obj_align(btn, LV_ALIGN_CENTER, 0, 0); // 居中显示
嗯,这里要注意:lv_scr_act() 返回当前活动屏幕。所有对象都得挂在一个父对象上,屏幕就是最顶层的父对象。
我的习惯:创建复杂界面时,我会先画一张对象树草图。父容器、子容器、控件层级一目了然。省得后面调布局调到头秃。
4.2 基本属性:大小、位置、颜色
对象的基本属性,说白了就三组 API:
| 属性 | 常用函数 | 说明 |
|---|---|---|
| 大小 | lv_obj_set_width/height/size |
单位是像素,支持百分比 |
| 位置 | lv_obj_set_pos/x/y/align |
相对父对象坐标,或对齐方式 |
| 颜色 | lv_obj_set_style_bg_color |
通过样式设置,不是直接赋值 |
举个例子:
lv_obj_t * label = lv_label_create(btn);
lv_label_set_text(label, "Click Me");
lv_obj_center(label); // 相对于父对象居中
你可能会问:为什么颜色不能直接 lv_obj_set_color?
因为 LVGL 把颜色归到了样式系统里。对象本身不存颜色,它只存一个指向样式的指针。这样设计的好处是——多个对象可以共用同一个样式,改一处就全变了。
我曾经踩过的坑:直接修改了某个对象的背景色,结果发现同组按钮全变了。后来才意识到,它们共用了一个样式指针。解决方案很简单:用
lv_style_copy 或 lv_obj_add_style 时指定局部样式。
4.3 样式系统:lv_style_t
样式是 LVGL 的灵魂。一个 lv_style_t 可以控制:
- 背景颜色、渐变、透明度
- 边框宽度、颜色、圆角
- 文字颜色、字体、对齐
- 阴影、轮廓、内边距
- ……总共上百个属性
使用样式的标准流程:
static 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_border_width(&style_btn, 2);
lv_style_set_border_color(&style_btn, lv_color_hex(0x1976D2));
lv_obj_add_style(btn, &style_btn, 0); // 0 表示默认状态
这里有个关键点:lv_obj_add_style 的第三个参数是状态。你可以为同一个对象的不同状态设置不同样式:
| 状态 | 宏定义 | 典型用途 |
|---|---|---|
| 默认 | LV_STATE_DEFAULT |
普通显示 |
| 按下 | LV_STATE_PRESSED |
按钮被按下时变色 |
| 聚焦 | LV_STATE_FOCUSED |
键盘/遥控器选中时高亮 |
| 禁用 | LV_STATE_DISABLED |
变灰不可操作 |
你想想看,一个按钮在不同状态下自动切换样式,是不是比你自己写 if-else 去改颜色优雅多了?
4.4 主题定制:让界面风格统一
项目做到后期,最怕什么?最怕每个工程师各写各的样式。按钮 A 圆角 6px,按钮 B 圆角 8px,标签字体大小还不一样……
主题就是来解决这个问题的。LVGL 支持两种主题方式:
- 内置主题:
lv_theme_default_init,改几个颜色就能用。 - 自定义主题:自己写回调函数,统一创建样式。
我一般用自定义主题,因为控制力更强。大致思路:
static lv_theme_t * my_theme;
static lv_style_t style_btn_theme;
void my_theme_init(void) {
lv_style_init(&style_btn_theme);
lv_style_set_bg_color(&style_btn_theme, lv_palette_main(LV_PALETTE_BLUE));
lv_style_set_radius(&style_btn_theme, 4);
// 注册主题回调
my_theme = lv_theme_my_init(
lv_disp_get_default(),
lv_palette_main(LV_PALETTE_BLUE),
lv_palette_main(LV_PALETTE_GREY),
NULL
);
lv_disp_set_theme(lv_disp_get_default(), my_theme);
}
之后所有新创建的按钮,都会自动应用主题里的样式。你只需要在主题里定义一次,全局生效。
核心思路:主题 = 一组预定义的样式集合。它不直接修改对象,而是作为对象的默认样式。对象可以覆盖主题样式,但主题保证了整体一致性。
4.5 知识体系总览
下面这张图,把本章的核心逻辑串起来了:
4.6 避坑指南与实战建议
最后,分享几个我实际项目中遇到的教训:
- 样式变量必须是静态或全局的:局部变量在函数退出后会被销毁,对象指针就变成了野指针。我见过有人把样式定义在函数里,结果界面一刷新就花屏。
- 不要滥用
lv_obj_set_style_*系列函数:每次调用都会产生一次样式更新开销。批量设置时,先改样式对象,再一次性lv_obj_add_style效率更高。 - 主题优先级低于局部样式:如果你在对象上单独设置了样式,它会覆盖主题里的同名属性。这是故意的——让你在统一中保留灵活性。
- 调试时用
lv_style_dump:打印出当前样式的所有属性值,排查问题非常方便。
我的小技巧:在项目初期就定义好主题色板。主色、辅色、警告色、背景色各一个宏。后面所有样式都引用这些宏,改主题时只改一处,全界面跟着变。