4. 对象与基础属性:lv_obj_t的创建与销毁,位置、大小、对齐方式、父对象关系

好,我们进入第四章。这一章是整个LVGL的基石——对象。

你想想看,屏幕上任何一个按钮、一个标签、一个滑块,本质上都是什么?都是一个 lv_obj_t 结构体实例。说白了,LVGL里的一切都是对象。你学会了怎么摆弄这个对象,后面所有控件都是信手拈来。

4.1 对象的创建与销毁

先看创建。LVGL里创建一个对象,只需要一行代码:

lv_obj_t * btn = lv_obj_create(lv_scr_act());

这个 lv_obj_create() 函数接收一个参数——父对象指针。返回值是一个指向新对象的指针。

我刚开始用LVGL时,总觉得这玩意儿跟malloc一样要手动释放内存。其实不是。LVGL有自己的内存管理机制,但销毁对象时你确实需要主动调用:

lv_obj_del(btn);

调用完这行,btn指向的对象就被销毁了,内存也回收了。注意,销毁后不要再使用这个指针,否则就是野指针访问,大概率会硬故障。

我曾经踩过的坑:在定时器回调里直接销毁了一个对象,但定时器还在引用它。结果下一次定时器触发时,程序直接崩溃。后来我学乖了——销毁对象前,先确保所有引用它的地方都清理干净。

如果你想延迟销毁,可以用 lv_obj_del_async(),它会在下一个LVGL心跳周期里安全地删除对象。适合在中断或回调里调用。

4.2 位置与大小

对象创建出来,默认在父对象的左上角,大小为100×100像素。但实际项目中,你肯定要调整。

设置大小:

lv_obj_set_size(btn, 120, 40);

设置位置:

lv_obj_set_pos(btn, 50, 30);

这两个函数分别设置对象的宽度/高度和x/y坐标。坐标是相对于父对象内容区域的左上角。

我个人习惯把位置和大小分开设置,这样代码更清晰。当然你也可以用 lv_obj_set_width()、lv_obj_set_height() 单独改一边。

小技巧:如果你想让对象填满父对象,可以用 lv_obj_set_size(btn, LV_PCT(100), LV_PCT(100))。LV_PCT宏表示百分比,非常实用。

4.3 对齐方式

手动算坐标太麻烦了。LVGL提供了对齐机制,让你轻松把对象放在父对象的某个位置。

最常用的对齐函数:

lv_obj_align(btn, LV_ALIGN_CENTER, 0, 0);

这行代码把btn居中放在父对象里。第三个和第四个参数是x和y的偏移量,单位是像素。

常用的对齐常量:

常量 含义
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(label, btn, LV_ALIGN_OUT_RIGHT_MID, 10, 0);

这行代码把label放在btn的右侧中间,并且向右偏移10像素。非常适合做标签和输入框的布局。

核心要点:对齐函数不会自动更新。如果你改变了父对象的大小,需要重新调用一次对齐。或者你可以用 lv_obj_set_align() 设置自动对齐标志,这样父对象变化时子对象会自动调整位置。

4.4 父对象关系

父对象关系是LVGL布局的核心。每个对象只能有一个父对象,但可以有多个子对象。父对象移动时,子对象跟着移动;父对象隐藏时,子对象也隐藏;父对象销毁时,所有子对象自动销毁。

获取父对象:

lv_obj_t * parent = lv_obj_get_parent(btn);

获取子对象列表:

uint32_t child_cnt = lv_obj_get_child_cnt(parent);
lv_obj_t * child = lv_obj_get_child(parent, 0);  // 获取第一个子对象

改变父对象:

lv_obj_set_parent(btn, new_parent);

这个操作会把btn从当前父对象移除,添加到new_parent下。位置会保持相对屏幕不变,但坐标会重新计算。

注意:改变父对象时,如果新旧父对象的坐标原点不同,子对象的位置可能会“跳一下”。我建议在改变父对象后,重新设置一下子对象的位置或对齐方式。

4.5 知识体系图

下面这张图帮你理清本章的核心脉络:

lv_obj_t 对象基础属性知识体系 lv_obj_t 对象 创建与销毁 位置与大小 对齐方式 父对象关系 lv_obj_create() lv_obj_del() lv_obj_set_pos() lv_obj_set_size() lv_obj_align() lv_obj_align_to() lv_obj_get_parent() lv_obj_set_parent() 💡 核心:每个对象有且仅有一个父对象,父对象销毁时所有子对象自动销毁。 对齐和位置都是相对于父对象内容区域计算的。

4.6 综合示例

最后,我们写一个完整的例子,把今天学的都用上:

/* 创建父容器 */
lv_obj_t * cont = lv_obj_create(lv_scr_act());
lv_obj_set_size(cont, 300, 200);
lv_obj_align(cont, LV_ALIGN_CENTER, 0, 0);

/* 创建子按钮 */
lv_obj_t * btn = lv_obj_create(cont);
lv_obj_set_size(btn, 100, 40);
lv_obj_align(btn, LV_ALIGN_CENTER, 0, 0);

/* 创建标签,放在按钮右侧 */
lv_obj_t * label = lv_obj_create(cont);
lv_obj_set_size(label, 80, 30);
lv_obj_align_to(label, btn, LV_ALIGN_OUT_RIGHT_MID, 10, 0);

/* 3秒后销毁按钮 */
lv_timer_t * timer = lv_timer_create([](lv_timer_t * t) {
    lv_obj_del((lv_obj_t *)t->user_data);
    lv_timer_del(t);
}, 3000, btn);

这段代码创建了一个居中容器,里面放了一个居中按钮,按钮右侧10像素处放了一个标签。3秒后按钮自动销毁。

我的建议:刚开始练习时,多试试不同的对齐组合和父对象嵌套。你很快就会发现,LVGL的布局其实非常灵活,而且代码写起来很直观。

嗯,这一章的内容就到这里。对象的基础属性是LVGL的根基,后面所有控件都是在这个基础上扩展的。你把这些搞明白了,后面学控件就是水到渠成的事。