这次用 LVGL 画了一个怎样的界面?
前言:先看成品,再拆控件
学习 LVGL 时,我一直觉得有一个问题很容易被忽略:
很多教程习惯从 API 开始讲。
比如:
lv_obj_create()lv_label_create()lv_button_create()lv_obj_set_style_bg_color()
这些函数当然重要。
但如果一开始只盯着函数,很容易产生一种感觉:
text
每个函数我好像都能看懂,但不知道它们组合起来到底能做什么。
所以这一组文章,我不打算一上来就把函数表铺开讲。
我想先从一个完整界面开始。
这次做的是一个基于 LVGL 的设备控制面板。
它不是一个复杂的真实产品界面,但它包含了学习 LVGL 时非常典型的一批内容:
- 对象
- 标签
- 图标
- 卡片
- 折线图
- 开关
- 滑条
- 圆弧仪表盘
- 自定义音量条
- Flex 布局
- Grid 布局
- 中文字体
- 事件回调
也就是说,这个界面刚好可以作为一条 LVGL 学习路线。
我们先看它画出了什么。
然后在后面的文章里,再一篇一篇拆开讲:
text
这个界面里的每一个部分,到底是怎么用 LVGL 写出来的。
一、这次做的是一个设备控制面板
整个界面大概长这样:

这个界面模拟的是一个嵌入式设备上的控制面板。
它的风格有点像工业屏、智能中控屏、设备管理看板这类界面。
页面主要分成几个区域:
- 顶部状态栏
- 数据指标卡片
- 传感器趋势图
- 设备控制区
- 快捷功能区
- 底部导航栏
如果只看最终效果,界面里的内容似乎很多。
但从 LVGL 的角度拆开看,它其实就是一层一层对象组合出来的。
可以把它理解成这样:
text
屏幕
└── 根容器
├── 顶部状态栏
├── 数据卡片区域
├── 趋势图区域
├── 控制区域
├── 快捷功能区域
└── 底部导航栏
这也是用 LVGL 写界面的基本思路:
先创建父对象。
再往父对象里放子对象。
最后给这些对象设置尺寸、布局、样式和事件。
二、顶部状态栏画了什么?
顶部状态栏主要包含这些内容:
- 左侧图标
- 页面标题
- 中间时间
- 右侧 Wi-Fi 图标
- 电池图标和电量
- 平台和 LVGL 版本信息
效果大概是:
text
[图标] 设备控制面板 14:30:25 Wi-Fi 电池 85% RK3568 / LVGL 9.2
这一块主要用到:
lv_obj:作为状态栏容器lv_label:显示文字和图标LV_SYMBOL_*:显示 LVGL 内置符号- Grid 布局:把左、中、右三块放到不同列
- Flex 布局:让左侧和右侧内容横向排列
顶部状态栏看起来只是一个很普通的区域,但里面已经包含了不少 LVGL 基础知识。
比如下面这一行:
c
create_label(left, "设备控制面板", UI_FONT, COLOR_TEXT);
它背后其实涉及很多问题:
- 怎么创建标签?
- 怎么设置文字?
- 怎么设置字体?
- 怎么设置颜色?
- 中文为什么能正常显示?
所以后面会专门拆一篇文章讲 lv_label 和中文字体。
三、数据指标卡片画了什么?
页面上方有四张指标卡片:
| 卡片 | 显示内容 |
|---|---|
| 温度 | 32.6 °C |
| 湿度 | 58.3 %RH |
| CPU 使用率 | 42 % |
| 当前 FPS | 60 FPS |
每张卡片里都有:
- 一个图标
- 一个标题
- 一个大号数值
- 一个单位
- 一条小趋势线
这类卡片很适合用来练习 LVGL 的基础对象。
因为它不是一个单独的控件,而是一组控件组合出来的。
比如一张温度卡片,可以拆成这样:
text
card 对象
├── 图标 label
├── 标题 label
├── 数值 label
├── 单位 label
└── 小折线图 chart
在代码里,这些卡片通过一个通用函数来创建:
c
create_metric_card(...)
这个函数不是 LVGL 自带的。
它是我们自己封装的一个辅助函数,用来减少重复代码。
这也是写 LVGL 界面时很常见的做法:
先用 LVGL 的原始控件搭出一个结构。
当这个结构需要重复出现时,再把它封装成自己的函数。
四、趋势图区域画了什么?
中间左侧是一张"传感器趋势"图。
它用来显示最近一小时的三条曲线:
- 温度
- 湿度
- CPU
这里主要用到的是:
c
lv_chart
不过,图表区域并不只是一个 lv_chart。
它还包含:
- 图表标题
- 图例
- 左侧 Y 轴刻度
- 右侧 Y 轴刻度
- 中间折线图
- 底部时间轴
所以它真正的结构更接近这样:
text
趋势图卡片
├── 标题
├── 图例
└── 图表区域
├── 左侧刻度
├── 折线图
├── 右侧刻度
└── 底部时间轴
这一块很适合学习两个内容:
lv_chart怎么画折线图。- Grid 布局怎么把坐标轴和图表区域放到准确位置。
折线图本身由 lv_chart 负责绘制。
坐标轴、标题、图例这些内容,其实都是普通的 lv_label 和 lv_obj 组合出来的。
这也说明了一个很重要的思路:
LVGL 里的很多"复杂界面",并不是由某个神秘控件直接生成的。
它们通常是由多个基础对象一层一层组合出来的。
五、设备控制区画了什么?
右侧中间是设备控制区。
里面包含:
- LED 总开关
- 屏幕亮度滑条
- 风扇转速仪表盘
- 系统音量条
这一块是整个页面里最适合学习交互控件的部分。
1. LED 总开关
LED 总开关用的是:
c
lv_switch
它的核心不是样式,而是状态。
比如开关打开时,对象会进入:
c
LV_STATE_CHECKED
所以开关的打开和关闭,本质上就是这个状态有没有被设置。
后面讲 lv_switch 时,会重点看它的状态变化和事件回调。
2. 屏幕亮度滑条
屏幕亮度用的是:
c
lv_slider
它适合表达一个连续变化的数值。
比如:
text
0% 到 100%
滑条通常可以拆成三个关键部分:
- 主体轨道
- 已滑过的指示器
- 可以拖动的旋钮
对应到 LVGL 里就是:
c
LV_PART_MAIN
LV_PART_INDICATOR
LV_PART_KNOB
理解这三个 part,对后面定制滑条样式很重要。
3. 风扇转速仪表盘
风扇转速仪表盘用的是:
c
lv_arc
这个控件看起来像一个圆形滑条,但它比普通滑条多了一个角度概念。
比如代码里设置了背景弧线的角度范围:
c
lv_arc_set_bg_angles(arc, 200, 340);
这就是它能呈现出仪表盘效果的原因。
通过控制起始角度、结束角度、当前值和样式,就可以做出类似风扇转速、进度环、温度环这样的控件。
4. 系统音量条
系统音量条没有直接使用某个"音量控件"。
它是用一组普通对象画出来的。
每一根竖条都是一个 lv_obj。
然后根据当前音量值,决定哪些条亮起来,哪些条变暗。
这部分很适合说明一个思路:
text
LVGL 不只能使用现成控件。
普通 lv_obj 也可以组合成自定义控件。
很多嵌入式界面里的特殊控件,本质上都是这样做出来的。
六、快捷功能区画了什么?
底部上方有一排快捷功能按钮:
- 灯光
- 风扇
- 网络
- 设置
这些按钮看起来像卡片。
每个按钮里有:
- 左侧图标
- 中间文字
- 右侧箭头
这一块主要练习的是:
- 用
lv_obj做按钮容器 - 用
lv_label显示图标和文字 - 使用 Flex 横向布局
- 设置卡片样式
- 设置边框、圆角和背景色
比如一个快捷按钮,本质上可以拆成这样:
text
button-like obj
├── icon label
├── text label
└── right arrow label
它不一定非要使用 lv_button_create()。
普通的 lv_obj_create() 加上样式和事件,也可以做出按钮效果。
这一点对 LVGL 开发很重要。
因为在嵌入式界面里,很多控件都需要定制。
当现成控件不够用时,就可以用基础对象自己组合。
七、底部导航栏画了什么?
最下面是一个导航栏。
它包含四个导航项:
- 首页
- 控制
- 数据
- 关于
这一块同样主要使用 Flex 布局。
导航栏本身是一个父容器。
四个导航项横向排列,每个占 25% 宽度。
每个导航项里面又包含:
- 图标
- 文字
当前激活的导航项背景更亮,文字和图标颜色也更突出。
这部分会用到前面几篇文章里讲过的很多知识:
- 对象层级
- 标签
- 图标
- Flex 布局
- 状态样式
- 卡片式背景
所以它很适合作为一个综合练习。
八、这个界面主要用了哪些 LVGL 知识?
如果把这次界面拆成知识点,大概可以分成下面几类。
1. 基础对象
最核心的是:
c
lv_obj
几乎所有东西都是对象。
页面、卡片、容器、音量条、按钮背景,本质上都可以是一个 lv_obj。
2. 文本和图标
主要用的是:
c
lv_label
文字、数字、单位、图标,很多都是通过 lv_label 显示出来的。
LVGL 的内置图标,本质上也是字体符号。
所以理解 lv_label,也就顺带理解了 LVGL 里很多图标的显示方式。
3. 交互控件
主要包括:
c
lv_switch
lv_slider
lv_arc
它们分别解决:
- 开关状态
- 连续数值调节
- 圆弧仪表盘显示
这些控件不仅要会创建,还要理解它们的状态、取值范围和事件。
4. 数据展示
主要用的是:
c
lv_chart
它负责绘制折线趋势图。
在这个界面里,传感器趋势区就是通过 lv_chart 来展示温度、湿度和 CPU 的变化。
5. 布局
主要用到:
c
LV_LAYOUT_GRID
LV_LAYOUT_FLEX
Grid 用来搭页面的大框架。
Flex 用来排列一行或一列里的细节内容。
可以简单理解为:
text
大结构用 Grid。
小区域用 Flex。
6. 样式
页面里大量用到了样式设置函数,例如:
c
lv_obj_set_style_bg_color()
lv_obj_set_style_bg_opa()
lv_obj_set_style_border_width()
lv_obj_set_style_radius()
lv_obj_set_style_pad_all()
lv_obj_set_style_text_color()
这些函数决定了界面的颜色、透明度、边框、圆角、间距和文字颜色。
也就是说,界面最终看起来是什么风格,很大程度上都由样式系统决定。
7. 事件
交互部分离不开事件:
c
lv_obj_add_event_cb()
LV_EVENT_VALUE_CHANGED
LV_EVENT_PRESSED
LV_EVENT_PRESSING
比如:
- 开关变化时打印开关状态。
- 滑条变化时更新百分比。
- 圆弧拖动时更新风扇转速。
- 音量条按下或拖动时重新计算音量。
控件能不能"动起来",关键就在事件回调。
九、为什么选择深色 Dashboard 风格?
这次界面选择的是深色科技感风格。
原因主要有两个。
第一,设备控制面板、监控看板、工业屏这类界面,本来就很适合深色背景。
深色背景可以让高亮数据更突出。
比如:
- 蓝色表示温度或主要数据
- 青色表示湿度或风扇
- 绿色表示 CPU 或启用状态
- 紫色表示音量或 FPS
第二,深色界面对学习 LVGL 的样式系统也很有帮助。
因为在深色背景下,可以更明显地看出:
- 背景层级
- 卡片边界
- 边框线条
- 文本层次
- 控件不同
part的区别
比如一个滑条,如果放在白色背景上,LV_PART_MAIN、LV_PART_INDICATOR、LV_PART_KNOB 之间的区别可能不够明显。
换成深色背景之后,这些结构会更容易观察。
所以深色风格不只是为了好看。
它也更方便我们学习 LVGL 的样式系统。
十、这个项目为什么适合拆成一组文章?
因为它刚好覆盖了 LVGL 新手最常遇到的一批问题。
为什么第一个要学 lv_obj?
因为所有模块都离不开对象。
卡片、容器、音量条、导航项,很多都可以用 lv_obj 来实现。
文字怎么显示?
因为界面里到处都是 lv_label。
中文标题、数字、单位、图标都和 label 有关。
按钮、开关、滑条怎么理解?
因为控制区和快捷功能区里都有这些交互控件。
这些控件不仅要看样式,还要看状态、数值和事件。
圆弧为什么不只是圆形滑条?
因为风扇转速用了 lv_arc。
它和普通滑条相比,多了角度范围、弧线样式和仪表盘式的表现方式。
图表怎么画?
因为传感器趋势区用了 lv_chart。
这部分可以顺带学习折线图、数据序列、坐标轴和图例的组合方式。
事件怎么走?
因为所有交互最终都要靠事件回调连接起来。
开关变化、滑条拖动、音量调整,都离不开事件。
为什么需要 Flex 和 Grid?
因为这个界面不是几个控件随便摆一下。
它有完整的页面结构。
外层需要 Grid。
内部需要 Flex。
中文为什么会变成方块?
因为这个界面用了中文。
中文字体、字库裁剪、字符集,这些问题迟早都会遇到。
所以这个界面不是为了炫技。
它更像是一张 LVGL 学习地图。
后面的文章,就围绕这张地图一块一块往下拆。
十一、代码入口大概在哪里?
这个界面的入口函数是:
c
void app_create(void)
它做的事情很直接:
- 创建一个新屏幕。
- 设置背景颜色。
- 创建根容器。
- 依次创建各个区域。
结构大概是:
c
void app_create(void)
{
lv_obj_t * scr = lv_obj_create(NULL);
lv_screen_load(scr);
lv_obj_t * root = create_dashboard_root(scr);
create_top_bar(root);
create_card_area(root);
create_chart_card(root);
create_control_card(root);
create_quick_area(root);
create_nav_bar(root);
}
这段结构很值得学习。
它没有把所有代码都堆在一个函数里。
而是把页面拆成了几个区域:
text
top_bar
card_area
chart_card
control_card
quick_area
nav_bar
每个区域再继续拆成更小的函数。
这样写界面会更清楚,也更容易维护。
以后要改趋势图,就去看 create_chart_card()。
要改底部导航,就去看 create_nav_bar()。
这比把所有代码都挤在一个函数里要清晰得多。
十二、这一组文章会怎么展开?
从这篇开始,后面每一篇会拆一个主题。
大概顺序是:
- 先讲
lv_obj,因为它是所有控件的基础。 - 再讲
lv_label,因为界面里的文字和图标都离不开它。 - 然后讲按钮、开关、滑条、圆弧这些常用控件。
- 再讲图表,因为这个界面里有趋势线。
- 接着讲事件,因为控件要真正动起来,需要事件回调。
- 再讲 Flex 和 Grid,因为页面布局离不开它们。
- 最后讲字体和中文显示,因为中文界面一定会遇到字库问题。
每篇文章只解决一个问题。
不追求一次性把 LVGL 全部讲完。
而是围绕这个设备控制面板,把每个知识点拆开讲清楚。
总结
这次用 LVGL 画的是一个设备控制面板。
它包含:
- 顶部状态栏
- 数据指标卡片
- 传感器趋势图
- LED 开关
- 屏幕亮度滑条
- 风扇转速圆弧
- 系统音量条
- 快捷功能按钮
- 底部导航栏
从表面看,它是一个完整界面。
从 LVGL 学习角度看,它是一组知识点的集合。
每一个模块背后,都对应着后面要讲的一篇文章。
所以这一篇先不急着讲 API。
先把整体画面放在脑子里。
后面再一层一层拆:
text
这个界面到底是怎么用 LVGL 画出来的。