2. LVGL基础架构与对象模型
好,咱们开始聊LVGL的核心。说白了,LVGL就是一个图形框架,它帮你管理屏幕上那些按钮、滑块、标签。你想想看,如果没有框架,你要自己画像素、处理触摸、管理刷新——那得多累?
LVGL的软件架构,我习惯把它分成三层:
- 底层驱动层:负责跟硬件打交道。比如屏幕的读写、触摸屏的数据采集。这部分你得自己写,或者用现成的驱动。
- 核心层:这是LVGL的心脏。对象管理、事件分发、绘图引擎、样式系统,全在这层。咱们今天聊的对象模型,就是核心层的一部分。
- 组件层:基于核心层封装好的各种控件。按钮、标签、滑块、图表...你直接用就行。
这三层的关系,就像盖房子。底层是地基,核心层是框架结构,组件层是装修好的房间。嗯,这个比喻我觉得挺贴切。
2.1 对象(lv_obj_t)的概念
在LVGL里,一切皆对象。按钮是对象,标签是对象,屏幕也是对象。所有对象的基础类型,就是 lv_obj_t。
你可以把 lv_obj_t 想象成一个「空盒子」。这个盒子有位置、大小、父对象、样式、事件回调...但里面是空的。你往盒子里塞不同的东西,它就变成了不同的控件。
我记得刚开始用LVGL时,总搞不清对象和控件的关系。后来想通了:控件 = 基础对象 + 特定功能。比如按钮,就是在基础对象上加了点击响应、按下状态这些功能。
核心要点:lv_obj_t 是所有控件的基类。你创建的每一个按钮、标签、滑块,本质上都是一个 lv_obj_t 指针。
2.2 对象的创建与删除
创建对象很简单,就一个函数:
lv_obj_t * btn = lv_btn_create(lv_scr_act()); // 在当前活动屏幕上创建按钮
这个函数做了三件事:
- 在内存里分配一个
lv_obj_t结构体 - 初始化它的属性(位置、大小、样式等)
- 把它添加到父对象的子对象列表里
删除对象也简单:
lv_obj_del(btn); // 删除按钮
这里有个坑,我踩过。如果你删除了一个对象,它所有的子对象也会被递归删除。所以如果你有一个容器,里面放了10个按钮,你删容器,10个按钮也跟着没了。
警告:删除对象后,原来的指针就成了野指针。不要再访问它。我建议删除后立即把指针置为 NULL。
还有一种延迟删除的方式:
lv_obj_del_async(btn); // 安全删除,会在下一个事件循环中执行
这个在事件回调里特别有用。比如你点击一个按钮后要删除它,直接调用 lv_obj_del 可能会出问题,因为事件系统还在处理这个对象。用 lv_obj_del_async 就安全了。
2.3 父子层级关系
LVGL的对象组织方式,是一棵树。每个对象可以有多个子对象,但只有一个父对象。
为什么要搞父子关系?三个原因:
- 位置继承:子对象的位置是相对于父对象的。父对象移动,子对象跟着动。
- 可见性继承:父对象隐藏,子对象也隐藏。
- 事件传递:事件可以从子对象冒泡到父对象。
举个例子:
lv_obj_t * parent = lv_obj_create(lv_scr_act()); // 创建一个容器
lv_obj_set_size(parent, 200, 200);
lv_obj_t * child = lv_btn_create(parent); // 按钮的父对象是容器
lv_obj_set_pos(child, 10, 10); // 相对于容器的位置
如果你移动容器,按钮也跟着移动。如果你隐藏容器,按钮也看不见了。这就是父子关系的妙处。
小技巧:用 lv_obj_get_parent(child) 可以获取父对象,用 lv_obj_get_child(parent, 0) 可以获取第一个子对象。这在调试时很有用。
2.4 屏幕(Screen)与活动屏幕
屏幕是LVGL里最特殊的对象。它是所有对象的根节点。每个屏幕都是一个 lv_obj_t,但它没有父对象。
系统默认会创建一个屏幕,叫 lv_scr_act()。你创建的所有对象,最终都要挂到某个屏幕上。
你可以创建多个屏幕,然后切换:
lv_obj_t * screen1 = lv_obj_create(NULL); // 创建新屏幕,父对象为NULL
lv_obj_t * screen2 = lv_obj_create(NULL);
// 在screen1上放一些控件
lv_obj_t * btn1 = lv_btn_create(screen1);
lv_obj_t * label1 = lv_label_create(screen1);
lv_label_set_text(label1, "屏幕1");
// 在screen2上放一些控件
lv_obj_t * btn2 = lv_btn_create(screen2);
lv_obj_t * label2 = lv_label_create(screen2);
lv_label_set_text(label2, "屏幕2");
// 切换到screen1
lv_scr_load(screen1);
切换屏幕时,LVGL会做两件事:
- 隐藏旧屏幕
- 显示新屏幕
而且切换过程可以带动画效果:
lv_scr_load_anim(screen1, LV_SCR_LOAD_ANIM_MOVE_LEFT, 500, 0, false);
这个函数会让新屏幕从左边滑入,持续500毫秒。我个人很喜欢这个功能,做产品时用户体验会好很多。
注意:lv_scr_act() 返回的是当前活动屏幕。任何时候你创建对象,如果不指定父对象,默认父对象就是 lv_scr_act()。
我曾经在一个项目里,需要做多页面应用。每个页面是一个屏幕,切换时用滑动动画。效果非常流畅,用户反馈很好。但要注意,屏幕多了会占用内存。每个屏幕上的对象都会占用RAM。所以如果屏幕太多,建议用 lv_obj_del 删除不用的屏幕,或者用 lv_scr_load 配合对象池来管理。
嗯,关于对象模型,今天就聊这么多。下一章咱们会深入对象的属性,比如位置、大小、对齐方式。这些东西在实际开发中天天用,搞清楚了能省不少事。