LVGL 选项卡视图应该是嵌入式 ESP32 屏幕开发里最实用的控件之一,早期很多人的仪表盘、菜单主页、配网页面都用它搭出来。这次我们把lv_tabview从创建、添加页面、切换事件到内存占用完整过一遍,并结合小屏幕适配和实际项目场景给出一套可以直接用的代码模板。
很多刚接触 ESP32 + LVGL 的开发者会遇到一个问题:官方示例能跑,但一到自己的项目里就出现花屏、内存不足、切换卡顿,甚至编译报错。这些大部分不是芯片问题,而是lv_conf.h配置、屏幕驱动和选项卡视图使用方式没对上。这篇文章会先讲清楚 LVGL 选项卡视图的核心能力和适用场景,再给出本地部署(模拟器调试 + 真机烧录)的完整流程,最后补充排错清单和工程化建议。
1. 核心能力速览
| 能力项 | 说明 |
|---|---|
| 项目类型 | 嵌入式 GUI 控件应用示例,基于 LVGL 图形库 |
| 控件名称 | lv_tabview(选项卡视图 / 标签页控件) |
| 核心功能 | 顶部/底部选项卡按钮 + 多个内容页面切换 |
| 典型搭配控件 | lv_obj容器、lv_label、lv_btn、lv_list、lv_group |
| 适合芯片 | ESP32、ESP32-S3(推荐带 PSRAM)、STM32 等可运行 LVGL 的 MCU |
| 屏幕接口 | SPI 屏(常见 ILI9341、ST7789、ST7735)或 RGB 屏,取决于移植的显示驱动 |
| 显示分辨率 | 320x240、480x320、240x240 等,需按屏幕实际分辨率配置 |
| 输入方式 | 触摸屏、按键、编码器、遥控器(通过lv_group接入) |
| 内存要求 | 与色深、分辨率、缓冲区和控件数量有关,建议 ESP32 使用 PSRAM 或降低色深 |
| 启动方式 | LVGL 模拟器(PC)/ 真机编译烧录 |
| 支持 API | 是,LVGL 提供原生 C API,可封装事件回调、页面切换接口 |
| 批量任务 | 不直接涉及,但可为多个传感器/设备页面做统一布局管理 |
| 适合场景 | 菜单设置、设备状态页、多模式切换、数据监控页、配网页面 |
从材料看,这个项目是嵌入式 ESP32 系列的 LVGL 实践内容,重点落在“选项卡视图”上。选项卡视图不是某个独立软件,而是 LVGL 内置的标准控件,开发者可以基于它快速搭出多页面 UI,再配合不同的内容容器承载实时数据、控制按钮或状态展示。
2. 适用场景与使用边界
2.1 适合谁
- 正在做 ESP32 屏幕项目,需要多页面切换功能的开发者;
- 想从基础控件开始搭建 LVGL 界面的嵌入式初学者;
- 需要在 MCU 上实现“仪表盘 + 设置页 + 数据页”这类常见结构的开发者;
- 正在学习 LVGL 源码和对象模型的开发者。
2.2 能解决什么问题
选项卡视图解决的是“如何在有限的屏幕空间内组织多块内容”。如果不用选项卡,你会面临两个常见麻烦:
- 页面切换逻辑自己写,状态管理混乱;
- 页面堆叠显示,内存占用高,屏幕还容易乱。
使用lv_tabview后,每个选项卡对应一个独立容器,切换时 LVGL 自动处理显示隐藏关系,开发者只需要往每个容器里塞内容。
2.3 不适合什么场景
- 需要高度自定义交互的页面流,比如多个页面之间的复杂转场动画,选项卡视图的切换效果比较基础;
- 内容量极大的列表类应用,更适合用
lv_list或lv_menu; - 屏幕空间极小(如 128x64)的设备,选项卡按钮会挤压内容区域,建议改用滑块式或翻页式布局。
2.4 使用边界与合规提醒
本示例属于嵌入式 UI 开发技术实践。如果在真实项目中接入传感器数据、摄像头画面、联网信息或用户数据,请务必遵守设备所在地区的数据保护规定,只在合法授权的数据和设备范围内进行处理。涉及第三方图标、字体、素材时,注意版权合规;涉及用户个人信息展示时,做好脱敏和权限控制。
3. 环境准备与前置条件
3.1 硬件选择
| 硬件 | 用途 | 建议 |
|---|---|---|
| ESP32 / ESP32-S3 开发板 | 运行 LVGL 的主控 | 首选带 PSRAM 的型号,复杂页面更从容 |
| SPI 屏幕 | 显示 UI | 常见 2.4 寸、2.8 寸、3.5 寸,分辨率 320x240 居多 |
| 触摸/按键模块 | 切换选项卡 | 可选,键盘输入通过lv_group也能切换 |
| USB 转 TTL 或开发板自带串口 | 烧录和日志输出 | 必备 |
实际选用哪个型号,取决于你的屏幕驱动 IC 和引脚定义。ILI9341、ST7789、ST7735 在 LVGL 项目中都很常见。
3.2 软件环境
| 软件 | 用途 | 说明 |
|---|---|---|
| ESP-IDF 或 Arduino ESP32 环境 | 编译、烧录、运行 | 二选一即可,本项目通常用 ESP-IDF 更顺手 |
| LVGL 源码 | GUI 图形库 | 版本建议用当前主流稳定版,如 8.3.x 系列 |
| 屏幕驱动 | 显示底层 | TFT_eSPI(Arduino 平台)或 ESP-IDF 下的显示驱动封装 |
| PC 模拟器(可选) | 先在电脑上调 UI | lv_sim_eclipse_sdl或lv_sim_vscode_sdl都可 |
| 串口工具 | 查看日志 | idf.py monitor或 Arduino 串口监视器 |
3.3 通用检查清单
在开始敲代码前,先确认以下内容:
- 开发板能否正常烧录基础例程(比如点灯程序);
- 屏幕接线是否和驱动代码里的引脚定义一致;
lv_conf.h是否已经复制到项目并开启LV_USE_TABVIEW;- 是否根据屏幕分辨率正确设置
LV_HOR_RES_MAX和LV_VER_RES_MAX; - 是否有足够的 FLASH 空间存放中文字库或图片资源。
如果没有现成的屏幕驱动,建议先用 LVGL PC 模拟器跑通选项卡视图,再迁移到真机。这样 UI 逻辑和硬件问题可以分开排查。
4. LVGL 选项卡视图本地部署与启动
4.1 在 PC 模拟器上验证
先不碰硬件,在 PC 上创建一个 LVGL 模拟器工程(以 SDL 模拟器为例),核心思路是初始化 LVGL 后创建一个选项卡视图并加几个页面。
#include "lvgl.h" #include <stdio.h> void tabview_demo(void) { lv_obj_t *tabview = lv_tabview_create(lv_scr_act(), LV_DIR_TOP, 50); /* 第 1 个选项卡页面 */ lv_obj_t *tab1 = lv_tabview_add_tab(tabview, "首页"); lv_obj_t *label1 = lv_label_create(tab1); lv_label_set_text(label1, "这是首页内容"); lv_obj_center(label1); /* 第 2 个选项卡页面 */ lv_obj_t *tab2 = lv_tabview_add_tab(tabview, "设置"); lv_obj_t *label2 = lv_label_create(tab2); lv_label_set_text(label2, "设置页内容"); lv_obj_center(label2); /* 第 3 个选项卡页面 */ lv_obj_t *tab3 = lv_tabview_add_tab(tabview, "关于"); lv_obj_t *label3 = lv_label_create(tab3); lv_label_set_text(label3, "版本 v1.0.0"); lv_obj_center(label3); }这段代码就是在屏幕顶部创建一个选项卡栏,高度 50 像素,然后添加“首页”“设置”“关于”三个页面。每个页面都是一个独立的容器对象,后续可以在里面继续添加按钮、滑块、标签、图表等控件。
模拟器里运行的效果是:点击顶部按钮,下方内容区域会切换到对应页面。
4.2 在 ESP32 真机上跑
真机部署的流程和模拟器类似,但要注意屏幕驱动初始化必须放在 LVGL 初始化之前。
以 ESP-IDF 为例,整体步骤如下:
- 配置好 ESP-IDF 环境;
- 将 LVGL 源码放进项目组件目录(components/lvgl);
- 将屏幕驱动初始化代码和 LVGL 的
lv_port_disp、lv_port_indev结合起来; - 在
app_main里按顺序:屏幕初始化 -> LVGL 初始化 -> 显示驱动注册 -> 输入驱动注册 -> 创建选项卡视图界面。
#include <stdio.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_log.h" #include "lvgl.h" #include "disp_driver.h" /* 你的屏幕驱动头文件 */ #include "lv_port_disp.h" #include "lv_port_indev.h" void lv_tabview_demo(void) { lv_obj_t *tabview = lv_tabview_create(lv_scr_act(), LV_DIR_TOP, 45); lv_obj_t *tab1 = lv_tabview_add_tab(tabview, "温湿度"); lv_obj_t *tab2 = lv_tabview_add_tab(tabview, "控制"); lv_obj_t *tab3 = lv_tabview_add_tab(tabview, "日志"); lv_obj_t *label1 = lv_label_create(tab1); lv_label_set_text(label1, "温度: 26.5 C\n湿度: 48.0 %"); lv_obj_center(label1); lv_obj_t *sw = lv_switch_create(tab2); lv_obj_center(sw); lv_obj_t *label3 = lv_label_create(tab3); lv_label_set_text(label3, "2025-01-01 10:00:00 boot ok\n"); lv_obj_center(label3); } void app_main(void) { /* 1. 屏幕硬件初始化 */ disp_driver_init(); /* 2. LVGL 初始化 */ lv_init(); /* 3. 注册显示驱动 */ lv_port_disp_init(); /* 4. 注册输入驱动(触摸或按键) */ lv_port_indev_init(); /* 5. 创建选项卡视图界面 */ lv_tabview_demo(); /* 6. LVGL 任务循环 */ while (1) { lv_timer_handler(); vTaskDelay(pdMS_TO_TICKS(5)); } }真机移植时最常见的错误就是忘记在lv_conf.h里开启LV_USE_TABVIEW 1。LVGL 有些控件默认是关闭的,如果没启用,编译时会报“函数未定义”或控件创建失败。
4.3 配置示例:lv_conf.h 关键项
#define LV_USE_TABVIEW 1 #define LV_COLOR_DEPTH 16 #define LV_MEM_SIZE (48U * 1024U) #define LV_HOR_RES_MAX 320 #define LV_VER_RES_MAX 240色深和缓冲区大小需要根据屏幕模组规格调整。16 位色在 320x240 屏幕上比较常见,48KB 的 LVGL 堆内存对选项卡视图来说属于基础设置,页面控件数量多时还需要继续加大,或者改用 PSRAM 支持的外部堆内存。
5. 选项卡视图功能测试与效果验证
5.1 基础切换测试
| 测试项 | 操作 | 预期结果 |
|---|---|---|
| 选项卡创建 | 调用lv_tabview_create | 顶部或底部出现选项卡按钮,下方出现内容容器 |
| 添加选项卡 | 调用lv_tabview_add_tab3 次 | 出现 3 个可点击的按钮 |
| 页面切换 | 点击不同选项卡 | 内容区域显示对应页面,无残留、无重叠 |
| 默认页 | 不设置当前页 | 默认显示第 1 个页面 |
验证代码:
void set_tab_test(lv_obj_t *tabview) { /* 切换到第 2 个页面,索引从 0 开始 */ lv_tabview_set_act(tabview, 1, LV_ANIM_ON); }lv_tabview_set_act的第三个参数控制切换动画,LV_ANIM_ON会有滑动过渡,LV_ANIM_OFF则直接切换。在低端 MCU 上如果觉得动画掉帧,建议关闭。
5.2 事件回调测试
选项卡视图本身会处理按钮点击事件,但如果你需要在切换页面时更新页面数据,就需要监听选项卡的切换事件。方式是在选项卡内容容器上添加事件回调。
static void tab_event_cb(lv_event_t *e) { lv_event_code_t code = lv_event_get_code(e); if (code == LV_EVENT_VALUE_CHANGED) { lv_obj_t *tabview = lv_event_get_target(e); uint16_t act_id = lv_tabview_get_tab_act(tabview); printf("[LVGL] 当前激活选项卡: %d\n", act_id); } } void tabview_with_event(lv_obj_t *parent) { lv_obj_t *tabview = lv_tabview_create(parent, LV_DIR_TOP, 45); lv_obj_add_event_cb(tabview, tab_event_cb, LV_EVENT_ALL, NULL); lv_tabview_add_tab(tabview, "A"); lv_tabview_add_tab(tabview, "B"); lv_tabview_add_tab(tabview, "C"); }在这里要注意:LV_EVENT_VALUE_CHANGED是选项卡视图切换时最常用的事件。调试时可以把当前激活的选项卡索引打出来,确认事件触发是否正常。
5.3 内容添加测试
每个新增的 tab 都是一个lv_obj_t *容器,你可以像操作普通屏幕一样操作它。
void add_widgets_to_tab(lv_obj_t *tab) { /* 添加一个滑块 */ lv_obj_t *slider = lv_slider_create(tab); lv_obj_set_pos(slider, 10, 10); /* 添加一个开关 */ lv_obj_t *sw = lv_switch_create(tab); lv_obj_set_pos(sw, 10, 50); /* 添加一个文本区 */ lv_obj_t *ta = lv_textarea_create(tab); lv_obj_set_pos(ta, 10, 90); lv_obj_set_size(ta, 200, 60); }判断成功标准:每个 widget 都出现在对应 tab 内,切换到其他 tab 时隐藏,切回来时状态保留。
5.4 中文字体显示测试
很多人的选项卡按钮需要显示中文,比如“设置”“主页”“数据”。LVGL 默认字体lv_font_montserrat_14不支持中文,需更换支持中文的字体,或者在lv_conf.h中开启并载入自定义字体。
/* 示例:使用系统已有中文字体,需在你的工程中实际引入 */ lv_obj_set_style_text_font(tabview, &my_cn_font_14, LV_STATE_DEFAULT);如果不做字体处理,中文会显示成方框或空白。这是 LVGL 中文项目最常见的问题之一。
5.5 键盘/遥控器导航测试
如果你的设备没有触摸屏,而是用红外遥控器、按键或旋钮控制,可以借助lv_group实现选项卡切换。
lv_group_t *g = lv_group_create(); lv_group_add_obj(g, tabview); lv_group_focus_obj(tabview_enable_key); /* 在 lv_port_indev 的按键回调中调用 */ void key_event_handler(uint8_t key) { if (key == KEY_PREV) { lv_group_focus_prev(g); } else if (key == KEY_NEXT) { lv_group_focus_next(g); } else if (key == KEY_ENTER) { /* 确认,触发当前控件点击事件 */ lv_event_send(lv_group_get_focused(g), LV_EVENT_CLICKED, NULL); } }lv_tabview默认支持按键导航,因为它在内部使用了一个按钮矩阵。把选项卡视图加入lv_group后,左右键就能在选项卡之间移动。
6. 选项卡视图 API 与事务管理
LVGL 本身是 C 语言库,所谓“接口”就是它提供的一组 API。本节给出项目和 UI 逻辑中比较常用的一组函数,适合在开发中直接查阅。
| API 函数 | 功能说明 |
|---|---|
lv_tabview_create(parent, pos, tab_size) | 创建选项卡视图,pos控制选项卡在顶部还是底部 |
lv_tabview_add_tab(tabview, name) | 添加一个选项卡页面,返回内容容器对象 |
lv_tabview_set_act(tabview, id, anim) | 切换到指定索引的选项卡 |
lv_tabview_get_tab_act(tabview) | 获取当前激活的选项卡索引 |
lv_tabview_get_tab_count(tabview) | 获取选项卡总数 |
lv_tabview_get_tab(tabview, id) | 获取指定索引的选项卡容器 |
6.1 批量创建页面
如果页面结构相似(比如多路传感器监控),建议用一个数组加循环来批量创建选项卡。
static const char *pages[] = {"传感器1", "传感器2", "传感器3", "传感器4"}; void create_multi_pages(lv_obj_t *parent) { lv_obj_t *tabview = lv_tabview_create(parent, LV_DIR_TOP, 40); lv_obj_t *tabs[4]; for (int i = 0; i < 4; i++) { tabs[i] = lv_tabview_add_tab(tabview, pages[i]); char buf[32]; snprintf(buf, sizeof(buf), "通道 %d\nValue: %d", i, i * 10); lv_obj_t *label = lv_label_create(tabs[i]); lv_label_set_text(label, buf); lv_obj_center(label); } }这样做的优势很明显:后期新增一个传感器页面,只需要在pages数组里加一个字符串,不需要重复写创建代码。
6.2 页面数据刷新实践
选项卡页面里的数据通常要定时刷新。建议给每个 tab 建一个独立的更新函数,避免在全局lv_timer_handler里堆大量刷新逻辑。
void update_tab1_data(lv_timer_t *timer) { /* 更新页面上的标签文本 */ lv_label_set_text_fmt(sensor_label1, "温度: %.1f C", read_temp()); }然后在初始化时创建定时器:
lv_timer_create(update_tab1_data, 1000, NULL);这种“每个页面一个定时器”的方式,在页面较少时很清爽。页面很多时,可以改为一个定时器遍历刷新。
6.3 事件冲突处理
如果某个页面里还有滑块或开关,它们也会产生LV_EVENT_VALUE_CHANGED。在事件回调中最好加一层判断,确认事件来源。
static void tab_event_cb(lv_event_t *e) { lv_event_code_t code = lv_event_get_code(e); lv_obj_t *target = lv_event_get_target(e); lv_obj_t *tabview = lv_event_get_user_data(e); if (code == LV_EVENT_VALUE_CHANGED && target == tabview) { uint16_t act = lv_tabview_get_tab_act(tabview); printf("switch to %d\n", act); } }这样可以避免页内控件的事件误触发页面切换逻辑。
7. 资源占用与性能观察
7.1 显存/内存观察方法
LVGL 不是直接操作 GPU 的框架,它的内存消耗主要分为三块:
- LVGL 内部堆内存
LV_MEM_SIZE,用于所有控件对象; - 显示缓冲区,一般是一个或两个
LV_HOR_RES_MAX * LV_VER_RES_MAX * LV_COLOR_DEPTH / 8大小的像素缓冲区; - 字体和图片资源占用的 FLASH 或 RAM,大量中文文字和图片会显著增加占用。
没有实际编译运行之前,很难给出准确的占用数字。比较稳妥的做法是:
- 第一次先跑最小选项卡示例,观察内存余量;
- 不断添加控件,观察内存变化;
- 打开 LVGL 自带的内存监控函数
lv_mem_monitor打印内存快照。
void print_mem_info(void) { lv_mem_monitor_t mon; lv_mem_monitor(&mon); printf("total: %lu, free: %lu, used: %lu, frag: %u%%\n", mon.total_size, mon.free_size, mon.used_size, mon.frag_pct); }7.2 降低内存占用的常用手段
| 手段 | 说明 |
|---|---|
| 降低色深 | 从 16 位色降到 8 位色或 1 位色,适合内容简单的 UI |
| 缩小缓冲区 | 使用单缓冲或局部刷新,牺牲一定帧率 |
| 开启 PSRAM | 把大缓冲区或字体图片放到外部 RAM |
| 减少同时显示的控件 | 选项卡页内控件尽量按需创建,或使用lv_obj_set_hidden |
| 使用图片代替大量绘制 | 复杂背景可以预渲染成图片,避免运行时绘制 |
| 关闭不需要的动画 | 切换页关闭动画,减少 CPU 负载 |
7.3 性能观察重点
- 启动耗时:从芯片上电到界面显示完成,是否在可接受范围;
- 切换延迟:点击选项卡到内容刷新的时间是否明显;
- 帧率:
lv_timer_handler执行一次的时间是否小于帧间隔; - CPU 占用:FreeRTOS 下可以通过
vTaskList或esp_get_free_heap_size辅助判断。
一般来说,选项卡比较少、每个页面内容简单时,MCU 负载很低;页面多且每个页面有大量图形控件时,需要重点观察纹理刷新是否拖动卡顿。
如果出现明显的切换卡顿,优先检查显示缓冲区是否太小、动画是否开启、是否有其他任务长期占用 CPU。
8. 常见问题与排查方法
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
编译报错:LV_USE_TABVIEW未定义 | lv_conf.h中没有启用选项卡控件 | 检查lv_conf.h | 设置#define LV_USE_TABVIEW 1 |
| 中文显示成方框 | 使用了默认英文字体 | 检查字体加载代码 | 引入支持中文的字体,并设置到控件样式 |
| 选项卡按钮不显示 | 控件被放置到超出屏幕范围或父容器尺寸异常 | 检查lv_tabview_create的父对象和尺寸 | 确保父容器尺寸合理,选项卡视图尺寸覆盖屏幕内容区 |
| 点击选项卡没有反应 | 输入驱动未注册或触摸坐标不准 | 先跑触摸测试例程 | 校准触摸,或者改用按键/编码器测试 |
| 切换页面时内容重叠 | 缓冲区或刷新区域设置不对 | 检查lv_port_disp的缓冲区配置 | 改用全屏刷新或正确绘制脏矩形 |
| 运行后重启或白屏 | 内存不足或 SPI 通信异常 | 查看串口日志、检查接线 | 缩小控件数量、加大内存、检查屏幕初始化时序 |
| API 调用段错误 | 对象句柄无效或提前释放 | 检查对象生命周期 | 在创建和销毁时统一管理句柄 |
| 页面较多时内存溢出 | 每个 tab 创建了太多控件 | 打印内存快照 | 按需创建页面,或减少每页控件数量 |
8.1 启动后屏幕一直闪烁
常见原因是显示缓冲区刷新过于频繁或刷新区域计算异常。可以先关闭动画,将刷新方式改为全屏刷新,如果不再闪烁,说明脏矩形重绘逻辑没有适配好。
8.2 无法切换到预期页面
先检查调用lv_tabview_set_act时传入的索引是否越界。索引从 0 开始,最大值为lv_tabview_get_tab_count(tabview) - 1。另外确认调用发生在 LVGL 初始化完成之后。
8.3 真机运行正常但模拟器异常
模拟器和真机的分辨率、字体路径、输入方式不同,建议优先保证模拟器上的 UI 效果,再针对真机调整尺寸和字体。
9. 最佳实践与使用建议
9.1 小参数先验证
第一次移植选项卡视图,不要一上来就设计复杂界面。先跑通 2 个 tab,内容只放一个标签。确认选项卡切换正常后,再逐步增加页面和控件。
9.2 保持一套最小可运行配置
把选项卡视图的最小可运行代码单独保存为一个工程模板,包含正确的lv_conf.h、屏幕驱动初始化和输入驱动注册。后续做新项目时直接基于这个模板改,能省很多时间。
9.3 目录管理
建议按下面的结构组织 LVGL 项目:
project/ ├── main/ │ ├── app_main.c │ ├── ui_tabview.c │ ├── ui_tabview.h │ └── lv_conf.h ├── components/ │ ├── lvgl/ │ └── display_driver/ └── build/UI 代码和业务逻辑分开,ui_tabview.c只负责界面创建和事件响应,不要在 UI 文件里直接读写传感器或网络数据,通过回调函数或接口透传。
9.4 批量任务与页面状态管理
选项卡视图页面多后,容易出现“所有页面同时刷新”的问题。建议用“当前激活页优先刷新”的策略,切到哪个页面就刷新哪个页面的数据,未激活页面降低刷新频率或完全停止。
void update_active_tab_only(lv_timer_t *timer) { uint16_t act = lv_tabview_get_tab_act(tabview_global); if (act == 0) { /* 刷新温湿度页 */ } else if (act == 1) { /* 刷新控制页 */ } else if (act == 2) { /* 刷新日志页 */ } }这样虽然代码多一点,但能明显降低 MCU 负担。
9.5 日志和错误处理
嵌入式 UI 出问题时最难排查的就是“不知道当前走到哪一步”。建议在页面切换、数据刷新、内存告警三个位置都加上调试日志。内存告警可以使用自定义回调或定期检查空闲堆内存,提前报警总比白屏好处理。
10. 总结与下一步
这个项目最值得尝试的地方在于:它把 LVGL 里最常用的多页面组织方式拆成了一个可复制、可扩展的模板。你不用在一开始就掌握 LVGL 的全部对象模型,只需要理解“选项卡视图是一个容器,每个 tab 是子容器”这个核心概念,就能把温湿度页、控制页、日志页、设置页全部组织起来。
建议先做的事:
- 在 PC 模拟器上跑通 3 个 tab 的选项卡视图;
- 把其中一个 tab 替换为真正的业务内容,比如一个开关或一个实时数据标签;
- 迁移到 ESP32 真机,重点观察内存余量和切换流畅度;
- 把常见中文显示、字体加载、事件回调问题记录到自己的笔记里。
最容易踩的坑有三个:LV_USE_TABVIEW没开启、中文字体没准备、屏幕驱动初始化顺序不对。先把这三个问题解决,选项卡视图的开发会顺利很多。
后续扩展方向:
- 使用
lv_tabview做复杂设备控制面板,每个 tab 对应一个功能模块; - 将选项卡视图与
lv_chart结合,做实时曲线监控页面; - 把页面切换状态通过 MQTT 或串口上报到上位机;
- 结合 FreeRTOS 任务,把 UI 刷新和数据采集解耦。
LVGL 的选项卡视图在嵌入式小屏设备上属于“花小钱办大事”的控件,花少量内存就能换来清晰的界面层级。建议收藏备用,实际项目里可以直接套用上面的代码框架。