3、LVGL基础对象(lv_obj):对象的创建、父子关系、坐标与大小、对齐方式
各位好,我是老周。今天咱们聊聊 LVGL 里最基础、也最重要的东西——lv_obj。
你想想看,不管你做多炫酷的控件,本质上都是在操作一个个对象。按钮是对象,标签是对象,滑块也是对象。它们都继承自 lv_obj。所以,搞懂这个基础对象,后面的路就好走了。
我记得刚接触 LVGL 那会儿,总觉得它跟其他 GUI 框架差不多。后来踩了几个坑才发现,它的对象模型有自己的脾气。今天我就把这些经验掰开揉碎了讲给你。
3.1 对象的创建
创建对象很简单,就一个函数:
lv_obj_t * obj = lv_obj_create(parent);
这个 parent 参数,就是父对象。你传什么,新对象就挂在谁下面。
如果不传父对象呢?也可以。LVGL 有个默认的根对象——lv_scr_act(),也就是当前活动屏幕。你写:
lv_obj_t * obj = lv_obj_create(lv_scr_act());
效果一样。但我个人习惯,还是显式传参,代码可读性更好。
3.2 父子关系
父子关系是 LVGL 对象体系的核心。说白了,就是一棵树。
每个对象只能有一个父对象,但可以有多个子对象。父对象移动,子对象跟着动。父对象隐藏,子对象也看不见。这就是继承关系。
我举个例子:
lv_obj_t * screen = lv_scr_act();
lv_obj_t * parent = lv_obj_create(screen);
lv_obj_t * child = lv_obj_create(parent);
lv_obj_set_pos(parent, 50, 50);
// child 的位置是相对于 parent 的,所以它也会偏移
这里有个坑,我曾经踩过:
那么,怎么获取父子关系呢?
| 函数 | 作用 |
|---|---|
lv_obj_get_parent(obj) |
获取父对象 |
lv_obj_get_child(obj, idx) |
获取第 idx 个子对象(从 0 开始) |
lv_obj_get_child_cnt(obj) |
获取子对象数量 |
lv_obj_del(obj) |
删除对象及其所有子对象 |
嗯,这里要注意:删除父对象时,所有子对象也会被自动删除。所以别手抖删错了。
3.3 坐标与大小
每个对象都有位置和尺寸。位置用 x 和 y 表示,尺寸用 width 和 height 表示。
设置坐标:
lv_obj_set_pos(obj, 100, 50);
设置大小:
lv_obj_set_size(obj, 200, 100);
也可以单独设置:
lv_obj_set_x(obj, 100);
lv_obj_set_y(obj, 50);
lv_obj_set_width(obj, 200);
lv_obj_set_height(obj, 100);
获取坐标和大小:
lv_coord_t x = lv_obj_get_x(obj);
lv_coord_t y = lv_obj_get_y(obj);
lv_coord_t w = lv_obj_get_width(obj);
lv_coord_t h = lv_obj_get_height(obj);
这里有个细节:lv_obj_get_x 返回的是相对于父对象的坐标。如果你想获取屏幕上的绝对坐标,需要用 lv_obj_get_x_aligned 或者自己计算。
我在项目中遇到过一个问题:一个按钮在屏幕上的位置总是不对。查了半天,发现它的父对象是一个隐藏的容器。父对象隐藏了,但子对象还在,坐标却乱了。所以,父对象的可见性会影响子对象的布局,这点要记住。
3.4 对齐方式
对齐是 UI 布局的利器。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, target, LV_ALIGN_OUT_RIGHT_TOP, 10, 0);
这个函数的意思:把 obj 放在 target 的右侧外部,顶部对齐,再偏移 (10, 0)。
常用的相对对齐标志:
| 标志 | 含义 |
|---|---|
LV_ALIGN_OUT_TOP_LEFT |
目标外部左上角 |
LV_ALIGN_OUT_TOP_MID |
目标外部顶部居中 |
LV_ALIGN_OUT_TOP_RIGHT |
目标外部右上角 |
LV_ALIGN_OUT_BOTTOM_LEFT |
目标外部左下角 |
LV_ALIGN_OUT_BOTTOM_MID |
目标外部底部居中 |
LV_ALIGN_OUT_BOTTOM_RIGHT |
目标外部右下角 |
LV_ALIGN_OUT_LEFT_TOP |
目标外部左侧顶部 |
LV_ALIGN_OUT_LEFT_MID |
目标外部左侧居中 |
LV_ALIGN_OUT_LEFT_BOTTOM |
目标外部左侧底部 |
LV_ALIGN_OUT_RIGHT_TOP |
目标外部右侧顶部 |
LV_ALIGN_OUT_RIGHT_MID |
目标外部右侧居中 |
LV_ALIGN_OUT_RIGHT_BOTTOM |
目标外部右侧底部 |
我个人习惯,做复杂布局时,先用 lv_obj_align 做相对父对象的对齐,再用 lv_obj_align_to 做对象之间的相对对齐。这样代码清晰,也容易调试。
lv_obj_align,结果发现性能很差。后来才知道,每次对齐都会触发布局计算。如果只是设置一次位置,用 lv_obj_set_pos 更高效。对齐适合在初始化时用,运行时尽量少用。
3.5 知识体系总览
下面这张图,把本章的核心知识点串起来了。你可以把它当作一个快速参考。
这张图把本章的四个核心知识点串在了一起。你可以看到,创建是起点,父子关系决定了结构,坐标与大小控制了外观,对齐方式则让布局更灵活。它们共同构成了 LVGL 对象模型的基础。
3.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);
这个例子很简单:一个容器居中在屏幕上,容器里有一个按钮居中,按钮右侧有一个标签。你看,父子关系、坐标、对齐,全用上了。