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() 改变它的父对象,重新调整层级。
下面这张图展示了父子关系与层级:
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 项目里反复使用。把它们练熟,后面的控件学习就会轻松很多。