2. LVGL基础对象与屏幕:lv_obj_t结构体、创建与删除对象、屏幕对象管理、屏幕切换基础
好,咱们今天来聊聊LVGL里最核心的东西——对象和屏幕。
你想想看,不管你要画一个按钮、一个滑块,还是一个列表,它们本质上都是什么?都是对象。LVGL里万物皆对象,这个理念贯穿始终。我刚开始接触LVGL时,第一件事就是去理解这个 lv_obj_t,搞懂了它,后面就顺了。
2.1 lv_obj_t结构体——对象的身份证
每个LVGL对象,本质上就是一个 lv_obj_t 结构体实例。它就像对象的身份证,记录了对象的所有信息。
| 成员 | 说明 |
|---|---|
parent |
父对象指针,对象挂在哪棵树上 |
child |
子对象链表,管理它的孩子们 |
coords |
对象在屏幕上的坐标和尺寸 |
flags |
各种标志位,比如是否可点击、是否隐藏 |
style |
样式指针,控制外观 |
event_cb |
事件回调函数,处理点击、滑动等操作 |
说白了,这个结构体就是LVGL的基石。你创建一个按钮,LVGL内部就分配一个 lv_obj_t,然后往里填数据。我在项目里调试时,经常直接打印 coords 来看对象位置对不对,这招挺管用的。
lv_obj_dump_tree() 打印出当前所有对象的父子关系树,调试时特别好用。
2.2 创建与删除对象——生与死的管理
创建对象很简单,核心就一个函数:lv_obj_create()。
/* 创建一个对象,父对象是当前活动屏幕 */
lv_obj_t * obj = lv_obj_create(lv_scr_act());
/* 设置大小和位置 */
lv_obj_set_size(obj, 100, 50);
lv_obj_set_pos(obj, 20, 30);
嗯,这里要注意:创建对象时一定要指定父对象。如果不指定,它就是个孤儿,显示不出来。我见过不少新手踩这个坑,创建了对象但屏幕上啥也没有,其实就是忘了设父对象。
删除对象呢?用 lv_obj_del()。
lv_obj_del(obj); /* 删除单个对象 */
lv_obj_clean(parent); /* 删除父对象下的所有子对象,但保留父对象 */
lv_obj_del_async(obj); /* 安全删除,适合在事件回调里用 */
lv_obj_del() 删除触发事件的对象!这会导致野指针崩溃。要用 lv_obj_del_async(),它会等事件处理完再删。
我曾经在项目里犯过这个错。一个按钮的点击事件里直接删了自己,结果程序跑飞了。查了半天才发现是删除时机的问题。后来就养成了习惯:凡是回调里删对象,一律用异步删除。
2.3 屏幕对象管理——你的画布
屏幕在LVGL里也是对象,它继承自 lv_obj_t。每个屏幕都是一个独立的画布,你可以创建多个屏幕,然后在它们之间切换。
常用的屏幕操作函数:
lv_scr_act()— 获取当前活动屏幕lv_obj_set_scr(obj)— 设置某个对象为活动屏幕lv_scr_load(obj)— 加载并切换到指定屏幕lv_scr_load_anim(obj, anim_type, time, delay, auto_del)— 带动画切换屏幕
创建新屏幕的典型做法:
/* 创建一个新屏幕 */
lv_obj_t * scr2 = lv_obj_create(NULL); /* 父对象为NULL,表示它是顶级屏幕 */
lv_obj_set_style_bg_color(scr2, lv_color_hex(0x2196F3), 0);
/* 在scr2上添加一些控件 */
lv_obj_t * btn = lv_btn_create(scr2);
lv_obj_center(btn);
/* 切换到scr2 */
lv_scr_load(scr2);
你想想看,为什么创建屏幕时父对象要传 NULL?因为屏幕是顶级容器,它不隶属于任何其他对象。每个屏幕自己就是一个独立的根节点。
2.4 屏幕切换基础——页面跳转的核心
屏幕切换说白了就是换画布。LVGL提供了两种方式:直接切换和动画切换。
直接切换:
lv_scr_load(scr2); /* 瞬间切换,没有过渡效果 */
动画切换:
/* 从右向左滑入 */
lv_scr_load_anim(scr2, LV_SCR_LOAD_ANIM_MOVE_RIGHT, 500, 0, false);
/* 淡入淡出 */
lv_scr_load_anim(scr2, LV_SCR_LOAD_ANIM_FADE_ON, 300, 0, false);
动画类型有这些:
| 动画类型 | 效果 |
|---|---|
LV_SCR_LOAD_ANIM_NONE |
无动画,瞬间切换 |
LV_SCR_LOAD_ANIM_OVER_LEFT |
新屏幕从左侧覆盖 |
LV_SCR_LOAD_ANIM_OVER_RIGHT |
新屏幕从右侧覆盖 |
LV_SCR_LOAD_ANIM_OVER_TOP |
新屏幕从顶部覆盖 |
LV_SCR_LOAD_ANIM_OVER_BOTTOM |
新屏幕从底部覆盖 |
LV_SCR_LOAD_ANIM_MOVE_LEFT |
新屏幕从右侧推入,旧屏幕被推出 |
LV_SCR_LOAD_ANIM_MOVE_RIGHT |
新屏幕从左侧推入 |
LV_SCR_LOAD_ANIM_MOVE_TOP |
新屏幕从底部推入 |
LV_SCR_LOAD_ANIM_MOVE_BOTTOM |
新屏幕从顶部推入 |
LV_SCR_LOAD_ANIM_FADE_ON |
淡入效果 |
最后一个参数 auto_del 很有意思。如果设为 true,切换完成后会自动删除旧屏幕。我一般会在不需要返回的页面用 true,比如设置页进入子页面后,子页面关闭时自动清理,省得手动管理内存。
auto_del = true。
我个人习惯在项目里维护一个屏幕栈。比如主菜单、设置页、关于页,每个页面都是一个独立屏幕。用数组存起来,切换时压栈出栈。这样返回上一页就特别方便,直接 lv_scr_load(stack[--top]) 就行。
嗯,这里再提醒一句:屏幕切换动画虽然好看,但别滥用。嵌入式设备性能有限,频繁的动画切换会占用CPU,导致界面卡顿。我一般只在主页面切换时用动画,子页面之间直接切换,省资源。
好了,这一章的内容就这些。搞懂了 lv_obj_t 和屏幕管理,你就掌握了LVGL的根基。下一章咱们聊聊更具体的控件——按钮和标签,看看怎么让屏幕活起来。