news 2026/2/11 11:20:28

LVGL列表与下拉菜单:实战项目应用解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LVGL列表与下拉菜单:实战项目应用解析

LVGL实战:用列表与下拉菜单打造高效嵌入式HMI

你有没有遇到过这样的场景?在一台工业控制器上,想改个通信波特率,结果要点五六次“+”按钮才能从9600跳到115200——不仅效率低,用户还容易按错。又或者,在智能家居面板里,主菜单靠一堆并列按钮堆出来,滑动卡顿、布局混乱,看起来像上世纪的界面。

这些问题的本质,是缺乏对现代GUI组件的系统性理解与工程化应用。而解决它们的关键,就藏在LVGL 的两个看似普通却极为强大的控件中:列表(List)和下拉菜单(Dropdown)

今天,我们不讲理论套话,也不罗列API文档。我们要做的,是从一个真实项目出发,手把手带你把这两个组件用活、用深、用出工程价值。


为什么选 List 和 Dropdown?

先说结论:

在资源受限的嵌入式系统中,List 是导航中枢,Dropdown 是配置核心

它们不是“能用就行”的备选项,而是构建专业级HMI的基础架构单元

举个例子。你在开发一款基于STM32H7 + 480×272 TFT屏的医疗设备操作终端。需求很明确:

  • 主界面要有功能入口(设置、校准、数据导出);
  • 进入设置后要能切换语言、选择串口参数、调节背光亮度;
  • 所有操作必须支持触摸和编码器输入;
  • 内存紧张,总可用RAM仅128KB,GUI占用不能超过30KB。

如果你还在用lv_btn手动排布按钮、自己写弹窗逻辑……那恭喜你,代码会迅速膨胀,维护成本飙升,用户体验也难以保证一致性。

但如果你熟练掌握List 和 Dropdown,你会发现:这些需求几乎已经被LVGL“预判”了。


列表(List):不只是按钮集合

很多人以为lv_list_t就是一组带图标的按钮垂直排列。这没错,但远远不够。

它真正的价值,在于“结构化导航”

想象一下Android或iOS的应用菜单——层级清晰、滚动流畅、点击反馈自然。LVGL的List就是让你在MCU上复刻这种体验。

核心机制拆解

List 并不是一个简单的容器。它的底层其实是一个智能按钮管理器 + 滚动引擎 + 焦点调度器的组合体。

当你调用:

lv_obj_t *btn = lv_list_add_btn(list, LV_SYMBOL_WIFI, "Wi-Fi Network");

LVGL 做了什么?

  1. 创建一个lv_btn
  2. 自动在按钮内部创建lv_label显示文本;
  3. 如果提供了符号(如LV_SYMBOL_WIFI),则创建lv_img插入左侧;
  4. 绑定默认点击行为(高亮 + 触发事件);
  5. 将其添加到垂直布局中,并更新滚动范围。

整个过程无需手动计算位置、对齐方式或事件绑定——这是封装带来的生产力跃迁

工程实践中的关键点
✅ 动态加载 vs 静态构造

如果列表项固定且数量少(<5项),直接初始化时全部添加即可。

但如果面对的是“历史记录”、“设备日志”这类可能上百条的内容,就必须考虑内存问题。

建议策略
- 启用懒加载:只显示可视区域附近的条目;
- 使用lv_obj_clean(list)清空后再重填;
- 或者更进一步,结合lv_fragment模块实现分页加载(需启用LV_USE_FRAGMENT)。

✅ 支持非触摸设备

很多工业设备仍使用编码器+确认键。这时候,List 必须配合焦点系统工作。

lv_group_t *g = lv_group_create(); lv_group_add_obj(g, list); // 将list加入输入组 lv_indev_set_group(knob_indev, g); // 编码器关联该组

这样用户就能通过旋转编码器上下移动焦点,按压确认进入子页面——完全模拟物理按键交互。

✅ 性能优化技巧
  • 关闭不必要的滚动条:lv_obj_set_scrollbar_mode(list, LV_SCROLLBAR_MODE_OFF)
  • 设置合适的内边距避免重绘扩散:lv_obj_set_style_pad_all(list, 8, 0);
  • 对长文本启用省略显示:lv_obj_set_style_text_overflow(label, LV_TEXT_OVERFLOW_CLIP, 0);

下拉菜单(Dropdown):小空间里的大智慧

如果说 List 解决的是“怎么组织功能”,那么 Dropdown 解决的就是“如何高效选择参数”。

它的本质,是一个“按需展开的选择器”

初始状态它只占一行高度,点击后才动态生成浮层列出所有选项。这种设计在仪表盘、参数配置页中极具优势。

内部是怎么运作的?

Dropdown 表面看是个输入框,实际上包含两部分:

  1. 主控件:显示当前值的文本区域;
  2. 弹出层(Popup):点击时创建的临时浮窗,本质是一个精简版列表。

所有选项以\n分隔字符串形式存储在内部缓冲区。例如:

"中文\nEnglish\n日本語"

当用户点击时,LVGL 会动态创建一个居中浮层,每一行作为一个可点击项。选择后自动关闭并更新主文本。

为什么比手动画弹窗更好?
手动实现LVGL Dropdown
需管理窗口生命周期自动创建/销毁
需处理遮挡与层级内建z-index机制
易出现内存泄漏固定缓冲区大小可控
交互不一致统一动画与焦点行为

更重要的是:它原生支持键盘导航、索引访问、动态更新

实战代码详解
// 创建下拉菜单 lv_obj_t *dd = lv_dropdown_create(lv_scr_act()); lv_obj_set_width(dd, 120); lv_obj_align(dd, LV_ALIGN_TOP_RIGHT, -10, 10); // 设置选项 lv_dropdown_set_options(dd, "1s\n5s\n10s\n30s"); // 默认选中第3项(10秒) lv_dropdown_set_selected(dd, 2); // 添加事件监听 lv_obj_add_event_cb(dd, on_interval_changed, LV_EVENT_VALUE_CHANGED, NULL);

回调函数中可以这样读取:

void on_interval_changed(lv_event_t *e) { lv_obj_t *dd = lv_event_get_target(e); uint8_t idx = lv_dropdown_get_selected(dd); uint32_t interval_ms; switch(idx) { case 0: interval_ms = 1000; break; case 1: interval_ms = 5000; break; case 2: interval_ms = 10000; break; case 3: interval_ms = 30000; break; } // 更新定时器或其他模块 update_sampling_interval(interval_ms); }
高阶技巧
🔍 启用首字母搜索

对于长列表(如国家选择),开启动态标志后,用户输入“A”即可快速定位到“America”:

lv_dropdown_add_flag(dd, LV_DROPDOWN_FLAG_DYNAMIC);

注意:此功能依赖输入设备支持字符输入(如 keypad),并非所有硬件都适用。

🌍 多语言适配方案

不要硬编码字符串!推荐结合lv_i18n模块:

extern const char *lang_options_en[]; extern const char *lang_options_zh[]; void update_language_dd(lv_obj_t *dd, int lang_id) { const char **opts = (lang_id == LANG_ZH) ? lang_options_zh : lang_options_en; char buf[256] = {0}; for(int i = 0; opts[i]; i++) { strcat(buf, opts[i]); if(opts[i+1]) strcat(buf, "\n"); } lv_dropdown_set_options(dd, buf); }

这样切换语言时只需重新设置选项即可。

⚠️ 内存安全提醒
  • lv_dropdown_set_options()会复制字符串到内部缓冲区;
  • 缓冲区大小由LV_CFG_DROPDOWN_DEF_STR_MAX_LEN控制(默认256字节);
  • 超长会被截断,务必预估最大长度;
  • 若频繁更新选项,建议复用同一字符串缓冲区,避免碎片。

真实项目中的整合应用

让我们回到开头提到的医疗设备案例,看看如何将两者协同使用。

架构设计思路

主屏幕 ├── [List] 功能导航 │ ├── 设置 → 跳转设置页 │ ├── 校准 → 触发校准流程 │ └── 导出 → 弹出文件保存对话框 │ └── 状态栏 └── [Dropdown] 实时模式选择(待机 / 监测 / 报警)

页面跳转怎么做才规范?

别再用全局变量满天飞了。推荐使用轻量级页面栈:

#define MAX_PAGES 4 static lv_obj_t *page_stack[MAX_PAGES]; static uint8_t sp = 0; void push_screen(lv_obj_t *new_page) { if(sp > 0) lv_obj_add_flag(page_stack[sp-1], LV_OBJ_FLAG_HIDDEN); if(sp < MAX_PAGES) page_stack[sp++] = new_page; lv_obj_clear_flag(new_page, LV_OBJ_FLAG_HIDDEN); lv_scr_load(new_page); } void pop_screen(void) { if(sp <= 1) return; lv_obj_del(page_stack[--sp]); // 自动触发析构 lv_scr_load(page_stack[sp-1]); }

然后在 List 的事件回调中调用:

void on_settings_click(lv_event_t *e) { lv_obj_t *settings_page = create_settings_page(); // 工厂函数 push_screen(settings_page); }

是不是有点像移动端的 Activity 栈?这就是工程化思维的价值


常见坑点与调试秘籍

❌ 问题1:点击无反应?

检查是否注册了正确的事件类型:

// 错误 ❌ lv_obj_add_event_cb(obj, cb, LV_EVENT_PRESSED, NULL); // 正确 ✅ lv_obj_add_event_cb(obj, cb, LV_EVENT_CLICKED, NULL);

PRESSED是按下瞬间,CLICKED是完整点击(按下+释放)。大多数情况下应使用后者。

❌ 问题2:下拉菜单弹窗跑偏甚至黑屏?

原因通常是显存不足导致渲染失败

解决方案:

  • 减少LV_VER_RES或启用双缓冲降为单缓冲;
  • 限制 dropdown 最大高度:lv_dropdown_set_max_height(dd, 120);
  • 检查disp_drv.flush_cb是否正确实现,特别是DMA传输完成中断。

❌ 问题3:内存占用越来越高?

排查是否重复创建对象未删除。典型错误模式:

// 每次刷新都新建dropdown —— 内存泄漏! while(1) { lv_obj_t *dd = lv_dropdown_create(parent); // ... }

正确做法:创建一次,后续仅更新内容。


设计哲学:让UI服务于功能

最后分享几点来自一线项目的思考:

场景推荐方案
功能少于5项,常驻操作使用 List 直接展示
参数选择,选项固定Dropdown
数值调节,连续变化使用 Slider + Label 反馈
高频切换状态用 Toggle Button 或下拉结合图标

记住一句话:

最好的UI,是让用户感觉不到UI的存在

List 和 Dropdown 的意义,不仅是“画出来”,更是帮助开发者建立起结构化的交互模型——让复杂系统变得可预测、易操作。


写在最后

LVGL 不只是一个图形库,它是嵌入式世界里的“前端框架”。而 List 和 Dropdown,则是其中最常用也最容易被低估的两个组件。

当你下次接到一个HMI任务时,不妨先问自己几个问题:

  • 我的功能要不要分层?→ 用 List 构建导航。
  • 用户要不要做选择?→ 用 Dropdown 提供明确选项。
  • 设备有没有键盘?→ 加入 group 支持焦点导航。
  • 内存够不够?→ 控制字符串长度,避免频繁重建。

把这些细节揉进日常开发习惯里,你的嵌入式界面就会从“能用”走向“好用”,最终迈向“专业”。

如果你正在做类似项目,欢迎留言交流具体场景。也可以分享你在使用 List 和 Dropdown 时踩过的坑,我们一起避坑前行。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/6 2:20:08

VIA键盘配置工具:三步打造专属机械键盘的终极指南

VIA键盘配置工具&#xff1a;三步打造专属机械键盘的终极指南 【免费下载链接】app 项目地址: https://gitcode.com/gh_mirrors/app8/app 还在为机械键盘的复杂配置而烦恼吗&#xff1f;VIA键盘配置工具就是你的完美解决方案&#xff01;这款完全免费的开源Web应用让任…

作者头像 李华
网站建设 2026/2/8 5:20:00

如何快速实现Markdown到Notion的无缝转换:终极完整指南

如何快速实现Markdown到Notion的无缝转换&#xff1a;终极完整指南 【免费下载链接】md2notion 项目地址: https://gitcode.com/gh_mirrors/md/md2notion 想要将Markdown笔记完美迁移到Notion却找不到合适工具&#xff1f;md2notion正是你需要的终极解决方案。这个强大…

作者头像 李华
网站建设 2026/2/8 5:55:33

玄铁E906 RISC-V处理器:开启嵌入式AIoT开发新篇章

玄铁E906 RISC-V处理器&#xff1a;开启嵌入式AIoT开发新篇章 【免费下载链接】opene906 OpenXuantie - OpenE906 Core 项目地址: https://gitcode.com/gh_mirrors/ope/opene906 在嵌入式系统开发领域&#xff0c;RISC-V架构正以前所未有的速度改变着技术格局。作为平头…

作者头像 李华
网站建设 2026/2/4 10:58:58

卡卡字幕助手:AI智能字幕的终极操作手册

卡卡字幕助手&#xff1a;AI智能字幕的终极操作手册 【免费下载链接】VideoCaptioner &#x1f3ac; 卡卡字幕助手 | VideoCaptioner - 基于 LLM 的智能字幕助手&#xff0c;无需GPU一键高质量字幕视频合成&#xff01;视频字幕生成、断句、校正、字幕翻译全流程。让字幕制作简…

作者头像 李华
网站建设 2026/2/5 9:23:20

抖音视频无水印下载技术全解析

抖音视频无水印下载技术全解析 【免费下载链接】TikTokDownload 抖音去水印批量下载用户主页作品、喜欢、收藏、图文、音频 项目地址: https://gitcode.com/gh_mirrors/ti/TikTokDownload 在数字内容创作日益普及的今天&#xff0c;获取高质量的原始视频素材已成为众多创…

作者头像 李华