4. LVGL 基础对象(lv_obj):一切从对象开始

各位同学,欢迎来到第四章。从这一章开始,我们正式进入 LVGL 的核心——对象系统。

你想想看,一个嵌入式 GUI 界面,说白了就是一堆“东西”摆在那。按钮、标签、滑块、图表……这些在 LVGL 里统称为“对象”。我刚开始接触 LVGL 时,第一反应就是:这玩意儿跟面向对象编程里的“对象”是不是一回事?嗯,差不多,但又不完全一样。

4.1 什么是对象?

在 LVGL 里,对象(lv_obj) 是最基本的构建单元。你可以把它理解成一块“画布”,或者一个“容器”。

  • 每个对象都有自己的位置、大小、样式。
  • 对象可以嵌套,形成父子关系。
  • 所有高级控件(按钮、标签、列表等)都是从 lv_obj 派生出来的。

我习惯把 lv_obj 比作“白纸”。你可以在上面画东西,也可以在上面贴其他“白纸”。LVGL 里几乎所有可见元素,本质上都是一个 lv_obj,只不过穿上了不同的“外衣”。

核心概念:lv_obj 是所有控件的“祖宗”。你学会了操作 lv_obj,就等于学会了操作一切控件的基础。

4.2 对象的创建与删除

创建对象很简单,就一个函数:

lv_obj_t * obj = lv_obj_create(parent);

这个 parent 参数,就是对象的“父对象”。如果你想让这个对象直接挂在屏幕上,就用 lv_scr_act() 获取当前活动屏幕。

删除对象更简单:

lv_obj_del(obj);

这里有个坑,我踩过。如果你删除了一个父对象,它所有的子对象也会被自动删除。所以千万别在子对象被删除后还去访问它——那就是野指针,必挂。

注意:删除对象后,记得把指针置为 NULL,或者确保不再使用它。LVGL 不会帮你清空指针。

还有一种延迟删除的方式:

lv_obj_del_async(obj);

这个函数会在下一次事件循环中删除对象。我在处理动画或定时器回调时经常用,避免在回调中直接删除对象导致崩溃。

4.3 对象的父子关系与层级

父子关系是 LVGL 对象系统的核心。说白了,就是“谁是谁的爹”。

  • 父对象:负责管理子对象的生命周期、位置、可见性。
  • 子对象:相对于父对象定位,父对象移动,子对象跟着动。
  • 层级(Z-order):决定谁在上面,谁在下面。

举个例子:

lv_obj_t * scr = lv_scr_act();  // 屏幕
lv_obj_t * parent = lv_obj_create(scr);
lv_obj_t * child1 = lv_obj_create(parent);
lv_obj_t * child2 = lv_obj_create(parent);

这里 child1 和 child2 都是 parent 的子对象。它们的位置是相对于 parent 的。如果我把 parent 往右移 50 像素,两个子对象也会跟着右移 50 像素。

层级控制用这两个函数:

lv_obj_move_foreground(obj);  // 移到最前面
lv_obj_move_background(obj);  // 移到最后面

我记得有一次做菜单界面,子菜单总是被父菜单挡住。查了半天,原来是层级没调对。一个 lv_obj_move_foreground() 就解决了。

小技巧:如果你想让某个对象始终在最前面,可以在它的父对象创建后,调用 lv_obj_move_foreground()。或者用 lv_obj_set_parent() 改变它的父对象,重新调整层级。

下面这张图展示了父子关系与层级:

LVGL 对象父子关系与层级示意图 屏幕 (lv_scr_act) 父对象 lv_obj_create(scr) 子1 层级低 子2 层级高 独立对象 lv_obj_create(scr) 与父对象同级 子对象相对于父对象定位,父对象移动,子对象跟着动

4.4 对象的基本属性

每个对象都有三个最基本的几何属性:位置、大小、对齐。这三个属性决定了对象在屏幕上长什么样、在哪。

4.4.1 位置(Position)

位置用 x 和 y 表示。注意,这个坐标是相对于父对象的左上角。

lv_obj_set_pos(obj, 50, 100);  // 设置 x=50, y=100

也可以单独设置:

lv_obj_set_x(obj, 50);
lv_obj_set_y(obj, 100);

获取位置:

lv_coord_t x = lv_obj_get_x(obj);
lv_coord_t y = lv_obj_get_y(obj);

这里有个细节:lv_obj_get_x() 返回的是相对于父对象的坐标。如果你想获取对象在屏幕上的绝对坐标,用 lv_obj_get_x_aligned() 或自己递归计算。

4.4.2 大小(Size)

大小用 width 和 height 表示。

lv_obj_set_size(obj, 200, 100);  // 宽200,高100

获取大小:

lv_coord_t w = lv_obj_get_width(obj);
lv_coord_t h = lv_obj_get_height(obj);

还有一个 lv_obj_get_content_width(),它返回的是去掉 padding 后的可用宽度。我在做自适应布局时经常用这个。

经验之谈:如果你想让对象的大小自适应内容,可以用 lv_obj_set_size(obj, LV_SIZE_CONTENT, LV_SIZE_CONTENT)。但要注意,这只对某些控件有效,比如标签(lv_label)。

4.4.3 对齐(Alignment)

对齐是 LVGL 里非常强大的功能。它让你不用手动计算坐标,就能把对象放在父对象的某个位置。

基本用法:

lv_obj_align(obj, LV_ALIGN_CENTER, 0, 0);  // 居中,偏移量为0

第二个参数是对齐方式,第三个和第四个是 x 和 y 的偏移量。

常用的对齐方式:

对齐常量 说明
LV_ALIGN_TOP_LEFT 左上角
LV_ALIGN_TOP_MID 顶部居中
LV_ALIGN_TOP_RIGHT 右上角
LV_ALIGN_LEFT_MID 左侧居中
LV_ALIGN_CENTER 正中心
LV_ALIGN_RIGHT_MID 右侧居中
LV_ALIGN_BOTTOM_LEFT 左下角
LV_ALIGN_BOTTOM_MID 底部居中
LV_ALIGN_BOTTOM_RIGHT 右下角

还有一种对齐方式叫“相对于另一个对象对齐”:

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

这个函数的意思是:把 obj 放在 ref_obj 的右侧外部,顶部对齐,再偏移 10 像素。我在做弹出菜单时特别喜欢用这个。

注意:对齐操作会覆盖之前设置的位置。如果你先调了 lv_obj_set_pos(),然后又调 lv_obj_align(),最终位置以 lv_obj_align() 为准。

4.5 综合示例:创建一个简单的界面

说了这么多,我们来写一个完整的例子。创建一个屏幕,上面放一个容器,容器里放一个按钮和一个标签。

// 获取当前屏幕
lv_obj_t * scr = lv_scr_act();

// 创建一个容器,放在屏幕中央,大小 300x200
lv_obj_t * cont = lv_obj_create(scr);
lv_obj_set_size(cont, 300, 200);
lv_obj_align(cont, LV_ALIGN_CENTER, 0, 0);

// 在容器顶部居中放一个标签
lv_obj_t * label = lv_label_create(cont);
lv_label_set_text(label, "Hello, LVGL!");
lv_obj_align(label, LV_ALIGN_TOP_MID, 0, 10);

// 在容器底部居中放一个按钮
lv_obj_t * btn = lv_btn_create(cont);
lv_obj_set_size(btn, 100, 40);
lv_obj_align(btn, LV_ALIGN_BOTTOM_MID, 0, -10);

// 在按钮上放一个标签
lv_obj_t * btn_label = lv_label_create(btn);
lv_label_set_text(btn_label, "Click me");
lv_obj_center(btn_label);  // 让标签在按钮中居中

这个例子虽然简单,但包含了本章所有的核心概念:创建对象、设置父子关系、调整位置和大小、使用对齐。你想想看,如果不用对齐,你得手动计算坐标,多麻烦。

总结一下:对象是 LVGL 的基石。创建、删除、父子关系、位置、大小、对齐——这六个操作,你会在每个 LVGL 项目里反复使用。把它们练熟,后面的控件学习就会轻松很多。