4、LVGL对象系统:对象模型、对象树、对象生命周期、对象属性与样式
LVGL 的核心,说白了就是它的对象系统。你想想看,屏幕上每一个按钮、每一个滑块、每一张图片,本质上都是一个对象。我刚开始接触 LVGL 时,觉得它跟其他 GUI 框架差不多,不就是创建控件、设置属性嘛。但深入用下去才发现,它的对象模型设计得相当精巧,尤其是对象树和生命周期管理,用好了能省不少事。
4.1 对象模型:一切皆对象
LVGL 里所有的可视化元素都继承自 lv_obj_t 这个基类。你可以把它理解成「万物之源」。无论是标签、按钮还是图表,底层都是一个 lv_obj_t 结构体,再加上各自特有的数据。
核心概念:LVGL 的对象模型采用单继承的类结构。所有控件都是 lv_obj_t 的子类,通过 C 语言的结构体嵌套实现「继承」效果。
我记得第一次看源码时,发现 lv_obj_t 里包含了坐标、父对象指针、子对象链表、样式指针、事件回调等字段。每个控件创建时,底层都会先分配一个 lv_obj_t,然后再附加控件特有的数据。这种设计让整个框架非常统一。
// 创建一个按钮对象
lv_obj_t *btn = lv_btn_create(lv_scr_act());
// 创建一个标签对象
lv_obj_t *label = lv_label_create(btn);
// 设置标签文本
lv_label_set_text(label, "Click me");
你看,按钮和标签的创建方式完全一样,都是 lv_xxx_create(parent) 的形式。这就是对象模型统一性的体现。
4.2 对象树:父子关系与层级管理
LVGL 的对象不是孤立的,它们以树形结构组织在一起。每个对象都有一个父对象,也可以有多个子对象。这棵树从根节点 lv_scr_act()(当前活动屏幕)开始,向下延伸到每一个控件。
我的习惯:在复杂界面中,我会刻意利用对象树来管理显示层级。比如弹窗放在屏幕的子节点中,遮罩层放在弹窗和屏幕之间,这样 z-order 自然就对了。
为什么会用树结构?因为 GUI 天然就是树状的——一个窗口包含多个面板,每个面板又包含按钮和标签。对象树让事件传递、样式继承、自动销毁都变得非常自然。
// 创建对象树结构
lv_obj_t *scr = lv_scr_act(); // 根节点:当前屏幕
lv_obj_t *panel = lv_obj_create(scr); // 第一层:面板
lv_obj_t *btn = lv_btn_create(panel); // 第二层:按钮
lv_obj_t *label = lv_label_create(btn); // 第三层:标签
这张图展示了 LVGL 对象树的典型结构:
4.3 对象生命周期:从创建到销毁
每个 LVGL 对象都要经历三个阶段:创建、使用、销毁。听起来简单,但里面有不少坑。
| 阶段 | 关键函数 | 说明 |
|---|---|---|
| 创建 | lv_xxx_create(parent) |
分配内存、初始化属性、添加到父对象的子链表 |
| 使用 | 各种 setter/getter | 修改坐标、样式、事件回调等 |
| 销毁 | lv_obj_del(obj) |
从父对象移除、释放子对象、释放内存 |
我曾经踩过的坑:在事件回调中直接删除触发事件的对象,会导致野指针崩溃。正确的做法是用 lv_event_send() 的延迟删除机制,或者用 lv_async_call() 推迟删除操作。
销毁对象时,LVGL 会递归删除所有子对象。这意味着你只需要删除父容器,它下面的所有控件都会被自动清理。这个设计在动态界面中特别有用——切换页面时,直接删除旧屏幕的所有子对象即可。
// 安全删除对象的推荐做法
void my_event_handler(lv_event_t *e) {
lv_obj_t *obj = lv_event_get_target(e);
// 不要直接在这里调用 lv_obj_del(obj)
// 而是用异步删除
lv_async_call((lv_async_cb_t)lv_obj_del, obj);
}
4.4 对象属性与样式
LVGL 的对象属性分为两类:一类是通用属性(坐标、大小、透明度等),另一类是控件特有属性(比如滑块的取值范围、标签的文本内容)。
通用属性通过 lv_obj_set_xxx() 系列函数设置:
lv_obj_t *btn = lv_btn_create(lv_scr_act());
lv_obj_set_pos(btn, 50, 100); // 设置位置
lv_obj_set_size(btn, 120, 40); // 设置大小
lv_obj_set_opa(btn, 200); // 设置透明度 (0-255)
lv_obj_set_style_bg_color(btn, lv_color_hex(0x2196F3), 0); // 设置背景色
样式是 LVGL 的一大特色。它不像传统 GUI 那样直接给对象设置颜色和字体,而是通过「样式」来管理。一个对象可以叠加多个样式,优先级由样式选择器决定。
我建议:把通用样式定义在主题中,局部样式只覆盖需要变化的部分。这样修改主题时,所有控件会自动更新,不用一个个改。
样式系统支持级联覆盖:
- 本地样式:直接通过
lv_obj_set_style_xxx()设置,优先级最高 - 主题样式:通过
lv_theme_set_xxx()设置,影响所有未覆盖的对象 - 默认样式:LVGL 内置的兜底样式
举个例子,你给按钮设置了红色背景,但主题中按钮默认是蓝色。最终显示红色,因为本地样式覆盖了主题样式。这种机制让界面定制非常灵活。
// 样式覆盖示例
lv_obj_t *btn1 = lv_btn_create(lv_scr_act());
lv_obj_t *btn2 = lv_btn_create(lv_scr_act());
// btn1 使用主题默认样式(蓝色)
// btn2 覆盖为红色
lv_obj_set_style_bg_color(btn2, lv_color_hex(0xFF0000), 0);
样式还支持状态(正常、按下、禁用、聚焦等)。你可以为不同状态设置不同样式,LVGL 会自动切换:
// 为按钮的不同状态设置样式
lv_obj_set_style_bg_color(btn, lv_color_hex(0x2196F3), LV_STATE_DEFAULT);
lv_obj_set_style_bg_color(btn, lv_color_hex(0x1976D2), LV_STATE_PRESSED);
lv_obj_set_style_bg_color(btn, lv_color_hex(0xBBDEFB), LV_STATE_DISABLED);
核心要点:对象属性控制「是什么」,样式控制「长什么样」。两者分离,让代码更清晰,也更容易维护。
嗯,对象系统这块内容不少,但理解了对象树和生命周期,后面学布局、事件、动画都会轻松很多。我个人觉得,花时间把对象模型吃透,是学好 LVGL 最值得的投入。