4. LVGL基础对象:lv_obj_t

好,咱们今天来聊聊LVGL里最核心的东西——lv_obj_t。说白了,你在屏幕上看到的每一个按钮、每一个滑块、每一张图片,本质上都是一个lv_obj_t对象。它就像乐高积木里的那个基础块,所有复杂的界面都是用它搭出来的。

我记得刚接触LVGL那会儿,总觉得它很神秘。后来发现,搞懂了这个基础对象,后面学什么都顺了。你想想看,一个GUI系统,无非就是创建对象、设置属性、管理它们之间的关系。LVGL把这些事做得特别干净。

4.1 lv_obj_t是什么?

lv_obj_t是LVGL里所有控件的“祖宗”。它是一个结构体,里面存了对象的坐标、大小、样式、父子关系等等。你可以把它理解成一张白纸——还没画任何东西,但已经有了尺寸和位置。

核心要点:LVGL里所有控件(按钮、标签、滑块等)都是从lv_obj_t派生出来的。你创建一个按钮,本质上就是创建了一个lv_obj_t,然后给它套上了按钮的行为和样式。

我个人习惯把lv_obj_t比作“容器”。它本身不显示任何内容,但你可以往里面放东西,或者改变它的外观。嗯,这里要注意:lv_obj_t本身是透明的,你看到的所有视觉效果,都是通过样式(style)加上去的。

4.2 创建与删除对象

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

lv_obj_t * obj = lv_obj_create(parent);

这个parent参数就是父对象。如果你传NULL,那这个对象就挂在屏幕(display)的根节点上。我建议你永远不要传NULL,除非你真的知道自己在干什么。为什么呢?因为挂在根节点上的对象,不受任何布局管理,容易乱跑。

删除对象更简单:

lv_obj_del(obj);

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

避坑指南:我曾经在项目中用lv_obj_del_async()来删除对象,以为它能延迟删除更安全。结果发现,如果连续调用多次,会导致内存泄漏。后来我改成统一在定时器里处理删除操作,问题就解决了。

还有一个常用操作是清空所有子对象:

lv_obj_clean(obj);

这个函数会删除obj下的所有子对象,但保留obj本身。我在做页面切换时经常用它,比一个个删省事多了。

4.3 对象属性:大小、位置、颜色

对象创建好了,接下来就是设置它的样子。最常用的三个属性:大小、位置、颜色。

大小

lv_obj_set_width(obj, 100);
lv_obj_set_height(obj, 50);
// 或者一步到位
lv_obj_set_size(obj, 100, 50);

大小单位是像素。但LVGL也支持百分比,比如:

lv_obj_set_width(obj, lv_pct(50));  // 父对象宽度的50%

我个人特别喜欢用百分比,尤其是在做自适应布局的时候。你想想看,屏幕尺寸一变,所有控件自动跟着缩放,多省心。

位置

lv_obj_set_x(obj, 10);
lv_obj_set_y(obj, 20);
// 或者
lv_obj_set_pos(obj, 10, 20);

位置是相对于父对象的左上角。但这里有个容易混淆的点:如果你设置了lv_obj_align(),那位置就会变成相对于某个对齐方式。

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

我记得有一次,同事问我为什么他设置了lv_obj_set_pos()但控件没动。后来发现,他之前调用了lv_obj_align(),而align会覆盖set_pos的效果。所以,这两者不要混用,选一种方式就好。

颜色

颜色不是直接设置在对象上的,而是通过样式(style)来设置。但为了方便,LVGL提供了一些快捷函数:

lv_obj_set_style_bg_color(obj, lv_color_hex(0x3498db), 0);
lv_obj_set_style_border_color(obj, lv_color_hex(0x2980b9), 0);

这里的最后一个参数是样式选择器(state),0表示默认状态。如果你想让按钮按下时变红,可以这样:

lv_obj_set_style_bg_color(btn, lv_color_hex(0xe74c3c), LV_STATE_PRESSED);

小技巧:用lv_color_hex()比用lv_color_make()更直观,因为你可以直接写网页上看到的颜色值。我一般从设计稿里复制颜色代码,直接贴进去就行。

4.4 父子对象关系

父子关系是LVGL里最重要的概念之一。简单说:子对象的位置和可见性,都受父对象影响。

举个例子:

lv_obj_t * parent = lv_obj_create(lv_scr_act());
lv_obj_set_size(parent, 200, 200);
lv_obj_set_pos(parent, 50, 50);

lv_obj_t * child = lv_obj_create(parent);
lv_obj_set_size(child, 100, 100);
lv_obj_set_pos(child, 20, 20);  // 相对于parent的左上角

这里child的实际屏幕位置是(70, 70),因为它是相对于parent的(50, 50)再加上自己的(20, 20)。

父子关系还有几个重要特性:

  • 裁剪:默认情况下,子对象超出父对象的部分会被裁剪掉。你可以用lv_obj_set_style_clip_corner()来控制。
  • 事件传递:点击事件会从子对象向父对象冒泡。如果你点击了一个按钮,它的父容器也能收到这个事件。
  • 删除级联:删父删子,前面说过了。

注意:不要创建循环父子关系。比如A是B的父,B又是A的父。LVGL不会检测这种循环,但会导致无限递归,程序直接崩溃。我曾经在动态创建对象时不小心写了个循环,调试了一下午才发现。

下面这张图展示了父子对象的关系和属性设置流程:

LVGL 父子对象关系与属性设置 父对象 (parent) lv_obj_create(lv_scr_act()) 大小: 300x180 位置: (50, 70) 背景色: #3498db 子对象1 (child1) lv_obj_create(parent) 大小: 200x120 位置: (50, 40) 相对父 背景色: #2ecc71 子对象2 (child2) lv_obj_create(parent) 大小: 200x120 位置: (370, 0) 相对父 背景色: #e74c3c 属性设置流程 1. lv_obj_set_size(obj, w, h) 2. lv_obj_set_pos(obj, x, y) 3. lv_obj_set_style_bg_color(obj, color, 0) 4. lv_obj_align(obj, LV_ALIGN_CENTER, 0, 0) 关键说明: • 子对象位置是相对于父对象左上角的偏移量 • 删除父对象会自动删除所有子对象 • 子对象超出父对象范围的部分默认被裁剪

4.5 常用操作总结

为了方便你查阅,我把常用的对象操作整理成了表格:

操作 函数 说明
创建对象 lv_obj_create(parent) 返回新对象的指针
删除对象 lv_obj_del(obj) 同时删除所有子对象
清空子对象 lv_obj_clean(obj) 保留父对象,删除所有子
设置大小 lv_obj_set_size(obj, w, h) 支持百分比
设置位置 lv_obj_set_pos(obj, x, y) 相对于父对象
对齐 lv_obj_align(obj, align, x_ofs, y_ofs) 会覆盖set_pos
设置背景色 lv_obj_set_style_bg_color(obj, color, state) 通过样式设置
获取父对象 lv_obj_get_parent(obj) 返回父对象指针
获取子对象 lv_obj_get_child(obj, idx) 按索引获取

个人经验:我建议你在创建对象后,立即设置好大小和位置,然后再设置样式。这样代码逻辑清晰,不容易漏掉属性。另外,lv_obj_align()最好在set_pos之后调用,否则会被覆盖。