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());

效果一样。但我个人习惯,还是显式传参,代码可读性更好。

小技巧: 创建对象后,最好立刻检查返回值。虽然 LVGL 内部有错误处理,但养成习惯总没错。

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 的,所以它也会偏移

这里有个坑,我曾经踩过:

注意: 子对象的坐标是相对于父对象的。如果你把子对象放在 (100, 100),父对象在 (50, 50),那子对象在屏幕上的实际位置是 (150, 150)。这个很容易搞混,尤其是做嵌套布局的时候。

那么,怎么获取父子关系呢?

函数 作用
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 或者自己计算。

核心概念: LVGL 的坐标系统是相对于父对象的。所有子对象的位置,都是基于父对象的左上角。这个设计让嵌套布局变得简单,但也容易让人迷糊。

我在项目中遇到过一个问题:一个按钮在屏幕上的位置总是不对。查了半天,发现它的父对象是一个隐藏的容器。父对象隐藏了,但子对象还在,坐标却乱了。所以,父对象的可见性会影响子对象的布局,这点要记住。

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 基础对象知识体系 lv_obj 基础对象知识体系 lv_obj 基础对象 创建 父子关系 坐标与大小 对齐方式 lv_obj_create(parent) 默认父对象:lv_scr_act() 树形结构,一个父多个子 子坐标相对于父 lv_obj_set_pos/set_size 获取:get_x/get_y/get_width/get_height lv_obj_align lv_obj_align_to 核心要点 所有控件都继承自 lv_obj,掌握基础对象 = 掌握 LVGL 的根基

这张图把本章的四个核心知识点串在了一起。你可以看到,创建是起点,父子关系决定了结构,坐标与大小控制了外观,对齐方式则让布局更灵活。它们共同构成了 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);

这个例子很简单:一个容器居中在屏幕上,容器里有一个按钮居中,按钮右侧有一个标签。你看,父子关系、坐标、对齐,全用上了。

总结一下: lv_obj 是 LVGL 的基石。创建、父子关系、坐标大小、对齐方式,这四个点搞明白了,后面学任何控件都会轻松很多。我建议你动手写几个小例子,感受一下对象之间的层级关系。