FEATURED · 精选文章

LVGL环形无限循环滚动:lv_roller与lv_arc方案选型与实现

发布时间 / 2026/8/31 4:22:01
来源 / 创域科博编辑部
栏目 / 资讯中心
LVGL环形无限循环滚动:lv_roller与lv_arc方案选型与实现 LVGL 里做“环形无限循环滚动”是个常见需求但这需求其实分两种一种是要一个可以循环滚动的滚轮选择器另一种是要一个真正围绕圆环旋转的滚动菜单或指示环。很多嵌入式开发刚开始容易混在一起结果要么选中了错误控件要么花大量时间自绘最后发现官方控件已经支持。这篇主要讲清楚 lv_roller 的循环机制、lv_arc 的环形动画思路、Canvas 自绘扩展方式以及低资源设备上的内存优化和排错顺序。适合正在用 STM32、ESP32 做仪表盘、菜单屏、参数选择界面的开发者。先放结论如果只是“上下滚、滚到底再从头开始”的无限循环选择直接使用 lv_roller 标准控件默认或开启循环模式就能实现不需要自己写列表滚动如果要做“每个选项分布在圆环上跟随旋转”的环形滚动效果才需要考虑 lv_arc 配合三角函数布局或者用 lv_canvas 自绘。下面按实际的开发顺序拆开讲。1. 先搞清楚“环形无限循环”到底要哪种效果1.1 最常见需求滚轮式选择器用 lv_roller 就够了很多产品里的“环形无限循环滚动”其实是滚轮效果。比如一个温度设置界面温度范围是 0 到 100用户往上滑到 100 后继续滑希望回到 0往下滑到 0 后再继续滑希望跳到 100。这种交互就是无限循环滚轮。LVGL 里对应控件是lv_roller它本身是一个垂直滚轮列表所有选项从上到下排列用户通过滑动切换选中项。它和普通lv_list的区别在于roller 只会高亮显示中间一行的选中项并且支持循环模式。在 LVGL 8 中通过设置选项时传入LV_ROLLER_MODE_INFINITE滚动到末尾后会从另一端继续。LVGL 9 的枚举名称有所调整但只要看版本头文件里的模式定义就能找到。很多新手会去用lv_list自己实现滚动循环不是不行但代价很高。lv_list本质是滚动容器要自己处理边界判断、滚动复位、选中状态、动画衔接代码量不小。标准控件能覆盖的场景优先用标准控件这是嵌入式 UI 开发里应该坚持的第一原则。1.2 想要指针旋转或圆环扫描就得换 lv_arc 或自绘如果需求不是“滚轮选择”而是选项围绕圆环排布随选择旋转那lv_roller就不够了。典型场景有两种一个仪表盘上多个档位分布在圆周上指针旋转指向当前档位。一个环形菜单当前项位于正上方或正右方滑动圆环时整个圆环旋转。这类效果需要的是角度和圆周坐标而不是直线排列。LVGL 的lv_arc控件可以绘制圆弧、设置角度范围、响应触摸适合做“旋转指示”和“环形滑动”。但lv_arc本身不是列表不会自动布局 item所以如果你需要多个文本或图标分布在圆周上还得自己按角度计算位置或者用 Canvas 把整个环形界面画出来。1.3 方案选型判断看交互不看炫技我在做方案选型时一般会问三个问题用户是在“上下滑动选一项”还是在“拖动圆环”选项是否需要显示在圆周不同位置上是否要求滑动过程有明确的旋转视觉反馈如果用户操作是上下滑动且选项以文本列表呈现选 lv_roller。 如果用户操作是拖圆环或指针旋转选项不需要同步显示在圆周上选 lv_arc。 如果用户操作是拖圆环且每个选项都要出现在圆周对应位置优先考虑 lv_canvas 自绘或者手动布局一组 label 并做旋转动画。这个顺序能避免一开始就走进过度设计。多数产品的“无限循环滚动”只是第一个场景用 roller 就能正常交付。2. 环境准备先从模拟器跑通再进嵌入式2.1 PC 模拟器是最快的验证路径环形滚动效果涉及动画、触摸事件、循环边界直接烧到开发板上调试会很慢。最稳妥的方式是先在 PC 模拟器里把逻辑跑通确认交互和视觉符合预期再移植到板子上。LVGL 的 PC 模拟器常见方案有官方维护的 PC 模拟器工程支持 SDL、SDL2 驱动可以直接在 Windows、Linux、macOS 上编译。VSCode 配合 CMake 插件下载 LVGL 源码和模拟器工程后用 CMake 构建。部分用户也会用 CodeBlocks 或 Qt 环境但配置成本稍高。我在 VSCode 下一般是这样搭的# 假设已经准备 LVGL 源码和模拟器工程 cd lv_port_pc_vscode code .lv_conf.h里的关键配置先确认#define LV_COLOR_DEPTH 16 #define LV_USE_ROLLER 1 #define LV_USE_ARC 1 #define LV_USE_CANVAS 1 #define LV_MEM_CUSTOM 0如果LV_USE_ROLLER是 0控件就不会编译进去后面调用 API 会直接报未定义。这个是最常见的第一步坑先检查这个宏再去查代码逻辑。2.2 STM32/ESP32 移植时的差异点从模拟器切到开发板主要差异不是 API而是资源。STM32F103 这类低端 MCU主频低、RAM 小动画帧率上不去就先跑静态循环切换不跑平滑滚动。ESP32 有较多 RAM但仍要控制控件数量和 Canvas buffer 大小。如果使用 LVGL 9 搭配新芯片比如 ESP32-P4要注意 LVGL 版本和芯片驱动是否有已知兼容问题。特别是显示接口和旋转方向的配置很多卡死问题不是 LVGL 逻辑错误而是驱动层没有适配好。移植完成后不要马上跑新功能。先跑一遍 LVGL 自带的 demo 或者一个最小界面确认屏幕能正常刷新、触摸能正常上报。触摸不正常的场景下环形滚动效果即使逻辑正确用户体验也是错乱的。2.3 最小 Demo一个能循环滚动的滚轮下面是一个基于 LVGL 8 接口的滚动选择器示例。代码不复杂主要看几个关键 API 的配合。static void roller_event_cb(lv_event_t *e) { lv_obj_t *roller lv_event_get_target(e); char buf[16]; lv_roller_get_selected_str(roller, buf, sizeof(buf)); lv_label_set_text(result_label, buf); } void create_loop_roller(void) { lv_obj_t *roller lv_roller_create(lv_scr_act()); lv_roller_set_options(roller, Item 0\nItem 1\nItem 2\nItem 3\nItem 4, LV_ROLLER_MODE_INFINITE); lv_roller_set_visible_row_count(roller, 3); lv_obj_center(roller); lv_obj_add_event_cb(roller, roller_event_cb, LV_EVENT_VALUE_CHANGED, NULL); }这段示例里最重要的一行是选项字符串里的\n。LVGL 的 roller 把换行符作为选项分隔符每个\n代表一个选项。如果选项数量特别多可以用代码动态拼接字符串但注意字符串缓冲区要够大不能越界。如果你用的是 LVGL 9模式的枚举名可能在头文件里叫法不同。不要凭记忆写直接打开lv_roller.h看当前版本的 mode 定义。版本之间 API 变化在 LVGL 里很常见这不算问题只是落地时要先确认依赖版本。2.4 跑通之后检查什么先跑单条滚动任务验证三件事选中项是否随滑动变化。滑到末尾后是否自动回到另一端。滚动动画是否顺滑有没有跳变或卡顿。如果第一项就不正常优先检查LV_EVENT_VALUE_CHANGED事件是否注册成功。 如果第二项不正常检查模式枚举是否设置成循环模式。 如果第三项不正常先降低可见行数再检查刷新频率和屏幕驱动。我一般不会一上来就跑大量 item 或者高帧率动画。先把最小样例跑稳再逐步增加复杂度。3. lv_roller 的循环机制与核心参数3.1 循环模式是怎么“假装无限”的lv_roller 的循环模式并不是真正建立一个无限长的列表而是在滚动到末尾时重新映射选项顺序。滚到最后一项后再从第一项开始视觉上看起来是“绕了一圈”。这种设计非常省内存因为它不需要复制数据只需要调整选项索引。实现方只需要在逻辑上对索引做取模操作。虽然代码里是 LVGL 内部处理但理解这一点有助于排查问题如果循环切换时出现跳变多半是所选模式和索引映射不一致。3.2 可见行数、行高和动画时长怎么配合lv_roller_set_visible_row_count设置一次可见的选项行数这会直接影响控件高度。可见行数建议是奇数比如 3 或 5这样视觉上有“当前选中项在中间”的效果。行高由选项文本的字体决定。字体越大每行越高控件整体也越高。如果屏幕尺寸有限可以通过修改样式里的text_font来调整。动画时长由 LVGL 的动画参数控制。如果觉得滚动太过生硬可以设置一个适中的动画时间如果觉得卡顿就把动画时间缩短或关闭动画。这几个参数配合起来的判断标准很简单单次滚动后选中项快速稳定在中间位置没有来回晃动。3.3 怎么在切换时拿到选中项通过事件回调获取选中项的文本或索引。上面的示例中已经演示了获取文本的方法。获取索引则更简单int selected lv_roller_get_selected(roller);这种方式适合在代码里根据选中值去切换屏幕、修改其他控件内容或保存参数。注意lv_roller_get_selected返回的是当前选项的索引在循环模式下可能会有索引偏移实际使用时建议以lv_roller_get_selected_str获取到的文本为准或者统一用索引但提前做映射。3.4 数据动态更新时要注意什么如果选项需要动态变化比如根据当前语言切换显示内容注意以下三个坑选项字符串更新后原来选中的索引可能还指向旧数据需要手动重置选中项。如果选项数量变化很大先调用lv_roller_set_options重新设置再调用lv_roller_set_selected设置默认选中位置。如果是在多语言环境建议先用一个语言编码映射到选项文本再设置到 roller 上不要直接在每个界面里硬编码字符串。国际化场景下很多问题不是 roller 本身的问题而是文本长度和字体不全。中文字体比西文字体大如果字库没有包含需要的字符界面上就会出现空白或乱码。所以涉及中文显示时要提前确认字体文件是否覆盖目标字符范围。4. 想要“真环形”滚动lv_arc 旋转方案和 Canvas 自绘4.1 lv_arc 做环形选择器的基本思路lv_arc 本身就是圆弧控件支持设置背景弧、前景弧、角度范围也支持触摸旋转。如果需求只是“一个可拖动的环形指示器”完全没有必要自绘。基本用法lv_obj_t *arc lv_arc_create(lv_scr_act()); lv_obj_set_size(arc, 200, 200); lv_arc_set_rotation(arc, 270); lv_arc_set_bg_angles(arc, 0, 360); lv_arc_set_value(arc, 0); lv_arc_set_range(arc, 0, 100); lv_obj_center(arc);lv_arc_set_rotation用来调整起始角度通常设置为 270 度这样 0 值从正上方开始。lv_arc_set_range设置值范围。当用户拖动圆环时LV_EVENT_VALUE_CHANGED事件会触发读取当前值后再映射到对应选项。这个方案适合做“圆形滑条”或“档位旋转选择器”。比如一个音量环0 到 100 分布在 360 度上拖动旋钮可以调整音量。这种情况下选项不需要环绕排布只需要根据角度映射数值。4.2 用 Canvas 自绘环形轨道和滚动块如果选项必须环绕显示比如 5 个图标分布在圆环上当前项高亮那 lv_arc 就不够用了。这时更灵活的方式是lv_canvas自绘。基本流程创建一块 Canvas buffer。#define CANVAS_W 200 #define CANVAS_H 200 static lv_color_t cbuf[LV_CANVAS_BUF_SIZE_TRUE_COLOR(CANVAS_W, CANVAS_H)]; lv_obj_t *canvas lv_canvas_create(lv_scr_act()); lv_canvas_set_buffer(canvas, cbuf, CANVAS_W, CANVAS_H, LV_IMG_CF_TRUE_COLOR);清空画布绘制外环、内环和刻度线。在定时器或触摸事件中更新当前角度按角度计算每个 item 的圆心坐标。计算 item 位置时使用标准圆坐标int cx center_x radius * lv_trigo_sin(angle); int cy center_y - radius * lv_trigo_cos(angle);LVGL 内置了三角计算函数在小资源设备上比自己引入 libm 更可控。重新绘制 Canvas把当前角度的 active item 高亮其他 item 置灰。这种方案的优点是视觉自由度最高缺点是代码量和调试成本也最高。每次滑动都要重新绘制如果 buffer 太大或刷新频率太高性能会明显下降。4.3 自动循环滚动定时器驱动与动画曲线除了用户拖动有时还需要自动循环滚动。比如屏保模式、演示模式或者参数轮巡显示。这时可以创建一个 LVGL 定时器lv_timer_t *timer lv_timer_create(auto_step_cb, 1000, NULL); static void auto_step_cb(lv_timer_t *timer) { int next (current_index 1) % item_count; set_active_index(next); }自动循环的关键是取模运算。当索引等于总数量时回到 0这就是无限循环的核心逻辑。如果希望滚动更平滑可以配合动画库设置一个从当前角度到目标角度的旋转动画。但注意不要让手动操作和自动循环同时抢同一个控件否则会出现“按下没反应”或“动画不停抖动”的问题。我一般会在用户触摸时先删除自动循环定时器等触摸结束后再恢复。4.4 两套方案的取舍直接做一个对比表格。维度lv_arc 方案Canvas 自绘方案视觉自由度中等适合圆环和角度指示高可以画任意图形选项环绕显示需要额外布局不方便可以直接按角度绘制代码量较少较多性能开销较低较高受 buffer 影响大适用场景圆形滑条、档位指示环形菜单、复杂仪表盘如果你的屏幕是圆形屏Canvas 自绘方案能充分利用像素空间但必须做好裁剪和局部刷新。方形屏幕上如果只是做一个环形选择器lv_arc 就够不要为了炫而自绘。5. 低配开发和内存受限时的优化空间5.1 控件的数量、字体和动画都是内存大头在 STM32 或资源紧张的 ESP32 上LVGL 的内存优化不是最后才做而要在设计界面时就开始控制。以下几个方面最容易吃内存字体一个完整中文字体可能占用几百 KB 到几 MB。环形滚轮如果有多语言需求优先使用字体子集只包含用到的文字。控件数量每个控件都有对象结构体虽然 LVGL 做了优化但大量控件的内存累积仍然可观。Canvas bufferCanvas 的 color buffer 直接占用 RAM。计算公式大致是 宽度 × 高度 × 每像素字节数在 16 位色深下200×200 的 Canvas 就接近 80 KB。这是一个非常大的开销。动画对象每个动画都有回调和控制块同时运行的动画越多CPU 和内存压力越大。5.2 样式复用、局部样式和缓冲区的合理分配LVGL 里给多个控件设置相同样式时尽量复用同一个lv_style_t不要每个控件都 init 一套样式。环形滚轮里的多个 item 如果只有颜色不同可以共用一个基础样式再通过局部样式覆盖差异。对于 Canvas 自绘如果 Canvas 尺寸不需要 200×200就缩小到实际绘制区域。我见过一种做法是把缓冲设成 240×240但实际内容只占中间一个圆环周围全是浪费。正确做法是只申请圆环外接正方形区域或者拆成多个小 Canvas 分区域刷新。lv_conf.h里LV_MEM_SIZE的大小要根据实际项目调整。默认值可能偏大或偏小调试时可以直接看lv_mem_monitor的输出了解当前内存池使用情况。lv_mem_monitor_t mon; lv_mem_monitor(mon); LV_LOG_USER(used: %d free: %d frag: %d%%, mon.used_size, mon.free_size, mon.frag_pct);如果空闲内存长期低于总内存的 20%就要开始裁剪界面或优化纹理。5.3 怎么判断优化是否到位优化不是“感觉快了”而是有可观察指标启动后内存使用是否稳定不随时间持续上涨。滚动和动画的帧率是否达到预期。LVGL 自带LV_USE_PERF_MONITOR性能监控宏打开后可以在屏幕上看到 FPS 和 CPU 使用率。长时间运行后是否出现内存碎片或句柄泄漏。环形滚动如果不停创建和销毁动画长期运行可能产生碎片。在调整优化前先记录一组基线数据再改一项配置看数据变化。不要一次性改多个参数否则性能变好或变差都很难定位是哪个因素产生的影响。6. 常见问题排查链路与避坑清单6.1 点击没反应先看事件和触摸坐标环形滚动界面点击无反应常见原因不是控件逻辑而是事件没挂上或者触摸坐标错位。排查顺序确认触摸驱动是否正常在画布上画一个跟随手指移动的点看是否与实际触摸位置同步。确认目标控件是否设置了lv_obj_add_flag(obj, LV_OBJ_FLAG_CLICKABLE)。确认事件回调函数是否注册成功事件类型是否写对。确认是不是被其他覆盖控件挡住了检查 z-order。很多“按下不变化”的问题其实和滚动逻辑无关。我见过有用户在屏上叠了一个全屏透明容器事件全被容器吃掉了控件根本收不到触摸。6.2 滚动卡顿先看动画数和对齐逻辑卡顿先看两个方向动画太多还是重绘太多。如果每个 item 都在滚动的过程中执行缩放、平移、阴影效果那 CPU 和 GPU 压力会很大。低端设备上尽量只用位移和颜色变化不要用阴影和实时模糊。如果使用 Canvas 自绘每次滑动都全量重绘整个缓冲帧率自然会掉。优化思路是缩小绘制区域或者把 Canvas 拆成静态背景和动态前景两层静态部分只在初始化时画一次动态部分只画移动中的那一小块。6.3 循环切到边缘就跳变先看取模和对齐从最后一项切到第一项时出现跳变常见原因是取模没有正确作用到视觉偏移上。如果使用 lv_roller跳变问题通常来自模式设置错误确认使用的是循环模式而不是普通模式。 如果使用自绘方案跳变通常来自角度更新逻辑。比如当前角度到目标角度时应该选择同方向的最小路径不能直接让角度从 350 度跳到 10 度那样会产生很大的旋转动画。正确的做法是计算两个角度之间的目标差值然后按方向叠加 360 度取模。int diff (target - current 360) % 360; if (diff 180) diff - 360; // 选择短路径6.4 排查顺序表与实践建议下面是一份我实际调试环形滚动效果时常用的排查顺序。适用面比较宽建议直接保存每次遇到问题按顺序过一遍。步骤检查内容判断方式1现象定位是点击无效、卡顿、跳变还是没有动画2输入格式选项字符串分隔符、文本缓冲区大小、数据编码3环境情况lv_conf.h 控件宏、LVGL 版本、编译选项4资源占用内存剩余、CPU 使用率、FPS5参数配置模式枚举、可见行数、动画时长、角度范围6控件层级是否有遮罩、事件是否被其他控件截获7功能边界当前 LVGL 版本是否支持相应接口是否被裁剪这套顺序的核心逻辑是先确认现象 - 再确认输入 - 再确认环境 - 再确认参数 - 最后才是怀疑功能边界。很多问题看起来是 LVGL 不支持实际是输入格式或环境配置没有对齐。最后留一个建议如果你只是在给嵌入式屏幕加一个参数选择效果优先用 lv_roller 的循环模式把时间花在数据处理、按钮联动和界面整体交互上。等确认确实需要一个真正圆环旋转效果时再考虑 lv_arc 或 Canvas 自绘而且要提前算好内存预算和刷新代价。滚动效果本身不难真正影响交付体验的往往是触摸、字体、内存和版本这些外围因素。
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻