news 2026/8/31 4:21:50

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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 方案选型判断:看交互,不看炫技

我在做方案选型时一般会问三个问题:

  1. 用户是在“上下滑动选一项”,还是在“拖动圆环”?
  2. 选项是否需要显示在圆周不同位置上?
  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 跑通之后检查什么

先跑单条滚动任务,验证三件事:

  1. 选中项是否随滑动变化。
  2. 滑到末尾后是否自动回到另一端。
  3. 滚动动画是否顺滑,有没有跳变或卡顿。

如果第一项就不正常,优先检查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自绘。

基本流程:

  1. 创建一块 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);
  1. 清空画布,绘制外环、内环和刻度线。
  2. 在定时器或触摸事件中更新当前角度,按角度计算每个 item 的圆心坐标。

计算 item 位置时使用标准圆坐标:

int cx = center_x + radius * lv_trigo_sin(angle); int cy = center_y - radius * lv_trigo_cos(angle);

LVGL 内置了三角计算函数,在小资源设备上比自己引入 libm 更可控。

  1. 重新绘制 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 buffer:Canvas 的 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.hLV_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 点击没反应,先看事件和触摸坐标

环形滚动界面点击无反应,常见原因不是控件逻辑,而是事件没挂上或者触摸坐标错位。

排查顺序:

  1. 确认触摸驱动是否正常,在画布上画一个跟随手指移动的点,看是否与实际触摸位置同步。
  2. 确认目标控件是否设置了lv_obj_add_flag(obj, LV_OBJ_FLAG_CLICKABLE)
  3. 确认事件回调函数是否注册成功,事件类型是否写对。
  4. 确认是不是被其他覆盖控件挡住了,检查 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 使用率、FPS
5参数配置模式枚举、可见行数、动画时长、角度范围
6控件层级是否有遮罩、事件是否被其他控件截获
7功能边界当前 LVGL 版本是否支持相应接口,是否被裁剪

这套顺序的核心逻辑是:先确认现象 -> 再确认输入 -> 再确认环境 -> 再确认参数 -> 最后才是怀疑功能边界。很多问题看起来是 LVGL 不支持,实际是输入格式或环境配置没有对齐。

最后留一个建议:如果你只是在给嵌入式屏幕加一个参数选择效果,优先用 lv_roller 的循环模式,把时间花在数据处理、按钮联动和界面整体交互上。等确认确实需要一个真正圆环旋转效果时,再考虑 lv_arc 或 Canvas 自绘,而且要提前算好内存预算和刷新代价。滚动效果本身不难,真正影响交付体验的,往往是触摸、字体、内存和版本这些外围因素。

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

STM32F407+HAL库+大彩串口屏:从CubeMX配置到驱动实现

简介:本资源是一套基于STM32F407微控制器与HAL库开发的大彩TFT彩屏串口通信驱动工程,面向嵌入式初学者及中级开发者,聚焦于ARM Cortex-M4平台下SPI接口驱动TFT屏幕的核心实践问题,适用于智能仪表、HMI人机界面等硬件交互类项目开发…

作者头像 李华
网站建设 2026/8/31 4:21:19

深度学习26转置卷积

1. 转置卷积转置卷积通俗讲解先记住一句话:普通卷积把图片变小;转置卷积(也叫反卷积)把图片变大,它不是普通卷积求逆,只是把输出尺寸还原回去。1. 普通卷积可以等价变成矩阵乘法\(YX \star W\):…

作者头像 李华
网站建设 2026/8/31 4:21:08

微信小程序蓝牙开发实战:BLE通信完整流程与踩坑指南

简介:这是一份面向微信小程序开发者的学习型蓝牙通信实战Demo,聚焦BLE设备交互核心流程,解决初学者在小程序蓝牙API调用中常见的搜索失败、连接不稳定、特征值读写异常等典型问题。资源共17个文件,包含4个JS逻辑文件(实…

作者头像 李华
网站建设 2026/8/31 4:17:58

做市场分析PPT,这三个平台我反复用了大半年

做市场分析的PPT,数据整理和逻辑梳理本身已经够耗时了,如果还要从零搭建结构、调排版配色,整个流程会变得非常低效。过去一段时间,我陆续试过不少PPT模板平台,目前固定在用的有三个,分享一点真实体验。一、…

作者头像 李华
网站建设 2026/8/31 4:17:22

pdf图纸怎么转换成cad格式?我整理了五个实用工具的完整操作记录

使用背景与需求分析 上周接到一个朋友的求助,说客户发来一套厂房平面图,全是PDF格式。他需要把里面的墙体、门窗、尺寸标注在CAD里重新整理一遍,看看能不能加个设备区。结果用CAD打开PDF一看,全都是图片,看得见摸不着…

作者头像 李华
网站建设 2026/8/31 4:14:45

三极管伏安特性曲线全攻略:看懂三大区域,设计电路不再靠猜

这段时间在折腾硬件电路,绕来绕去都绕不过一个关键器件:三极管。很多初学者会有这种感觉:看电路图时觉得三极管能放大、能开关,可真到自己搭电路,选电阻、定静态工作点、判断饱和还是放大,总是靠查手册猜数…

作者头像 李华