1、LVGL基础回顾:LVGL简介、对象树与父子关系、事件处理机制、样式系统概述
1.1 LVGL是什么?我为什么选它?
LVGL,全称Light and Versatile Graphics Library。说白了,就是一个专门为嵌入式设备打造的图形库。
我最早接触LVGL是在一个智能家居面板项目上。那时候MCU资源紧张,RAM只有64KB,Flash 256KB。用传统GUI方案?想都别想。但LVGL跑起来,流畅得让我意外。
它的核心优势就三点:
- 轻量:最小配置下,Flash占用不到100KB,RAM几十KB就能跑
- 丰富:内置30+控件,按钮、滑块、图表、键盘……基本覆盖日常需求
- 可定制:样式系统极其灵活,你可以把任何控件改得面目全非
嗯,这里要注意:LVGL不是万能的。它适合资源受限的嵌入式场景,但如果你要做复杂3D动画,那还是得找别的方案。
1.2 对象树与父子关系——UI的骨架
LVGL里所有可见元素都是对象(lv_obj_t)。它们组织成一棵树,这就是对象树。
你想想看,一个屏幕(lv_scr_act())是根节点。上面放一个面板(lv_obj),面板里放按钮(lv_btn),按钮里放标签(lv_label)。这就是典型的父子关系。
核心规则:
- 子对象的位置、大小,默认是相对于父对象的
- 子对象会继承父对象的某些样式属性
- 父对象被删除,所有子对象也会被自动删除
我在项目中遇到过一个问题:创建了上百个按钮,每个都手动设置位置。后来发现,只要把按钮放到一个容器(lv_obj)里,统一设置容器的布局(lv_layout),代码量直接减少80%。
看个简单例子:
// 创建父对象(屏幕)
lv_obj_t * scr = lv_scr_act();
// 创建面板作为子对象
lv_obj_t * panel = lv_obj_create(scr);
lv_obj_set_size(panel, 200, 150);
lv_obj_center(panel);
// 在面板里创建按钮
lv_obj_t * btn = lv_btn_create(panel);
lv_obj_set_size(btn, 80, 40);
lv_obj_align(btn, LV_ALIGN_CENTER, 0, 0);
看到没?btn的父对象是panel,不是scr。所以btn的位置是相对于panel的。这就是父子关系的精髓。
我的习惯:每个功能模块创建一个独立的容器对象。这样管理起来清晰,调试也方便。你可以在LVGL的调试工具里看到完整的对象树结构。
1.3 事件处理机制——UI的灵魂
UI没有事件响应,那就是一张图片。LVGL的事件机制,说白了就是「谁发生了什么事,谁来处理」。
事件类型很多,常用的有:
| 事件类型 | 触发时机 | 典型场景 |
|---|---|---|
| LV_EVENT_CLICKED | 点击后释放 | 按钮点击 |
| LV_EVENT_PRESSING | 正在按下 | 长按检测 |
| LV_EVENT_VALUE_CHANGED | 值改变 | 滑块拖动 |
| LV_EVENT_RELEASED | 手指抬起 | 触摸结束 |
事件绑定的方式很简单:
static void btn_event_cb(lv_event_t * e) {
lv_event_code_t code = lv_event_get_code(e);
if(code == LV_EVENT_CLICKED) {
LV_LOG_USER("按钮被点击了!");
// 这里写你的业务逻辑
}
}
// 绑定事件
lv_obj_add_event_cb(btn, btn_event_cb, LV_EVENT_CLICKED, NULL);
我曾经踩过一个坑:在事件回调里直接修改了正在处理事件的控件属性,结果导致死循环。后来我养成了一个习惯——事件回调里只做标记,真正的逻辑放到主循环里处理。
注意:事件回调中不要做耗时操作,比如延时、大量计算。否则UI会卡死。如果必须做,请使用lv_timer或异步处理。
1.4 样式系统概述——UI的外衣
LVGL的样式系统,是我见过嵌入式GUI里最灵活的。它不像某些库那样用CSS,而是用C语言的结构体来定义样式。
一个样式(lv_style_t)可以设置:
- 背景颜色、渐变、透明度
- 边框宽度、颜色、圆角
- 文字颜色、字体、对齐方式
- 阴影、轮廓、内边距
- ……总共100+属性
样式的核心概念是「叠加」。你可以给一个对象添加多个样式,每个样式有不同的优先级。优先级高的覆盖优先级低的。
// 创建样式
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_text_color(&style_btn, lv_color_white());
// 应用到按钮
lv_obj_add_style(btn, &style_btn, 0);
这里有个细节:lv_obj_add_style的第三个参数是选择器(selector)。0表示默认状态。你还可以设置LV_STATE_PRESSED(按下状态)、LV_STATE_DISABLED(禁用状态)等。
我记得有个项目需要按钮按下时颜色变深。一开始我直接在事件回调里改颜色,后来发现用样式状态切换更优雅:
static 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);
你看,两行代码搞定。不用写任何事件处理逻辑。
我的建议:把样式定义成全局静态变量,在初始化时统一配置。这样后期改主题色,只需要改一个地方。别像我早期那样,每个控件单独设置颜色,改起来想哭。
1.5 小结
这一章我们回顾了LVGL的四个基础概念:
- 对象树:UI的骨架,父子关系决定布局和生命周期
- 事件机制:UI的灵魂,回调函数处理用户交互
- 样式系统:UI的外衣,灵活叠加,状态切换
这些是后续自定义控件开发的地基。地基打不牢,后面盖楼会歪。下一章,我们开始动手搭建第一个自定义控件。
嗯,如果你现在对LVGL还不太熟,建议先拿官方示例跑一跑。我当年就是一边看文档一边改demo,慢慢就上手了。