1. LVGL 的核心架构总览
LVGL 架构可分为 5 个主要模块:
- 应用层:创建控件、注册事件、编写逻辑代码
- 对象系统(Object Tree):控件组织成树结构,样式、布局、事件处理等
- 渲染 & 布局引擎(Rendering + Layout):计算控件尺寸、位置、样式、绘制到缓存
- 驱动抽象层(Display/Input)
- 显示:flush_cb(刷屏)
- 输入:read_cb(读取触摸)
- 底层硬件 / RTOS 定时器:LVGL 需要周期性调用 lv_tick_inc + lv_timer
2.运转机制 — 技术详细解读
2.1 引擎架构
关键点:LVGL 引擎运转只靠三个核心条件:输入、输出、时间这三者是整个 LVGL 的“生命线”。
2.1.1 输入接口(Input)
解释:LVGL 需要从你那里获取用户的操作,比如触摸、按键、鼠标、编码器旋钮等。
要做的事情:注册一个 read_cb()
函数
这个函数会在每一轮 lv_timer_handler()
调用时被执行,LVGL 会去查询:
- 用户有没有触摸?
- 触摸了哪里?
- 当前是按下状态还是释放状态?
void touchpad_read_cb(lv_indev_drv_t *drv, lv_indev_data_t *data) {
if (is_touched()) {
data->point.x = get_touch_x();
data->point.y = get_touch_y();
data->state = LV_INDEV_STATE_PRESSED;
} else {
data->state = LV_INDEV_STATE_RELEASED;
}
}
类比为:“用户在说话”,LVGL 是“听众”,靠你搭建的这根麦克风(read_cb)听懂用户说了什么。
2.1.2 显示接口(Display)
解释:LVGL 并不直接控制 LCD,而是在内存中先画好内容(framebuffer),然后调用驱动程序“把这块内容刷出去”。
要做的事情:实现 flush_cb()
函数,把渲染缓冲区里的像素数据传到实际屏幕。
static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p){
if(disp_flush_enabled) {
int32_t w = area->x2 - area->x1 + 1;
int32_t h = area->y2 - area->y1 + 1;
LCD_Address_Set(area->x1,area->y1,area->x2,area->y2);
LCD_WritePixels_DMA((uint16_t *)color_p, w*h);
}
lv_disp_flush_ready(disp_drv);
}
类比为:“LVGL 是设计师,画好了图纸”,你是粉刷工人,负责按照图纸去刷墙。
2.1.3 时间驱动(Tick)
解释:LVGL 不会自动运转,它是一个“被动调度”的引擎。你必须定时告诉它:“时间过去了”、“检查一下有没有事要干”。
有两个关键函数:
lv_tick_inc(1); // 每 1 毫秒调用一次(通常在系统定时器中)
lv_timer_handler(); // 每帧调用一次(建议每 5~10ms)
lv_tick_inc()
:推进全局“时钟”lv_timer_handler()
:核心主循环,处理所有更新逻辑
类比为:“你给系统上发条”,否则动画不会动、事件不处理、UI 不更新。
2.2 内部模块
2.2.1 控件树(Widget Tree)
解释:LVGL 的所有控件都是从 lv_obj_t
派生的,构成一棵树状结构,层层嵌套,像 HTML DOM 树。
lv_obj_t *btn = lv_btn_create(parent);
lv_obj_t *label = lv_label_create(btn);
- 父子关系决定了位置、样式继承、事件冒泡等
- 所有控件从
lv_scr_act()
(当前屏幕对象)开始
2.2.2 事件与动画系统
解释:
- 用户点击按钮时触发
LV_EVENT_CLICKED
- 控件支持内置的动画(滑动、缩放、颜色渐变等)
- 所有的事件和动画都会在
lv_timer_handler()
中自动调度
lv_obj_add_event_cb(btn, my_btn_cb, LV_EVENT_CLICKED, NULL);
LVGL 会在合适的时候自动触发 my_btn_cb()
回调。
2.3 lv_timer_handler循环剖析
建议每 10 ~ 20 毫秒调用一次(即每秒运行 100~200 次)太慢,动画卡顿、事件响应延迟;太快,浪费资源(LCD 一般刷不出更高帧率)。
- 读取输入:调用输入设备的
read_cb()
,获取触摸或按键状态。 - 处理动画:推进动画播放,更新控件位置、颜色等属性。
- 分发事件:触发控件的事件回调函数,如点击、长按等。
- 更新布局:根据控件变化,重新计算位置和尺寸。
- 标记重绘区域:找出哪些控件需要重绘,记录为“脏区域”。
- 重绘并刷新:在内存中绘制界面,并调用
flush_cb()
刷到 LCD。 - 调度定时器: 运行你注册的
lv_timer_t
类型定时任务。
lv_timer_handler()
负责驱动 LVGL 内部所有运行机制,让界面能自动响应用户、更新动画、刷新控件、执行事件与定时任务。是 LVGL 的主循环(大脑)和心跳(节拍器)
2.4 开发范式
开发者要真正让 LVGL 跑起来,其实流程非常清晰,只需掌握三个核心步骤:
初始化:先初始化 LVGL 本体,并接好以下几个关键模块:
- 显示驱动:注册
flush_cb
回调函数,让 LVGL 能把图像刷新到实际的 LCD;
- 输入设备:注册
read_cb
回调函数,让 LVGL 能读取触摸屏或按键状态; - 时间驱动:定期调用
lv_tick_inc()
推进时钟,并定时调用lv_timer_handler()
启动主循环。
- 显示驱动:注册
构建界面:使用 LVGL 提供的控件系统搭建 UI 界面:
- 创建按钮、标签、图标等控件对象;
- 使用布局系统(如 Flex、Grid)组织控件;
- 设置控件样式、属性与层级结构。
注册事件回调:为控件绑定事件处理函数,例如:
- 点击按钮后执行某个功能;
- 拖动滑块时更新显示数值;
- 长按触发菜单等交互逻辑。
界面构建后,其余流程(包括界面重绘、动画播放、事件分发、控件管理等)都由 LVGL 内部自动完成。开发者只需专注于控件创建与逻辑设计,不需要手动管理绘图和更新流程。但请注意:
- 控件对象的销毁需显式调用
lv_obj_del()
; - 除非使用
lv_scr_load()
切换屏幕,系统才会自动销毁旧界面对象。
3. 相关概念
3.1 显示器和显示对象
- 显示器(Display Panel):显示器(或显示面板)指的是物理硬件层面的 LCD、OLED、TFT 屏幕,是你实际看的那个屏幕。
它可能是:
- 一块 128×160 的 ST7735 LCD 面板
- 一块 480×320 的 RGB 屏
- 或者是模拟器上的虚拟屏幕(如 PC 上的模拟仿真窗口)
- 显示对象(
lv_display_t
)lv_display_t
是 LVGL 用来管理一块“显示面板”的核心结构。它包含驱动信息、缓冲区、屏幕列表、当前显示状态等。
每一块独立的显示器(物理或虚拟),都会在内部用一个 lv_display_t
对象来表示。它内部会:
- 保存你注册的显示驱动(flush_cb 等)
- 管理多个屏幕对象(screen list)
- 管理当前正在显示的 screen
- 管理 draw buffer(图像缓冲)
- 屏幕(Screen):LVGL 中的“屏幕”指的是一个 lv_obj_t 类型的根对象,它代表一个完整的页面或界面容器。
每个 screen:
- 是控件的容器,可以创建按钮、标签、图标等子控件
- 没有父对象,它直接挂在
lv_display_t
的screens
列表下 - 是用户可见的“画面”,可以切换或销毁
三者之间的关系(类比说明)
概念 | 类比 | 在 LVGL 中 |
---|---|---|
显示器(面板) | 一块幕布(物理屏幕) | 由 lv_display_t 表示 |
显示对象 | 放映设备 | 管理 draw buffer + 驱动接口 |
屏幕(Screen) | 当前播放的影片画面 | lv_obj_t 的根节点,可切换 |
3.2 控件
控件(Widget) 是构建图形用户界面的核心组成部分。你可以把它们看作是「按钮、标签、图标、滑块」等 可视化组件,类似于 Web 的 DOM 元素、Qt 的 QWidget、安卓的 View。
- LVGL 中控件的本质
在 LVGL 中,每一个控件都是一个 lv_obj_t
对象,所有控件都是从它派生出来的。控件之间可以嵌套,形成控件树(Object Tree),方便组织结构和层级关系。
- 常见控件分类与用途
控件类型 | 函数 | 用途 |
---|---|---|
基础控件 | ||
标签(Label) | lv_label_create() | 显示文字 |
按钮(Button) | lv_btn_create() | 用于点击交互 |
图像(Image) | lv_img_create() | 显示图片(支持 JPG/PNG) |
线条(Line) | lv_line_create() | 绘制一条线 |
块(Object) | lv_obj_create() | 通用容器控件(可用作背景、面板) |
输入控件 | ||
滑块(Slider) | lv_slider_create() | 选择数值范围 |
切换按钮(Switch) | lv_switch_create() | 开关型组件 |
滚轮(Roller) | lv_roller_create() | 类似滚动选择器 |
文本框(TextArea) | lv_textarea_create() | 文本输入框 |
键盘(Keyboard) | lv_keyboard_create() | 屏幕输入键盘 |
高级控件 | ||
列表(List) | lv_list_create() | 滚动式项目列表 |
表格(Table) | lv_table_create() | 网格化的数据表格 |
图表(Chart) | lv_chart_create() | 绘制折线图、柱状图等 |
页面(Tabview) | lv_tabview_create() | 标签页切换式界面 |
下拉框(Dropdown) | lv_dropdown_create() | 下拉选择菜单 |
滚动容器(Tileview) | lv_tileview_create() | 多个页面滑动切换 |
消息框(Msgbox) | lv_msgbox_create() | 弹出式消息窗口 |
日历(Calendar) | lv_calendar_create() | 日期选择控件 |
颜色选择器(Colorwheel) | lv_colorwheel_create() | 颜色拾取控件 |
布局容器 | ||
Flex 容器 | lv_obj_set_flex_flow() | 弹性盒布局 |
Grid 网格 | lv_obj_set_grid_dsc_array() | 网格布局容器 |
3. 属性支持
- 大小、位置(
lv_obj_set_size()
/lv_obj_align()
) - 布局与对齐(flex/grid)
- 样式(颜色、边框、圆角、字体)
- 状态(隐藏、禁用、焦点等)
- 动画效果
- 子对象嵌套
- 事件回调(点击、滑动、值变、焦点等)
3.3 控件树
在 LVGL 中,控件(lv_obj_t
)之间的嵌套关系,是整个 GUI 系统的基础。你可以将其理解为一个 “控件树”,也就是一种典型的 父子对象结构,非常类似于网页中的 DOM 结构,或 Qt 中的控件层级。
- 控件嵌套是怎么回事?
所有 LVGL 控件都可以有“父对象”和“子对象”。
- 子控件的坐标、大小、布局是相对于父控件来计算的;
- 父控件销毁时,子控件会一并被销毁;
- 父控件控制了子控件的裁剪区域和显示顺序。
所以,屏幕(screen)其实也是一个控件(lv_obj_t),它是所有控件的“根”。
- 控件树的结构(示意)
lv_scr_act() // 当前屏幕对象
├── Panel(面板)
│ ├── Label(文字)
│ └── Slider(滑块)
└── Button(按钮)
└── Label(按钮文字)
这个结构是通过以下代码创建的:
lv_obj_t *scr = lv_scr_act(); // 当前屏幕
lv_obj_t *panel = lv_obj_create(scr); // panel 是屏幕的子对象
lv_obj_t *label = lv_label_create(panel); // label 是 panel 的子对象
lv_obj_t *slider = lv_slider_create(panel); // slider 也是 panel 的子对象
lv_obj_t *btn = lv_btn_create(scr); // 按钮直接挂在屏幕上
lv_obj_t *btn_label = lv_label_create(btn); // 按钮里的文字是它的子对象
- 父子关系 API
创建控件时指定父对象:
lv_obj_t *child = lv_btn_create(parent); // parent 可以是屏幕、面板、按钮等
获取关系:
lv_obj_t *parent = lv_obj_get_parent(child);
lv_obj_t *first_child = lv_obj_get_child(parent, 0);
lv_obj_t *next_sibling = lv_obj_get_next(child);
4. 事件系统
事件(Event) 是控件响应交互的核心机制,类似于 “按下按钮执行某功能” 或 “滑块值变化时更新界面”——所有用户交互和控件状态变化,最终都会以事件的形式传递出来。LVGL 中,任何控件(lv_obj_t
)都可以注册一个或多个事件回调函数,用于响应特定事件,比如:
- 被点击(
LV_EVENT_CLICKED
) - 被按下(
LV_EVENT_PRESSED
) - 值发生变化(
LV_EVENT_VALUE_CHANGED
) - 被长按(
LV_EVENT_LONG_PRESSED
) - 获取焦点、被隐藏、拖动、滚动……
4.1 事件回调及使用
- 添加事件回调:
// 回调函数
void my_event_cb(lv_event_t *e) {
lv_obj_t *target = lv_event_get_target(e); // 被触发事件的控件
lv_event_code_t code = lv_event_get_code(e); // 事件类型
if(code == LV_EVENT_CLICKED) {
LV_LOG_USER("Button clicked!");
}
}
// 添加事件
lv_obj_add_event_cb(obj, my_event_cb, LV_EVENT_ALL, NULL);
- 常见的事件类型:
事件宏 | 含义 | 说明 |
---|---|---|
LV_EVENT_PRESSED | 按下 | 手指/鼠标按下但未释放 |
LV_EVENT_RELEASED | 释放 | 手指/鼠标松开 |
LV_EVENT_CLICKED | 点击 | 按下 + 松开后触发 |
LV_EVENT_LONG_PRESSED | 长按 | 长时间按住 |
LV_EVENT_VALUE_CHANGED | 值变 | 滑块/开关等控件数值变更 |
LV_EVENT_DRAW_MAIN | 绘制 | 控件重绘时触发(可自定义样式) |
LV_EVENT_FOCUSED / DEFOCUSED | 获取/失去焦点 | 键盘或导航模式下常用 |
LV_EVENT_READY / CANCEL | 操作完成/取消 | 文本框、消息框 |
LV_EVENT_SCROLL | 滚动中 | 滚动容器正在移动 |
LV_EVENT_DELETE | 控件即将销毁 | 常用于释放资源 |
注意:不是每个控件都会触发所有事件,具体取决于控件的功能类型。
- 与
lv_timer_handler()
的关系
- 所有事件都不会立刻执行,而是 在
lv_timer_handler()
被调用时触发; - 所以事件处理依赖于你的主循环定期调用
lv_timer_handler()
(通常每 5~10ms); - 没有心跳,事件就不会“冒泡”上来。
4.2 实用示例
按钮点击时改变标签内容:
void btn_cb(lv_event_t *e) {
lv_obj_t *label = lv_event_get_user_data(e);
lv_label_set_text(label, "按钮被点击!");
}
lv_obj_t *btn = lv_btn_create(lv_scr_act());
lv_obj_t *label = lv_label_create(btn);
lv_label_set_text(label, "Hello");
lv_obj_add_event_cb(btn, btn_cb, LV_EVENT_CLICKED, label);
滑块值变动更新数值显示:
void slider_cb(lv_event_t *e) {
lv_obj_t *slider = lv_event_get_target(e);
int val = lv_slider_get_value(slider);
printf("当前值: %d\n", val);
}
lv_obj_add_event_cb(slider, slider_cb, LV_EVENT_VALUE_CHANGED, NULL);
5. 部件与状态
部件(Part) 和 状态(State) 是构建丰富交互 UI 的核心机制。它们控制着控件的样式变化、行为反馈和视觉效果,是 LVGL 样式系统的基础。
5.1 部件
Part(部件) 指的是控件的不同组成区域。LVGL 的控件大多不只是一个“整体”,而是可以分成若干部分来分别设置样式。
控件 | 部件(Part) | 含义 |
---|---|---|
lv_btn (按钮) | LV_PART_MAIN | 整个按钮的背景 |
lv_slider (滑块) | LV_PART_MAIN | 背景轨道 |
LV_PART_INDICATOR | 滑块已选中的部分 | |
LV_PART_KNOB | 可拖动的圆点 | |
lv_label | LV_PART_MAIN | 文本内容区域 |
lv_bar (进度条) | LV_PART_MAIN | 底部轨道 |
LV_PART_INDICATOR | 进度条的填充部分 |
设置样式时指定部件:
lv_obj_set_style_bg_color(slider, lv_color_blue(), LV_PART_INDICATOR);
lv_obj_set_style_radius(slider, 10, LV_PART_KNOB);
每个控件至少有一个 LV_PART_MAIN
,部分控件有多个可细分的部件。
5.2 状态
State(状态) 是控件在运行中可能出现的交互或视觉状态,常见状态枚举(可组合):
宏定义 | 含义 |
---|---|
LV_STATE_DEFAULT | 默认状态 |
LV_STATE_PRESSED | 正在按下 |
LV_STATE_CHECKED | 已选中(用于开关、复选框等) |
LV_STATE_FOCUSED | 获得焦点(键盘/遥控) |
LV_STATE_DISABLED | 被禁用,灰色不可交互 |
LV_STATE_EDITED | 正在编辑 |
LV_STATE_HOVERED | 鼠标悬停(桌面环境) |
LV_STATE_USER_1 ~ 4 | 用户自定义状态(高级用途) |
LVGL 内部会自动检测并切换状态,例如:
- 按下按钮 → 自动进入
LV_STATE_PRESSED
lv_obj_add_state(obj, LV_STATE_DISABLED)
→ 手动设置为禁用- 焦点系统/滑块切换时自动设置
FOCUSED
也可以手动设置状态:
lv_obj_add_state(obj, LV_STATE_DISABLED);
lv_obj_clear_state(obj, LV_STATE_DISABLED);
5.3 部件 + 状态 = 精准样式控制
LVGL 的样式系统允许你根据 不同部件、不同状态 来设置样式,实现复杂 UI 效果。
示例:按钮的样式分层控制
// 默认背景颜色
lv_obj_set_style_bg_color(btn, lv_color_white(), LV_PART_MAIN | LV_STATE_DEFAULT);
// 被按下时背景变绿
lv_obj_set_style_bg_color(btn, lv_palette_main(LV_PALETTE_GREEN), LV_PART_MAIN | LV_STATE_PRESSED);
// 禁用时背景变灰
lv_obj_set_style_bg_color(btn, lv_color_hex(0xAAAAAA), LV_PART_MAIN | LV_STATE_DISABLED);
6. 样式
样式(Style)系统 是构建精美 UI 的核心机制。你可以通过样式来控制每个控件的外观,比如颜色、圆角、边框、阴影、字体、透明度、对齐方式等。LVGL 的样式系统非常灵活,可以通过 lv_style_t
自定义样式,也可以通过简化函数快速设置单个属性。
类型 | 样式属性 | 说明 |
---|---|---|
背景 | bg_color , bg_grad_color , bg_opa | 控件背景色 |
边框 | border_width , border_color | 控件边框 |
圆角 | radius | 控件边缘圆角半径 |
字体 | text_font , text_color | 文本样式 |
阴影 | shadow_color , shadow_width | 控件的投影 |
填充 | pad_top , pad_left 等 | 内边距 |
布局 | width , height , align | 尺寸和对齐方式 |
6.1 样式使用方式
- 快速设置某个属性
lv_obj_set_style_bg_color(btn, lv_palette_main(LV_PALETTE_BLUE), LV_PART_MAIN);
lv_obj_set_style_radius(btn, 10, LV_PART_MAIN);
这种方式适合简单的样式修改,不需要声明 lv_style_t
对象。
- 使用
lv_style_t
样式对象(推荐)
lv_style_t style;
lv_style_init(&style);
lv_style_set_bg_color(&style, lv_palette_main(LV_PALETTE_RED));
lv_style_set_radius(&style, 8);
lv_style_set_text_color(&style, lv_color_white());
lv_obj_add_style(btn, &style, LV_PART_MAIN);
可以将同一个样式重复用于多个控件,样式复用性强。
6.2 常用样式属性(函数接口)
样式类别 | 设置函数 | 示例 |
---|---|---|
背景颜色 | lv_style_set_bg_color | 设置背景色 |
背景不透明度 | lv_style_set_bg_opa | 设置透明度 |
边框颜色 | lv_style_set_border_color | 设置边框颜色 |
边框宽度 | lv_style_set_border_width | 0 表示无边框 |
圆角 | lv_style_set_radius | LV_RADIUS_CIRCLE 表示圆形 |
字体 | lv_style_set_text_font | 设置字体(如 &lv_font_montserrat_16 ) |
字体颜色 | lv_style_set_text_color | 设置文本颜色 |
内边距 | lv_style_set_pad_* | 控件内部的上下左右边距 |
阴影 | lv_style_set_shadow_color / shadow_width | 控件阴影样式 |
6.3 实用示例
自定义按钮样式
lv_style_t style_btn;
lv_style_init(&style_btn);
// 默认状态
lv_style_set_bg_color(&style_btn, lv_color_hex(0x3498db));
lv_style_set_radius(&style_btn, 10);
lv_style_set_text_color(&style_btn, lv_color_white());
// 按下状态
lv_style_set_bg_color(&style_btn, lv_color_hex(0x2ecc71));
// 应用到按钮
lv_obj_t *btn = lv_btn_create(lv_scr_act());
lv_obj_add_style(btn, &style_btn, LV_PART_MAIN);
7. 主题
主题(Theme) 是对多个控件的样式统一管理与复用机制。是 LVGL 样式系统的自动化高级封装,不用为每个控件都手动设置样式,只要启用一个主题,它会自动为你创建的每个控件设置默认样式。
lv_disp_t *disp = lv_disp_get_default();
lv_theme_t *theme = lv_theme_default_init(disp, lv_palette_main(LV_PALETTE_BLUE),
lv_palette_main(LV_PALETTE_RED),
LV_THEME_DEFAULT_DARK, &lv_font_montserrat_14);
lv_disp_set_theme(disp, theme);
设置后,创建的按钮、标签、滑块等,都会自动带有默认风格,比如蓝底白字、统一字体、圆角大小等。
7.1 默认主题使用方式
设置默认主题:
lv_theme_t *theme = lv_theme_default_init(
lv_disp_get_default(), // 绑定显示器
lv_palette_main(LV_PALETTE_BLUE), // 主色
lv_palette_main(LV_PALETTE_RED), // 次色
false, // false=亮色模式, true=暗色
&lv_font_montserrat_14); // 默认字体
lv_disp_set_theme(lv_disp_get_default(), theme);
常用调色板:
LV_PALETTE_RED
LV_PALETTE_BLUE
LV_PALETTE_GREEN
LV_PALETTE_GREY
LV_PALETTE_ORANGE
7.2 自定义主题(高级用法)
可以写自己的主题模块,并在初始化阶段绑定:
lv_theme_t *my_theme = my_custom_theme_init(...);
lv_disp_set_theme(disp, my_theme);
自定义主题结构示意:
lv_theme_t *my_custom_theme_init(...) {
static lv_theme_t theme;
lv_theme_init(&theme, ...);
theme.apply_cb = my_theme_apply;
return &theme;
}
void my_theme_apply(lv_theme_t *th, lv_obj_t *obj) {
if (lv_obj_check_type(obj, &lv_btn_class)) {
// 设置按钮默认样式
lv_obj_add_style(obj, &my_btn_style, LV_PART_MAIN);
}
}