3、LVGL基础对象(lv_obj):对象的创建与删除、父子层级关系、大小与位置、对齐方式、样式基础

好,咱们今天来啃LVGL最核心的一块——基础对象,也就是 lv_obj。你想想看,整个LVGL的界面,说白了就是一堆对象堆出来的。按钮是对象,标签是对象,滑块也是对象。它们都继承自同一个祖宗——lv_obj。

我刚开始学LVGL的时候,觉得这玩意儿不就是new一个对象嘛,有啥好讲的?结果真上手做项目,发现对象创建了不显示、父子关系搞乱了、对齐方式选错了……各种坑踩了个遍。所以这一章,咱们把基础对象彻底捋清楚。

3.1 对象的创建与删除

创建对象,就一个函数:

lv_obj_t * lv_obj_create(lv_obj_t * parent);

这个 parent 参数,就是对象的父对象。传 NULL 的话,它会挂到屏幕的根对象上。我个人习惯,从来不传 NULL,哪怕暂时不知道放哪,也会先创建一个容器对象当父级。为什么?因为后面调整布局会方便很多。

删除对象更简单:

void lv_obj_del(lv_obj_t * obj);

注意,这个函数会递归删除所有子对象。我曾经在项目里犯过一个错——删了一个父对象,结果它下面的子对象全没了,而我还拿着子对象的指针到处用,直接硬核死机。嗯,这里要注意:对象删除后,所有指向它的指针都变成野指针。

避坑指南: 我曾经在删除对象后没有置空指针,导致后续代码访问了非法内存。建议删除后立即将指针置为 NULL。

如果你想延迟删除,可以用:

void lv_obj_del_async(lv_obj_t * obj);

这个函数会在下一个事件循环中执行删除,适合在中断或回调里调用。

3.2 父子层级关系

LVGL的对象组织方式,就是一棵树。每个对象可以有多个子对象,但只有一个父对象。根节点是 lv_scr_act(),也就是当前活动屏幕。

为什么要搞父子关系?说白了,就是继承和联动。父对象移动,子对象跟着动;父对象隐藏,子对象全隐藏;父对象被删除,子对象全完蛋。

常用的父子操作函数:

功能 函数
获取父对象 lv_obj_get_parent(obj)
获取子对象(按索引) lv_obj_get_child(obj, idx)
获取子对象数量 lv_obj_get_child_cnt(obj)
将对象移到顶层 lv_obj_move_foreground(obj)
将对象移到底层 lv_obj_move_background(obj)

我记得有一次做仪表盘界面,需要把报警弹窗显示在最前面。当时没调层级,弹窗被其他控件挡住了。后来用了 lv_obj_move_foreground,问题就解决了。你想想看,这种小细节,不知道的话能折腾半天。

小技巧: 如果你想让某个对象始终在最上层,可以在它的父对象上调用 lv_obj_move_foreground,或者在创建时把它放在最后创建——后创建的对象默认在更上层。

3.3 大小与位置

每个对象都有两个核心属性:大小(width, height)和位置(x, y)。

设置大小:

lv_obj_set_width(obj, 100);
lv_obj_set_height(obj, 50);
lv_obj_set_size(obj, 100, 50);  // 一次性设置宽高

设置位置:

lv_obj_set_x(obj, 10);
lv_obj_set_y(obj, 20);
lv_obj_set_pos(obj, 10, 20);    // 一次性设置x和y

这里有个关键点:位置是相对于父对象的。如果你把父对象移动了,子对象的位置(相对于父对象)不变,但它在屏幕上的绝对位置会变。

我刚开始做项目时,经常搞混这个。明明设置了坐标,对象却跑到了奇怪的地方。后来才意识到,是父对象的坐标变了。所以,调试位置问题时,先检查父对象。

核心概念: LVGL中的坐标都是相对于父对象的。绝对坐标可以通过 lv_obj_get_x(obj) 和 lv_obj_get_y(obj) 获取,但这两个函数返回的其实是相对于父对象的坐标。要获取屏幕绝对坐标,需要用 lv_obj_get_x_aligned(obj) 或自己递归计算。

3.4 对齐方式

手动算坐标太累了,LVGL提供了对齐功能。说白了,就是让对象自动摆好位置。

最常用的对齐函数:

lv_obj_align(obj, LV_ALIGN_CENTER, 0, 0);

这个函数的意思:把 obj 相对于它的父对象居中对齐,偏移量为 (0, 0)。

常用的对齐常量:

常量 含义
LV_ALIGN_CENTER 居中
LV_ALIGN_TOP_LEFT 左上角
LV_ALIGN_TOP_MID 顶部居中
LV_ALIGN_TOP_RIGHT 右上角
LV_ALIGN_BOTTOM_LEFT 左下角
LV_ALIGN_BOTTOM_MID 底部居中
LV_ALIGN_BOTTOM_RIGHT 右下角
LV_ALIGN_LEFT_MID 左侧居中
LV_ALIGN_RIGHT_MID 右侧居中

还有一个更灵活的函数:

lv_obj_align_to(obj, base_obj, LV_ALIGN_OUT_RIGHT_TOP, 10, 0);

这个是把 obj 对齐到 base_obj 的右侧顶部,再偏移 (10, 0)。非常适合做“在某个控件旁边显示”的效果。

我记得做菜单界面时,需要让子菜单出现在父菜单项的右侧。用 lv_obj_align_to 配合 LV_ALIGN_OUT_RIGHT_TOP,一行代码搞定。如果手动算坐标,每个菜单项的位置都得重新算,太麻烦了。

小技巧: 对齐函数中的偏移量 (x, y) 是相对于对齐点计算的。比如 LV_ALIGN_CENTER 偏移 (10, 0),就是居中后再向右移10像素。这个偏移量可以是负数,向左或向上移动。

3.5 样式基础

样式,就是对象的外观。LVGL的样式系统非常强大,但咱们先从最基础的开始。

设置样式,需要先创建一个样式:

lv_style_t style;
lv_style_init(&style);
lv_style_set_bg_color(&style, lv_color_hex(0x3498db));
lv_style_set_radius(&style, 5);

然后把这个样式添加到对象上:

lv_obj_add_style(obj, &style, LV_STATE_DEFAULT);

第三个参数是状态,比如 LV_STATE_PRESSED(按下状态)、LV_STATE_DISABLED(禁用状态)。同一个对象可以有不同的样式对应不同的状态。

常用的样式属性:

属性 设置函数 说明
背景颜色 lv_style_set_bg_color 设置背景色
圆角半径 lv_style_set_radius 设置圆角大小
边框宽度 lv_style_set_border_width 设置边框粗细
边框颜色 lv_style_set_border_color 设置边框颜色
内边距 lv_style_set_pad_all 设置内边距
透明度 lv_style_set_opa 设置透明度(0-255)

这里有个坑:样式是全局共享的。如果你修改了一个样式对象,所有使用这个样式的对象都会跟着变。我曾经在项目里犯过这个错——改了一个按钮的样式,结果所有按钮都变了样。嗯,后来我学会了,每个独立的对象最好用独立的样式实例,或者用 lv_style_copy 复制一份。

避坑指南: 样式对象必须保持有效,直到所有使用它的对象被删除。如果你在函数内创建了一个局部样式,函数返回后样式就销毁了,但对象还在引用它——这会导致崩溃。我曾经因为这个bug排查了一整天。建议把样式声明为全局或静态变量。

3.6 知识体系总览

好了,上面讲了这么多,咱们用一张图把本章的知识体系串起来:

LVGL基础对象(lv_obj)知识体系 lv_obj 基础对象 创建与删除 父子层级关系 大小与位置 对齐方式 样式基础 核心要点: 1. 所有控件都继承自 lv_obj,掌握基础对象就掌握了LVGL的根基 2. 父子层级决定对象的生命周期和坐标系统 3. 对齐方式可以大幅减少手动计算坐标的工作量

这张图把本章的五个核心知识点串在了一起。你可以看到,lv_obj 是中心,向外辐射出创建删除、父子层级、大小位置、对齐方式和样式基础。这五个点,就是你在LVGL中操作任何控件的基础。

好了,这一章的内容就到这。记住,基础对象是所有控件的根,把这一章吃透了,后面学按钮、标签、滑块什么的,你会发现它们都差不多——只是多了几个专有属性和事件而已。