从一个设备控制面板开始,系统学习 LVGL 界面开发

这次用 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 写出来的。

一、这次做的是一个设备控制面板

整个界面大概长这样:

这个界面模拟的是一个嵌入式设备上的控制面板。

它的风格有点像工业屏、智能中控屏、设备管理看板这类界面。

页面主要分成几个区域:

  1. 顶部状态栏
  2. 数据指标卡片
  3. 传感器趋势图
  4. 设备控制区
  5. 快捷功能区
  6. 底部导航栏

如果只看最终效果,界面里的内容似乎很多。

但从 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 复制代码
趋势图卡片
  ├── 标题
  ├── 图例
  └── 图表区域
        ├── 左侧刻度
        ├── 折线图
        ├── 右侧刻度
        └── 底部时间轴

这一块很适合学习两个内容:

  1. lv_chart 怎么画折线图。
  2. Grid 布局怎么把坐标轴和图表区域放到准确位置。

折线图本身由 lv_chart 负责绘制。

坐标轴、标题、图例这些内容,其实都是普通的 lv_labellv_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_MAINLV_PART_INDICATORLV_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)

它做的事情很直接:

  1. 创建一个新屏幕。
  2. 设置背景颜色。
  3. 创建根容器。
  4. 依次创建各个区域。

结构大概是:

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()

这比把所有代码都挤在一个函数里要清晰得多。


十二、这一组文章会怎么展开?

从这篇开始,后面每一篇会拆一个主题。

大概顺序是:

  1. 先讲 lv_obj,因为它是所有控件的基础。
  2. 再讲 lv_label,因为界面里的文字和图标都离不开它。
  3. 然后讲按钮、开关、滑条、圆弧这些常用控件。
  4. 再讲图表,因为这个界面里有趋势线。
  5. 接着讲事件,因为控件要真正动起来,需要事件回调。
  6. 再讲 Flex 和 Grid,因为页面布局离不开它们。
  7. 最后讲字体和中文显示,因为中文界面一定会遇到字库问题。

每篇文章只解决一个问题。

不追求一次性把 LVGL 全部讲完。

而是围绕这个设备控制面板,把每个知识点拆开讲清楚。


总结

这次用 LVGL 画的是一个设备控制面板。

它包含:

  • 顶部状态栏
  • 数据指标卡片
  • 传感器趋势图
  • LED 开关
  • 屏幕亮度滑条
  • 风扇转速圆弧
  • 系统音量条
  • 快捷功能按钮
  • 底部导航栏

从表面看,它是一个完整界面。

从 LVGL 学习角度看,它是一组知识点的集合。

每一个模块背后,都对应着后面要讲的一篇文章。

所以这一篇先不急着讲 API。

先把整体画面放在脑子里。

后面再一层一层拆:

text 复制代码
这个界面到底是怎么用 LVGL 画出来的。
相关推荐
创业之路&下一个五年1 小时前
委托、事件、发布-订阅模式全梳理(完整总结)
学习·总结
陈_杨2 小时前
鸿蒙APP开发-带你走进黑胶阁的唱片收藏怎么管理
前端·javascript
一天 24h2 小时前
Pinia 新手完全指南:从入门到精通的实战教程
前端·javascript·vue.js·pycharm·前端框架
MartinYeung52 小时前
[论文学习] 全同态加密下的加密文字比较与子字串搜寻演算法延伸研究
学习·区块链·同态加密
shadow_glory2 小时前
vue3自定义指令directives
前端·javascript·vue.js
Front思2 小时前
如何学习Shopify前端开发?
前端·学习
薛先生_0992 小时前
vue-路由模块封装
前端·javascript·vue.js
薛先生_0992 小时前
vue-router-link实现导航高亮效果
前端·javascript·vue.js
郑州光合科技余经理2 小时前
海外版外卖系统源码:支付/地图/多语言核心代码实现
android·java·前端·后端·架构·uni-app·php