2、LVGL基础对象(lv_obj):对象的创建与删除、父子层级关系、常用属性(大小、位置、对齐)、事件回调机制入门。
各位同学,欢迎来到第二章。这一章我们聊聊LVGL里最核心的东西——基础对象,也就是 lv_obj。
你想想看,LVGL里所有的控件,什么按钮、标签、滑块,本质上都是 lv_obj 的“子类”。搞懂了这个基础对象,后面学啥都快。我个人习惯把 lv_obj 比作一张白纸——你可以在上面画东西,也可以在上面贴其他纸片。嗯,就是这么个道理。
核心认知:LVGL中一切控件皆对象。掌握 lv_obj 就等于掌握了LVGL的“内功心法”。
2.1 对象的创建与删除
先说说怎么“变”出一个对象来。LVGL里创建对象就一行代码:
lv_obj_t * btn = lv_obj_create(lv_scr_act()); // 在当前活动屏幕创建按钮
这里 lv_obj_create() 的参数是父对象。说白了,你告诉系统:“我要在这个父对象上面放一个新对象。” 返回值是一个指针,指向新创建的对象。我刚开始学的时候老忘记保存这个指针,结果后面想操作对象都找不到它——嗯,这个坑我踩过。
删除对象更简单:
lv_obj_del(btn); // 删除btn对象
这里有个细节要注意:如果你删除了一个父对象,它所有的子对象也会被自动删除。我曾经在项目里不小心删了一个容器,结果里面的十几个按钮全没了,调试了半天才发现是父对象被误删了。
⚠️ 避坑指南:千万不要在事件回调里直接删除触发事件的对象本身,否则会导致野指针崩溃。正确的做法是用 lv_event_send() 延迟删除,或者使用 lv_obj_del_async()。
2.2 父子层级关系
LVGL的界面是一个树形结构。每个对象都有一个父对象,也可以有多个子对象。屏幕(Screen)是最顶层的父对象。
我举个例子你就明白了:
lv_obj_t * scr = lv_scr_act(); // 获取当前屏幕
lv_obj_t * cont = lv_obj_create(scr); // 在屏幕上创建容器
lv_obj_t * btn = lv_obj_create(cont); // 在容器里创建按钮
这个层级关系决定了三件事:
- 显示顺序:子对象永远显示在父对象之上
- 裁剪范围:子对象超出父对象的部分默认会被裁剪
- 事件传递:点击子对象时,事件会冒泡到父对象
我记得有一次做智能家居面板,需要在一个卡片容器里放多个开关按钮。如果不设置父子关系,按钮就会跑到卡片外面去。后来我设置了正确的父子层级,一切就正常了。
💡 小技巧:用 lv_obj_get_parent() 获取父对象,用 lv_obj_get_child() 遍历子对象。这在调试复杂界面时特别有用。
2.3 常用属性设置
创建了对象,接下来就是给它“打扮打扮”。常用的属性就三个:大小、位置、对齐。
2.3.1 大小(Size)
lv_obj_set_size(btn, 100, 50); // 设置宽度100,高度50
你也可以用 lv_obj_set_width() 和 lv_obj_set_height() 分别设置。我个人习惯用 lv_obj_set_size(),一行搞定,省事。
2.3.2 位置(Position)
lv_obj_set_pos(btn, 20, 30); // 相对于父对象左上角,x=20, y=30
这里要注意:坐标是相对于父对象的。如果你把按钮放在容器里,坐标就是相对于容器的左上角。我见过有人把坐标设成相对于屏幕的,结果容器移动后按钮位置全乱了。
2.3.3 对齐(Alignment)
对齐是LVGL里非常强大的功能。它让你不用手动计算坐标:
lv_obj_align(btn, LV_ALIGN_CENTER, 0, 0); // 在父对象中居中
第二个参数是对齐方式,常用的有:
| 对齐常量 | 说明 |
|---|---|
LV_ALIGN_CENTER |
居中 |
LV_ALIGN_TOP_LEFT |
左上角 |
LV_ALIGN_TOP_MID |
顶部居中 |
LV_ALIGN_BOTTOM_RIGHT |
右下角 |
LV_ALIGN_LEFT_MID |
左侧居中 |
后两个参数是x和y的偏移量。比如你想让按钮在屏幕正中央,但偏右10像素:
lv_obj_align(btn, LV_ALIGN_CENTER, 10, 0);
实战经验:我建议你优先使用 lv_obj_align() 而不是手动设置坐标。因为当屏幕尺寸变化时,对齐会自动适应,而手动坐标会跑偏。这在做多分辨率适配时特别重要。
2.4 事件回调机制入门
对象创建好了,属性也设好了,但它还不会“动”。要让对象响应用户操作,就需要事件回调。
事件回调说白了就是:当某个事件发生时(比如点击、长按、拖动),系统会调用你写的一个函数。
注册事件回调的代码:
lv_obj_add_event_cb(btn, btn_event_cb, LV_EVENT_CLICKED, NULL);
// 回调函数实现
static void btn_event_cb(lv_event_t * e) {
lv_obj_t * obj = lv_event_get_target(e); // 获取触发事件的对象
LV_LOG_USER("按钮被点击了!");
}
这里 LV_EVENT_CLICKED 是事件类型。LVGL里有很多事件类型,常用的有:
| 事件类型 | 触发时机 |
|---|---|
LV_EVENT_CLICKED |
点击完成(按下并释放) |
LV_EVENT_PRESSED |
按下瞬间 |
LV_EVENT_RELEASED |
释放瞬间 |
LV_EVENT_LONG_PRESSED |
长按触发 |
LV_EVENT_VALUE_CHANGED |
值改变(如滑块拖动) |
最后一个参数是用户自定义数据。你可以传一个指针进去,在回调里用 lv_event_get_user_data() 取出来。这个功能很实用,比如多个按钮共用一个回调函数时,通过用户数据区分是哪个按钮被点击。
⚠️ 注意:回调函数里不要做耗时操作,比如延时、文件读写等。LVGL的事件处理是在主循环里同步执行的,耗时操作会卡死界面。如果确实需要做耗时操作,请使用 lv_task 或 lv_timer 异步处理。
好了,这一章的内容就这些。你可能会觉得知识点有点多,但别急,这些都是最基础的东西。后面每一章都会反复用到这些概念。你先把对象的创建、父子关系、属性设置和事件回调这四个点吃透,后面的学习就会顺畅很多。