LVGL实现环形无限循环滚动:三种高效方案
2026/8/31 14:56:45 网站建设 项目流程

很多嵌入式开发者在做 GUI 时都会遇到这样一个需求:界面上有一串列表,或者一组页面卡片,需要让用户左右滑动,而且滑到末尾之后不要停住,直接无缝回到开头继续滑,形成一种“转圈”的效果。这个需求在手机 App 里很常见,比如 iOS 的滚轮选择器、首页 Banner 无限轮播、选项卡横向滑动列表。

在 LVGL 项目里,很多人第一次接到这个需求时,第一反应是去写复杂的滚动状态机,或者往容器里不断追加数据,甚至想去修改 LVGL 的事件分发逻辑。其实这个需求并不需要从零造轮子。我的判断是先放在前面:

  • 如果你的目标是文本选择器,比如设置项里的滚动选值,官方lv_roller控件在LV_ROLLER_MODE_INFINITE模式下本身就是环形无限循环的,你只需要做配置,不需要自己写轮播逻辑。
  • 如果你的目标是 Banner、卡片或自定义控件横向无缝轮播,更推荐scroll_snap + 数据副本的思路,用最少的代码实现“到末尾后无感跳转回开头”。
  • 如果你的目标是不需要人为操作、全自动循环播放的展示页,可以再加一个lv_anim定时驱动,让滚动更顺滑。

这篇文章会把三条路径全部拆开,从概念、代码到验证方式都讲清楚,同时把最常见的几个坑列出来,包括滚动边界、事件不触发、动画跳变、版本差异等。读完你应该能直接在自己的 LVGL 工程里实现环形无限循环滚动效果。

1. 这篇文章真正要解决的问题

先明确一下“环形无限循环滚动”到底是什么意思。

普通的滚动是线性行为:内容有多少,滚动范围就有多大,滚到末尾就停止,再继续滑动会产生回弹或者边界提示。环形无限循环滚动指的是:内容在逻辑上形成一个环,用户滑到末尾时,自动无缝回到开头,中间不能出现内容空白、不能出现回跳动画、不能让用户感知到“已经到底了”。

这个需求通常出现在以下几个场景:

  1. 滚轮式选择器:类似闹钟设置里选择小时、分钟,列表上下循环滚动。
  2. 首页 Banner 轮播:多张图片横向滑动,滑完最后一张直接回到第一张。
  3. 仪表菜单页:横向多个功能卡片,用户左右滑动切换,首尾相接。
  4. 数据列表循环展示:比如当前温度、电压等状态信息循环显示。

很多开发者在这个需求上容易犯两个错误。

第一个错误是试图在 LVGL 里直接用一个超长容器,把所有数据重复很多份,比如把 3 个页面复制 100 次,让用户永远滑不到底。这种方案能实现“看起来无限”,但内存开销大,数据同步麻烦,而且滚动位置一旦异常复位就会露馅。

第二个错误是去改 LVGL 底层滚动逻辑,比如监听滚动事件后强制设置偏移量,结果没有关闭动画,或者没有处理回弹标志位,导致页面跳变严重,体验很差。

正确的思路应该是:优先使用 LVGL 官方已经提供的循环特性,比如lv_roller;如果官方控件不适合自定义 UI,再用“逻辑环形化”的方式,在滚动事件里做位置补偿。

2. 核心概念:LVGL 滚动机制与“环形无限循环”的本质

在动手写代码之前,理解 LVGL 的滚动模型比背 API 更重要。LVGL 里几乎所有对象都可以设置为可滚动对象,比如lv_objlv_listlv_roller。滚动行为依赖几个核心概念。

2.1 可滚动对象

任何对象只要内容超出自身尺寸,并且没有关闭滚动标志位,就会成为可滚动对象。在 LVGL 中,滚动方向通过LV_OBJ_FLAG_SCROLLABLE标志控制,可以使用lv_obj_add_flag/lv_obj_clear_flag来调整。例如:

lv_obj_t * cont = lv_obj_create(lv_scr_act()); lv_obj_set_size(cont, 200, 120); /* 横向滚动 */ lv_obj_set_scroll_dir(cont, LV_DIR_HOR);

这里LV_DIR_HOR表示只允许横向滚动。如果是纵向选择器,则使用LV_DIR_VER

2.2 scroll snap

scroll snap是 LVGL 中一个非常实用的功能:当用户滚动结束并且手指释放时,对象会自动吸附到指定的对齐位置。这个机制天然适合做页面轮播和卡片切换。

使用方式分两步:

  • 在容器对象上设置整体 snap 策略,例如LV_SCROLL_SNAP_CENTER表示居中吸附。
  • 在每个子对象上设置该子对象自己的 snap 对齐方式,例如LV_SCROLL_SNAP_CENTER
lv_obj_set_scroll_snap_x(cont, LV_SCROLL_SNAP_CENTER);
lv_obj_set_scroll_snap_align(child, LV_SCROLL_SNAP_CENTER);

只有同时设置了容器策略和子对象对齐方式,snap 才会生效。

2.3 scroll callback

LVGL 提供了LV_EVENT_SCROLL事件,可以在滚动过程中不断触发回调。这个事件是实现“到末尾后跳回开头”的关键。环形无限循环滚动本质上就是:在滚动回调里判断当前位置,如果已经越过了逻辑边界,就立刻把滚动偏移量替换到环的另一端,并且关闭动画。

2.4 环形缓冲区的思想

“环形无限循环滚动”这个名字很容易让人联想到嵌入式开发里的环形缓冲区。环形缓冲区之所以能做到“无限写”,是因为它通过取模运算把线性空间虚拟成了一个环。LVGL 滚动也可以借鉴这个思想:内容本身是有限的,但滚动位置是取模的。

也就是说,我们要做的不是增加无限多的内容,而是让“偏移量”周期性地映射回有效区间。这也是后面方案二的核心原理。

2.5 lv_roller 的循环模式

lv_roller是 LVGL 官方自带的滚轮控件,默认支持普通模式。在普通模式下,列表滚到底后也会停止。但是当设置为LV_ROLLER_MODE_INFINITE时,它就会自动启用环形循环模式,用户上下滑动时列表首尾相接,不会出现边界。

这是 LVGL 官方封装的“环形无限循环滚动”能力,所以如果你的场景是文本选择器,直接用它是最省事的。

3. 方案选型:三种实现路径对比

在实现之前,先做一个方案选型,方便你根据自己项目的 UI 结构选择。

方案适合场景代码量灵活性风险点
方案一:lv_roller 循环模式文本选择器、滚动选值极少低,只能基于 roller 样式自定义样式受限于控件结构
方案二:scroll_snap + 数据副本Banner、卡片、自定义页面轮播中等高,子控件完全自定义需要处理副本与真实数据的对应关系
方案三:lv_anim 自动驱动全自动轮播、大屏展示中等高,可以配合前两种需要管理定时器和动画生命周期

如果你的 UI 主要是几个矩形色块、图片、卡片组合成的页面,强烈建议使用方案二。如果你只是要做一个类似时钟设置里选数字的选择器,方案一最稳。方案三通常不单独使用,而是作为补充机制,叠加在方案二上。

4. 环境准备与基础配置

本文代码基于 LVGL 8.3 编写,但大多数 API 在 LVGL 9.x 也能使用。如果你使用的是 LVGL 9.x,注意以下几点:

  • lv_roller_createlv_roller_set_options仍然存在。
  • 事件处理中lv_event_get_target的用法不变。
  • 部分对象样式接口从lv_style_set_*调整为lv_obj_set_style_*,但本文主要使用官方推荐的后一种,兼容性更好。
  • LVGL 9 中滚动事件和 snap API 基本保持一致。

如果你还没有 LVGL 环境,建议先用模拟器跑通。常见方式有三种:

  1. 官方在线模拟器:直接打开 LVGL 官网模拟器页面验证效果。
  2. VS Code + 本地模拟器:使用 CMake 或者 PlatformIO 构建模拟器工程,适合调试。
  3. 嵌入式开发板:在 STM32、ESP32 等平台上移植 LVGL 后测试,需要确认屏幕驱动和输入设备正常。

如果你是在单片机上使用,注意 LVGL 内存配置,环形滚动本身不会带来额外内存压力,但是lv_roller和动画系统会占用一些内存,建议开启LV_MEM_CUSTOM并使用专用的内存池。

5. 方案一:lv_roller 实现文本环形无缝滚动

先上最简单的方案。lv_roller的用法很直接,它本质是一个纵向滚动的列表,文本项之间用\n分隔。

5.1 创建循环模式的 roller

/* 创建 roller */ lv_obj_t * roller = lv_roller_create(lv_scr_act()); /* 设置选项,LV_ROLLER_MODE_INFINITE 表示环形无限循环 */ lv_roller_set_options(roller, "0\n" "1\n" "2\n" "3\n" "4\n" "5\n" "6\n" "7\n" "8\n" "9", LV_ROLLER_MODE_INFINITE); lv_obj_set_size(roller, 150, 200); lv_obj_center(roller); /* 默认选中第 2 项 */ lv_roller_set_selected(roller, 2, LV_ANIM_ON); /* 给 roller 添加选中事件回调 */ lv_obj_add_event_cb(roller, roller_event_cb, LV_EVENT_VALUE_CHANGED, NULL);

这里最关键的就是LV_ROLLER_MODE_INFINITE。它让 roller 首尾相接,用户向下滑动到“9”之后继续滑动,会自动循环到“0”,不会在末端停止。从用户视角来看,这就是一个环形无限循环滚动效果。

5.2 读取当前选中项

在事件回调中,我们可以拿到当前选中的字符串或者索引:

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_LOG_USER("当前选中: %s", buf); }

如果你需要的是选中索引,可以直接调用:

uint16_t sel = lv_roller_get_selected(roller);

5.3 设置默认选中项

在初始化时,调用lv_roller_set_selected(roller, index, LV_ANIM_ON)即可。注意LV_ANIM_ON会让它滚动到目标项,LV_ANIM_OFF则是直接跳转。

5.4 方案一的局限

lv_roller虽然实现简单,但它的结构相对固定:一个滚轮容器,里面是文本标签。如果你需要在滚轮里显示图片、进度条、自定义图形,lv_roller就比较难改造。这时应该走方案二。

6. 方案二:scroll_snap + 数据副本实现容器无限轮播

这个方案适合自定义内容。核心思路是:

  1. 使用一个横向滚动容器,里面放置多个子页面。
  2. 在真实页面首尾各放一份副本,让用户可以滑过真实边界。
  3. 监听LV_EVENT_SCROLL事件,当滚动到尾部副本时,立刻无动画跳转到对应的真实页面;滚动到头部副本时,立刻无动画跳转到对应的真实页面。

6.1 创建横向滚动容器

#define ITEM_WIDTH 100 #define ITEM_HEIGHT 120 #define REAL_COUNT 3 static void scroll_event_handler(lv_event_t * e); lv_obj_t * cont = lv_obj_create(lv_scr_act()); lv_obj_set_size(cont, 200, 150); lv_obj_center(cont); /* 开启横向滚动,关闭弹性回弹 */ lv_obj_set_scroll_dir(cont, LV_DIR_HOR); lv_obj_clear_flag(cont, LV_OBJ_FLAG_SCROLL_ELASTIC); /* 滚动条不需要显示 */ lv_obj_set_scrollbar_mode(cont, LV_SCROLLBAR_MODE_OFF); /* 使用 flex 布局让子页面横向排布 */ lv_obj_set_layout(cont, LV_LAYOUT_FLEX); lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_ROW); lv_obj_set_flex_align(cont, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); /* 设置容器 snap 策略 */ lv_obj_set_scroll_snap_x(cont, LV_SCROLL_SNAP_CENTER); /* 添加滚动事件回调 */ lv_obj_add_event_cb(cont, scroll_event_handler, LV_EVENT_SCROLL, NULL);

这里有一个比较容易忽略的关键点:lv_obj_clear_flag(cont, LV_OBJ_FLAG_SCROLL_ELASTIC)。如果不关闭弹性回弹,滚动时可能会出现“往回拉”的效果,破坏环形滚动的连续性。

6.2 创建页面子对象

每个子对象需要设置LV_SCROLL_SNAP_CENTER,让 snap 逻辑以每个子对象的中心作为吸附点。

static void add_page(lv_obj_t * cont, const char * text, lv_color_t color) { lv_obj_t * page = lv_obj_create(cont); lv_obj_set_size(page, ITEM_WIDTH, ITEM_HEIGHT); lv_obj_set_style_bg_color(page, color, 0); lv_obj_set_style_radius(page, 8, 0); lv_obj_set_scroll_snap_align(page, LV_SCROLL_SNAP_CENTER); lv_obj_t * label = lv_label_create(page); lv_label_set_text(label, text); lv_obj_center(label); }

创建 5 个页面:头部副本 + 真实 3 页 + 尾部副本。

/* 头部副本:真实最后一页的副本 */ add_page(cont, "2", lv_palette_main(LV_PALETTE_RED)); /* 真实页面 */ add_page(cont, "0", lv_palette_main(LV_PALETTE_BLUE)); add_page(cont, "1", lv_palette_main(LV_PALETTE_GREEN)); add_page(cont, "2", lv_palette_main(LV_PALETTE_RED)); /* 尾部副本:真实第一页的副本 */ add_page(cont, "0", lv_palette_main(LV_PALETTE_BLUE));

为了直观,副本和真实页可以使用相同的背景色。这样用户滑动时看不出差异。

6.3 初始化位置

创建完成之后,应把滚动位置定位到第一个真实页面,也就是索引 1 的位置:

lv_obj_scroll_to_x(cont, ITEM_WIDTH, LV_ANIM_OFF);

如果不做这一步,初始位置会停在第 0 个副本上,相当于用户一打开就看到重复页面。

6.4 核心滚动事件处理

接下来是环形滚动的核心逻辑:

#define TOTAL_COUNT (REAL_COUNT + 2) static void scroll_event_handler(lv_event_t * e) { lv_obj_t * cont = lv_event_get_target(e); lv_coord_t x = lv_obj_get_scroll_x(cont); /* 滚到末尾副本:偏移量 >= REAL_COUNT * ITEM_WIDTH */ if (x >= REAL_COUNT * ITEM_WIDTH) { /* 跳到开头真实页面,也就是索引 1 的位置,偏移量为 ITEM_WIDTH */ lv_obj_scroll_to_x(cont, x - REAL_COUNT * ITEM_WIDTH, LV_ANIM_OFF); } /* 滚到头部副本:偏移量 < ITEM_WIDTH */ else if (x < ITEM_WIDTH) { /* 跳到末尾真实页面,偏移量为 REAL_COUNT * ITEM_WIDTH */ lv_obj_scroll_to_x(cont, x + REAL_COUNT * ITEM_WIDTH, LV_ANIM_OFF); } }

这段逻辑的本质是“取模运算”。当偏移量超过真实内容区间时,把它减去一个周期;当偏移量小于真实内容区间起点时,把它加上一个周期。因为设置了LV_ANIM_OFF,跳转过程没有动画,用户感知不到。

这里要注意:在LV_EVENT_SCROLL回调中修改滚动位置时,一定要关掉动画。如果使用LV_ANIM_ON,会看到页面突然往回滑动的过程,这就破坏了无缝效果。

6.5 与手势滑动的配合

这个方案在真实触摸滚动、鼠标滚轮、键盘方向键滚动时都能正常工作,因为LV_EVENT_SCROLL是 LVGL 滚动机制的统一事件入口。即使 snap 动画触发滚动,回调同样会被调用,环形逻辑仍然有效。

6.6 如何判断当前显示的是哪一页

在实际业务中,你可能需要知道用户当前停在哪个真实页面上。可以在LV_EVENT_SCROLL_ENDLV_EVENT_VALUE_CHANGED事件中计算当前索引:

static void scroll_end_handler(lv_event_t * e) { lv_obj_t * cont = lv_event_get_target(e); lv_coord_t x = lv_obj_get_scroll_x(cont); /* 计算出当前吸附的中心位置,再换算成真实页面索引 */ int page_index = x / ITEM_WIDTH; if (page_index >= REAL_COUNT) { page_index -= REAL_COUNT; } if (page_index < 0) { page_index += REAL_COUNT; } LV_LOG_USER("当前真实页面索引: %d", page_index); }

在 LVGL 8.3 中,滚动结束事件可以用LV_EVENT_SCROLL_END。如果你的版本中没有这个事件,改用LV_EVENT_RELEASED也能实现类似效果,但需要注意触摸释放的时机。

7. 方案三:lv_anim 驱动实现自动无限滚动

有时候我们不需要用户操作,而是希望页面自动循环播放,比如大屏展示、广告机、演示模式。这时候需要用一个定时器或动画循环控制滚动位置。

这个方案可以和方案二结合使用,在 scroll_snap 和环形跳转逻辑之上,增加一个定时器,每隔几秒自动滚动到下一页。

7.1 使用 lv_timer 每隔固定时间滚动

static void auto_scroll_timer_cb(lv_timer_t * timer) { lv_obj_t * cont = (lv_obj_t *)lv_timer_get_user_data(timer); lv_coord_t current_x = lv_obj_get_scroll_x(cont); /* 计算下一页位置,如果超过末尾则回到第一页 */ lv_coord_t target = current_x + ITEM_WIDTH; lv_coord_t max_x = REAL_COUNT * ITEM_WIDTH; if (target > max_x) { target = ITEM_WIDTH; } lv_anim_t a; lv_anim_init(&a); lv_anim_set_var(&a, cont); lv_anim_set_exec_cb(&a, scroll_x_anim_cb); lv_anim_set_values(&a, current_x, target); lv_anim_set_time(&a, 400); lv_anim_set_path_cb(&a, lv_anim_path_ease_out); lv_anim_start(&a); }

7.2 动画执行回调

static void scroll_x_anim_cb(void * var, int32_t v) { lv_obj_t * cont = (lv_obj_t *)var; lv_obj_scroll_to_x(cont, v, LV_ANIM_OFF); }

注意,这里在动画执行回调中调用lv_obj_scroll_to_x时必须使用LV_ANIM_OFF,否则会产生动画叠加,导致滚动位置抖动。

7.3 创建定时器

lv_timer_t * timer = lv_timer_create(auto_scroll_timer_cb, 3000, cont); lv_timer_ready(timer);

lv_timer_create的第二个参数是周期,单位毫秒。上面的代码表示每 3 秒自动滚动一次。lv_timer_ready会让定时器立即触发一次,让页面一开始就进入轮播节奏。

7.4 自动滚动与用户手势的冲突处理

生产环境中一个很常见的问题是:用户正在手动滑动页面,定时器突然触发自动滚动,导致两个动画打架。

解决办法是:在检测到用户触摸时暂停定时器,触摸结束一段时间后再恢复。例如在容器上添加LV_EVENT_PRESSEDLV_EVENT_RELEASED事件,在回调中暂停和恢复定时器。

static void cont_event_cb(lv_event_t * e) { lv_event_code_t code = lv_event_get_code(e); if (code == LV_EVENT_PRESSED) { lv_timer_pause(timer); } else if (code == LV_EVENT_RELEASED) { lv_timer_resume(timer); } }

这个细节在演示类和交互类界面中非常重要,如果不处理,用户会明显感觉到界面“抢”自己的操作。

8. 运行结果与效果验证

写完代码后,我们需要确认三件事:

8.1 现象是否达到预期

  • 手动向左滑动容器,滑过最后一个真实页面后,页面自动无缝回到第一个真实页面,中间没有空白停顿。
  • 手动向右滑动容器,滑过第一个真实页面后,页面自动无缝回到最后一个真实页面。
  • 文本选择器场景下,向下滚动到最后一个选项后继续滚动,列表回到第一个选项,并且没有跳动感。

8.2 如何用日志验证

在方案二的scroll_event_handler里加入日志输出:

LV_LOG_USER("scroll x = %d", (int)x);

观察日志中 x 的数值变化。正常环形滚动时,x 应该始终落在[ITEM_WIDTH, REAL_COUNT * ITEM_WIDTH]区间内。如果发现 x 超出这个范围后立刻跳回,说明环形逻辑生效;如果 x 卡在边界,说明副本数量或边界判断写错了。

8.3 如何验证事件

LV_EVENT_VALUE_CHANGED回调中打印当前页面索引,切换页面时应该能收到连续的索引变化,并且不会出现无效值。

如果事件没有触发,优先检查是否给容器添加了正确的事件回调函数。在 LVGL 8.3 中,使用lv_obj_add_event_cb时事件回调签名是void (*)(lv_event_t *),不要写错。

9. 常见问题与排查思路

问题现象可能原因排查方式解决方案
滚动到末尾后停止,没有循环未设置LV_ROLLER_MODE_INFINITE,或者方案二中未添加尾部副本检查lv_roller_set_options的第三个参数;检查容器子对象数量将 roller 模式改为LV_ROLLER_MODE_INFINITE;方案二补上尾部副本
环形跳转时出现明显回弹动画在滚动回调中使用了LV_ANIM_ON,或者没有关闭弹性回弹检查lv_obj_scroll_to_x参数;检查SCROLL_ELASTIC标志改为LV_ANIM_OFF;调用lv_obj_clear_flag(cont, LV_OBJ_FLAG_SCROLL_ELASTIC)
滚动位置异常跳变,页面错乱flex 布局下子对象宽度未固定,或者 snap 对齐未设置打印每个子对象的坐标和宽度固定子对象尺寸,并确保所有子对象设置LV_SCROLL_SNAP_CENTER
事件回调不触发事件回调未注册,或者注册在了错误对象上检查lv_obj_add_event_cb的目标对象将回调注册到实际可滚动容器上
自动轮播与手势冲突未在用户触摸时暂停定时器打印定时器状态,观察触发时机LV_EVENT_PRESSED暂停,在LV_EVENT_RELEASED恢复
在 LVGL 9.x 下编译报错部分 API 在 9.x 中改名或调整了头文件查看编译错误信息对应 API对照 LVGL 9 的迁移文档调整函数名,例如部分样式接口

10. 最佳实践与工程建议

10.1 优先使用官方循环能力

如果一个功能官方已经提供了,就不要自己再实现一遍。lv_rollerLV_ROLLER_MODE_INFINITE就是官方对环形无限滚动的封装,使用它能极大降低维护成本。只有在 UI 结构不满足需求时,才考虑自定义容器方案。

10.2 数据副本不要被业务逻辑污染

方案二中,首尾副本只是用来“骗过滚动边界”的 UI 占位,不应该存储真实业务数据。比如真实数据数组只有 3 条,副本不应该额外申请业务数组。跳转逻辑通过偏移量的取模完成,不要在副本上绑定业务状态,否则数据同步会出现麻烦。

10.3 关闭不必要的滚动效果

在环形滚动场景中,LV_OBJ_FLAG_SCROLL_ELASTICLV_SCROLLBAR_MODE_ON默认可能会对体验造成干扰。建议关闭弹性回弹,并隐藏滚动条。如果你需要滚动条提示用户位置,可以在容器上使用LV_SCROLLBAR_MODE_AUTO,但要注意环形跳转时滚动条位置可能瞬间变化。

10.4 考虑屏幕刷新率和动画时长

在嵌入式设备上,动画时间不宜太长。一般滑动吸附动画设置在 200ms 到 500ms 之间。如果屏幕刷新率较低,或者主控性能一般,动画时间过长会导致画面看起来拖沓。自动轮播的等待时间建议在 2 到 5 秒之间,具体根据内容量和用户阅读速度调整。

10.5 定时器生命周期管理

如果页面会被销毁,一定要在页面销毁时将自动轮播的lv_timer删除,否则会出现野指针和内存泄漏。推荐在页面析构或LV_EVENT_DELETE事件中调用lv_timer_del(timer)

10.6 版本差异处理

LVGL 8.x 和 9.x 在 API 上有一些差异,编写代码时最好统一到项目实际使用的版本。如果你还在学习阶段,建议使用 LVGL 8.3 或 9.x 的官方模拟器工程,先跑通再移植到开发板,这样可以减少环境问题带来的干扰。

11. 总结与后续学习方向

环形无限循环滚动并不是一个需要修改 LVGL 底层的复杂功能,关键在于理解它的本质:内容有限,滚动位置取模。无论是lv_rollerLV_ROLLER_MODE_INFINITE,还是自定义容器里的副本跳转,都是在用不同的方式实现同一个取模逻辑。

这篇文章给出了三种方案:

  • 文本选择器场景,直接使用lv_roller的循环模式。
  • 自定义页面轮播场景,使用scroll_snap + 数据副本 + 滚动事件跳转
  • 自动展示场景,使用lv_timer + lv_anim驱动滚动位置。

你可以先从方案一跑通,感受 LVGL 的环形滚动效果,再对照方案二理解滚动偏移量的取模逻辑。下一步可以尝试给方案二加入自动轮播,并在真实开发板上测试触摸交互和动画效果。如果遇到页面初始化位置不对、副本数量不够、滚动跳变等问题,优先检查滚动事件回调中的偏移量计算,把边界条件用日志打印出来,问题通常会很快定位。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询