2、LVGL基础对象与屏幕管理:lv_obj_t对象体系、创建与删除对象、屏幕(Screen)的创建与切换、获取活动屏幕

2.1 一切从lv_obj_t开始

说实话,LVGL里所有的控件,本质上都是对象。你看到的按钮、标签、滑块,底层都是一个lv_obj_t结构体。我刚开始接触LVGL时,总觉得这概念有点虚,后来做项目多了才明白——理解了这个,你就抓住了LVGL的命门。

lv_obj_t是LVGL的基石。它就像一张白纸,你可以在上面画任何东西。每个对象都有几个核心属性:

  • 父对象:谁生了你,你就归谁管
  • 位置和大小:你在屏幕上的坐标和尺寸
  • 样式:你的长相,颜色、边框、阴影这些
  • 事件回调:别人点你、拖你,你要怎么反应

核心要点:LVGL的对象体系是树形结构。每个对象只能有一个父对象,但可以有多个子对象。父对象移动,子对象跟着动;父对象隐藏,子对象全看不见。这个特性我在做仪表盘嵌套布局时经常用到。

2.2 创建与删除对象

创建对象其实就一行代码。我习惯这么写:

lv_obj_t * btn = lv_btn_create(lv_scr_act());  // 在当前活动屏幕上创建按钮

这里lv_btn_create()的第一个参数是父对象。你想想看,如果你不指定父对象,LVGL怎么知道这个按钮该放哪?

删除对象更简单:

lv_obj_del(btn);  // 删除按钮对象

嗯,这里要注意——删除父对象时,所有子对象也会被自动删除。我曾经在项目中犯过这个错:删了一个容器,结果里面的十几个控件全没了,排查了半天才发现是父对象被误删了。

避坑指南:千万不要在事件回调里直接删除触发事件的对象。比如按钮点击事件里调用lv_obj_del(btn),这会导致内存访问异常。正确的做法是用lv_event_send()延迟删除,或者用lv_async_call()

还有个常用函数:

lv_obj_clean(parent);  // 删除父对象的所有子对象,但保留父对象本身

这个我在做动态刷新界面时特别爱用。比如仪表盘上的数据列表,每次更新数据时,先清空旧列表,再重新创建新列表,干净利落。

2.3 屏幕(Screen)的创建与切换

屏幕在LVGL里也是对象,而且是特殊的对象。每个屏幕都是一个独立的lv_obj_t,但它没有父对象——它是根节点。

创建新屏幕:

lv_obj_t * screen2 = lv_obj_create(NULL);  // 父对象传NULL,表示创建屏幕
lv_scr_load(screen2);  // 切换到新屏幕

我个人习惯把每个功能页面做成独立的屏幕。比如主菜单一个屏幕,设置页面一个屏幕,数据展示一个屏幕。切换时直接lv_scr_load(),整个界面瞬间切换,非常流畅。

小技巧:如果你想让切换有动画效果,可以用lv_scr_load_anim()。比如从左往右滑入:

lv_scr_load_anim(screen2, LV_SCR_LOAD_ANIM_MOVE_LEFT, 300, 0, false);

300是动画时长(毫秒),最后一个参数是延迟时间。这个效果在车载仪表盘上特别显档次。

2.4 获取活动屏幕

活动屏幕就是当前正在显示的屏幕。获取它很简单:

lv_obj_t * active_scr = lv_scr_act();

这个函数我几乎每个项目都会用到。为什么?因为创建控件时,如果你不指定父对象,控件默认就创建在活动屏幕上。但为了代码清晰,我建议还是显式指定父对象。

还有个相关的函数:

lv_obj_t * layer_top = lv_layer_top();   // 获取顶层图层
lv_obj_t * layer_sys = lv_layer_sys();   // 获取系统图层

这两个图层很有意思。顶层图层在所有屏幕之上,系统图层又在顶层图层之上。我曾在项目中用lv_layer_top()来做全局的加载动画——不管用户切换到哪个屏幕,加载动画始终显示在最前面。

实战经验:做多屏幕应用时,我建议把全局的控件(比如状态栏、导航按钮)放在lv_layer_top()上。这样切换屏幕时,这些控件不会消失,用户体验会好很多。但要注意,顶层图层上的控件会遮挡下面的屏幕内容,布局时要留出空间。

2.5 对象体系的小结

说白了,LVGL的对象管理就三件事:

  1. 创建:指定父对象,生成新对象
  2. 管理:设置属性、添加样式、绑定事件
  3. 销毁:删除对象,释放内存

屏幕管理更简单——创建屏幕、加载屏幕、获取当前屏幕。但别小看这几步,我见过不少新手把屏幕和普通对象搞混,结果界面怎么都显示不出来。

最后分享一个我自己的习惯:每次创建对象后,我都会检查返回值是否为NULL。虽然LVGL内部有内存管理,但在资源紧张的嵌入式设备上,内存分配失败是可能发生的。加个判断,能省去很多调试时间。

lv_obj_t * my_obj = lv_obj_create(parent);
if (my_obj == NULL) {
    LV_LOG_ERROR("对象创建失败!");
    return;
}

嗯,基础打牢了,后面做复杂仪表盘才能得心应手。下一章我们聊聊对象的属性设置,那才是真正让控件"活"起来的关键。