无限滚动(Infinite Scroll)是一种在用户到达当前列表底部时自动加载内容的技术。根据 UX Design Collective, 2024 的数据,Infinite Scroll 在社交网络中将会话时长比分页提高了 40–60%。在移动开发中,这项技术通过滚动监听器和基于游标的分页 API 请求组合实现。无限滚动已成为内容流的事实标准,但需要仔细实现以避免性能和导航问题。
要点
无限滚动(Infinite Scroll)是一种数据加载模式,当用户滚动时,新元素会自动添加到列表末尾。用户无需点击“下一页”或“加载更多”按钮——系统自行判断何时请求下一批数据,并无缝地将新记录插入到现有列表中。
无限滚动因社交网络而流行——Twitter、Instagram 和 TikTok 将其用作内容分发的主要机制。根据 Nielsen Norman Group(2024)的数据,Infinite Scroll 使内容应用的用户参与度提高了 30–50%,因为它降低了认知负担:用户无需决定是否进入下一页。然而,对于需要精确定位的任务(搜索、商品比较),Infinite Scroll 可能会降低效率。
从技术上讲,无限滚动由三个组件组成:滚动监听器(跟踪滚动位置)、阈值(距列表末尾的距离,用于触发加载)和分页机制(API 请求和数据插入)。正确设置阈值至关重要:过早触发(距末尾 1000 像素)会导致不必要的请求,过晚触发(50 像素)用户会注意到加载暂停。
Infinite Scroll 的架构基于事件模型:列表组件在达到滚动阈值时生成事件,ViewModel 处理该事件并调用仓库加载下一批数据。收到响应后,新元素被插入到列表中,UI 通过适配器更新。这个链条必须是异步的,并且不能阻塞 UI 线程。
Infinite Scroll 的基本算法包括四个步骤。初始化:首次打开屏幕时,加载第一批数据(page 1 或 cursor = null)。监听:滚动监听器检查用户是否已达到阈值——通常是距列表末尾 200–500 像素。加载:向 API 发送带有分页参数的请求,UI 显示加载指示器(页脚中的旋转器)。插入:新元素被添加到适配器,调整滚动位置以避免跳转。
一个关键方面是请求的防抖。如果用户快速滚动到底部,触发器可能在收到服务器响应前被多次触发。没有防抖,这将导致重复请求(竞态条件)。解决方案是在前一个请求完成之前阻止新请求。ViewModel 中的 isLoading 标志防止多次调用:发送请求时 set isLoading = true,收到响应或错误时重置。
在移动平台上,Infinite Scroll 使用专门的机制。在 iOS 上,是 UICollectionView 中的 prefetchDataSource,它会自动请求屏幕外单元格的数据。在 Android 上,是 Google 的 Paging 3 库,它提供了包含 PagingSource、PagingData 和 PagingDataAdapter 的现成架构。Paging 3 支持 RemoteMediator 用于组合网络和本地数据,并自动管理加载状态。
基于偏移的分页使用 page 和 size 参数:page=2, size=20 返回第 21–40 条记录。这种方法实现简单,但有一个根本问题——如果在请求之间数据库中添加或删除了记录,偏移量就会错乱(用户看到重复或遗漏)。对于变化频率高的流(新闻、评论),基于偏移的分页会给出不正确的结果。
基于游标的分页使用最后一个元素的唯一标识符(游标):after=id_12345&limit=20。服务器返回指定游标之后的 20 条记录。这种方法保证了数据一致性,不受插入和删除的影响。根据 GraphQL Best Practices(2024),对于所有数据动态变化的实时应用,推荐使用基于游标的分页。
方法之间的选择取决于应用类型。对于社交网络(Instagram、TikTok)——仅使用基于游标的分页,因为流不断更新。对于变化较少的目录(在线商店的商品类别),基于偏移的分页是可以接受的。对于混合场景,Google 推荐 Paging 3 RemoteMediator,它结合了来自网络的基于游标的分页和来自本地 Room 数据库的基于偏移的分页。
在 Android 上,标准方法是使用 Jetpack 中的 Paging 3 库。PagingSource 定义数据源(网络或数据库),PagingData 包含数据块,PagingDataAdapter 在 RecyclerView 中显示它们。Paging 3 自动管理预取距离、重试和刷新。对于网络集成,使用 RemoteMediator:它从 API 加载数据,保存到 Room,并通知 PagingSource 有关更新。根据 Google I/O 2024 的数据,超过 60% 使用 Infinite Scroll 的 Android 应用都在使用 Paging 3。
Paging 3 基本实现示例:
class FeedPagingSource(
private val api: FeedApi
) : PagingSource<String, Post>() {
override suspend fun load(
params: LoadParams<String>
): LoadResult<String, Post> {
val response = api.getFeed(
cursor = params.key,
limit = params.loadSize
)
return LoadResult.Page(
data = response.items,
prevKey = null,
nextKey = response.nextCursor
)
}
}
在 iOS 上,Infinite Scroll 的实现使用 UICollectionView 与 prefetchDataSource 的组合。UICollectionViewDataSourcePrefetching 协议包含 collectionView(_:prefetchItemsAt:) 方法,当系统预测将滚动到特定索引路径时会调用该方法。与 Android Paging 3 不同,iOS 没有内置的分页库——开发人员需要手动实现或使用第三方解决方案,如 RxSwift + NSLayoutConstraint 或基于 Combine 的管道。
iOS 上的 prefetch 示例:
extension FeedViewController: UICollectionViewDataSourcePrefetching {
func collectionView(
_ collectionView: UICollectionView,
prefetchItemsAt indexPaths: [IndexPath]
) {
let lastRow = collectionView.numberOfItems(inSection: 0) - 1
if indexPaths.contains(IndexPath(row: lastRow, section: 0)) {
viewModel.loadNextPage()
}
}
}
SwiftUI 通过 onAppear 修饰符提供了更声明式的方法。开发人员在列表末尾放置一个 ProgressView,当它出现时调用下一页的加载。根据 Apple WWDC 2024,新的 AsyncSequence 和 Swift Algorithms API 通过提供内置的分块和防抖操作符简化了无限滚动的实现。
Infinite Scroll 的主要 UX 问题是页脚和导航丢失。在在线商店中,用户经常希望转到页脚查看联系信息或链接。无限滚动使页脚无法访问——随着数据加载,它不断向下移动。解决方案是添加一个快速返回顶部的浮动按钮(FAB)或将页脚与列表分开固定。
第二个问题是缺少滚动历史。如果用户在第 3 个位置看到了感兴趣的商品,滚动到第 50 个位置,然后按下“返回”——他们会回到列表的开头,不得不再次滚动到第 50 个位置。解决方案是在 ViewModel 中保存滚动位置,或在 Activity/UIViewController 级别使用状态恢复。iOS 支持 NSUserActivity 用于位置恢复,Android 使用 onSaveInstanceState。
第三个问题是数千个元素时的性能问题。如果未配置虚拟化,在加载 500–1000 个元素后,由于内存消耗增加,应用开始变慢。解决方案是使用 RecyclerView 或 UICollectionView 的虚拟化,它只在内存中保存可见和预取的单元格。定期清理旧数据(丢弃超过 N 页的页面)也可以降低负载。
常见问题
无限滚动(Infinite Scroll)是一种在用户到达列表底部时自动加载内容的技术。新数据无需点击分页按钮即可无缝添加。用于社交网络、新闻推送和动态内容目录。
分页需要手动在页面之间切换(“1, 2, 3”按钮),而 Infinite Scroll 自动加载数据。分页可预测且保持导航上下文,Infinite Scroll 提高参与度但使访问页脚和滚动历史变得困难。选择取决于内容类型和应用目标。
在 Android 上推荐使用 Jetpack 中的 Paging 3 库。它提供用于数据源的 PagingSource、用于数据块的 PagingData 和用于 RecyclerView 的 PagingDataAdapter。Paging 3 自动管理预取、加载状态和重试。对于混合离线/在线场景,请使用 RemoteMediator。
通过 debounce isLoading 标志防止重复请求。当第一个请求发送时,该标志设置为 true 并阻止新的调用,直到收到响应。成功响应后重置标志。此外,可以在向后滚动时使用协程取消(Kotlin)或 Cancellable(Swift)。
Infinite Scroll 不适用于带有搜索和商品比较的电子商务、带有重要页脚(联系信息、链接)的应用、搜索结果页面(用户需要返回特定元素)以及总计数很重要的统计/报告页面。在这些情况下,请使用经典分页或“加载更多”按钮。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。