2、LVGL架构总览:软件架构、模块划分、核心组件与渲染管线
好,咱们进入第二章。上一章我们搭好了环境,跑起了第一个demo。现在该把LVGL的“骨架”拆开看看了。
我刚开始接触LVGL时,第一反应是“这玩意儿代码量不小啊”。但真正理清它的架构后,发现设计得相当清晰。说白了,LVGL就是一个面向对象的嵌入式GUI框架,用C语言模拟了类的继承和多态。
2.1 软件架构:三层结构
LVGL的软件架构可以分成三层。我个人习惯把它想象成一个汉堡:
- 底层(面包底):硬件驱动 + 操作系统抽象层。负责屏幕刷新、触摸输入、定时器、内存管理。
- 中间层(肉饼):核心库。包括对象管理、事件系统、样式系统、布局引擎、动画引擎、绘图API。
- 上层(面包顶):控件库(Widgets)。所有你看到的按钮、标签、滑块、图表,都在这一层。
关键点:上层控件不直接操作硬件,它们只调用中间层的绘图API。中间层再通过底层驱动把像素刷到屏幕上。这种分层的好处是——换屏幕驱动?改底层就行,上层代码不用动。
2.2 模块划分:核心模块一览
LVGL的源码目录结构,其实就是模块划分的直接体现。我列一下最重要的几个模块:
| 模块 | 目录/前缀 | 职责 |
|---|---|---|
| 核心对象 | lv_obj |
所有控件的基类。创建、删除、移动、层级管理。 |
| 样式系统 | lv_style |
管理颜色、边框、阴影、圆角等视觉属性。支持CSS-like级联。 |
| 事件系统 | lv_event |
触摸、按键、自定义事件的注册、分发、冒泡。 |
| 布局引擎 | lv_layout |
Flexbox和Grid布局。自动计算子控件位置。 |
| 绘图引擎 | lv_draw |
绘制矩形、圆、文字、图片、弧线。支持抗锯齿。 |
| 动画引擎 | lv_anim |
属性插值、缓动函数、动画路径、自动回弹。 |
| 字体/图片 | lv_font / lv_img |
字体加载、图片解码、缓存管理。 |
| 输入设备 | lv_indev |
触摸、鼠标、键盘、编码器的抽象接口。 |
| 控件库 | lv_btn, lv_label ... |
几十种现成控件,开箱即用。 |
我的经验:刚开始不用把所有模块都啃完。先掌握 lv_obj、lv_style、lv_event 这三个,就能应付80%的开发场景。其他模块用到再查。
2.3 核心组件:对象、样式、事件
这三个组件是LVGL的“三驾马车”。我分别说一下。
2.3.1 对象(lv_obj)
LVGL里所有可见的东西都是对象。按钮是对象,标签是对象,甚至屏幕本身也是一个对象。对象之间可以形成父子关系:
lv_obj_t *scr = lv_scr_act(); // 获取当前屏幕(父对象)
lv_obj_t *btn = lv_btn_create(scr); // 在屏幕上创建按钮(子对象)
lv_obj_t *label = lv_label_create(btn); // 在按钮上创建标签(子对象的子对象)
父子关系决定了:
- 位置:子对象坐标相对于父对象。
- 裁剪:子对象超出父对象范围的部分会被裁剪。
- 事件冒泡:事件可以从子对象冒泡到父对象。
- 生命周期:删除父对象,所有子对象自动删除。
避坑指南:我曾经在项目中忘记设置父对象,直接把按钮创建在NULL上。结果按钮显示不出来,排查了半天。记住:lv_btn_create(NULL) 会创建一个“孤儿对象”,它不在任何屏幕上,自然看不见。
2.3.2 样式(lv_style)
样式就是控件的“皮肤”。LVGL的样式系统支持级联——你可以给屏幕设置一个全局样式,给按钮组设置一个组样式,再给某个按钮单独设置样式。优先级是:局部 > 组 > 全局。
lv_style_t style;
lv_style_init(&style);
lv_style_set_bg_color(&style, lv_color_hex(0x2196F3));
lv_style_set_radius(&style, 8);
lv_obj_add_style(btn, &style, LV_STATE_DEFAULT);
这里有个细节:样式可以绑定到不同的状态(LV_STATE_DEFAULT、LV_STATE_PRESSED、LV_STATE_DISABLED等)。按下的按钮和禁用的按钮可以有不同的样式。
2.3.3 事件(lv_event)
事件是用户交互的入口。LVGL的事件模型很简单:注册一个回调,当事件发生时回调被调用。
void btn_event_cb(lv_event_t *e) {
lv_event_code_t code = lv_event_get_code(e);
if (code == LV_EVENT_CLICKED) {
LV_LOG_USER("按钮被点击了!");
}
}
lv_obj_add_event_cb(btn, btn_event_cb, LV_EVENT_CLICKED, NULL);
事件类型很多,常用的有:LV_EVENT_CLICKED(点击)、LV_EVENT_PRESSING(按住中)、LV_EVENT_VALUE_CHANGED(值改变,比如滑块拖动)。
2.4 渲染管线概览:从事件到像素
好,现在我们把所有模块串起来,看看一次点击事件到屏幕像素变化,到底经历了什么。这就是LVGL的渲染管线。
- 输入采集:底层驱动检测到触摸按下,上报坐标。
- 事件分发:输入设备模块找到触摸点下的对象,触发
LV_EVENT_PRESSED事件。 - 应用逻辑:你的事件回调执行。比如修改了某个标签的文字。
- 标记脏区域:LVGL自动检测到对象属性变化,标记该对象所在的矩形区域为“需要重绘”。
- 重绘调度:在
lv_timer_handler()中,LVGL遍历所有脏区域,调用绘图引擎进行重绘。 - 绘图引擎执行:绘图引擎根据对象的样式、内容(文字、图片等),在内存中的缓冲区绘制像素。
- 刷新到屏幕:绘图完成后,调用显示驱动的
flush_cb,将缓冲区内容复制到屏幕显存。
嗯,这里要注意:LVGL并不是每帧都全屏重绘。它只重绘“脏区域”——也就是属性发生变化的那一小块。这大大提升了性能。我在一个只有64KB RAM的MCU上跑过LVGL,只要脏区域控制得好,帧率完全能接受。
2.5 小结
这一章我们把LVGL的架构拆了一遍。你记住三个数字就行:三层架构、九大模块、七步渲染管线。后面每一章都会围绕这些模块展开。
说白了,LVGL的设计哲学就是:分层解耦 + 脏区域重绘。理解了这个,你就能明白为什么它能在资源受限的嵌入式设备上跑得这么流畅。