为什么要分页

一个长列表可能有几千条数据。一次性全拉下来,流量大、加载慢、列表还卡。分页就是用户滚到哪、加载到哪,一次一页,体验丝滑。

滚到哪加载到哪,不一次性拉全。

Jetpack Paging 3 把这套逻辑封装好了,直接用。

手写分页不难但容易出 bug:滚到底没触发、重复加载、刷新后页码错位、加加载中状态忘了关。Paging 3 把这些脏活全包了,只管告诉它"下一页在哪一页、接口长什么样",剩下的翻页、去重、预取、错误重试它都自己来。

Paging 3 三件套

PagingSource 告诉数据从哪来、怎么翻页;Pager 把它变成一个 Flow;列表把这个 Flow 渲染出来。三者配合。

分页最难的其实是"下拉刷新"和"局部更新"。Paging 3 的刷新就是重新跑一遍 load,你只要在 ViewModel 里调一下 refresh,列表会从头开始重新加载。点赞、收藏这类单条更新,用 LazyPagingItems 的 peek 或者监听远端数据变化,让 Paging 自动 diff,不用整列表重刷。

Paging 速查
  • PagingSource定义数据源。
  • Pager产出 Flow。
  • collectAsLazyPagingItems喂给列表。

pageSize 别贪大,20 到 30 条是舒服区间;太小翻页次数多、太大首次加载慢。配 prefetchDistance 让列表提前在距底部几屏时就预取下一页,滚动才不卡。配合 cachedIn 把流放在 viewModelScope,旋转屏幕不重新加载。

写 PagingSource

class NewsSource : PagingSource<Int, News>() {
  override suspend fun load(p: LoadParams<Int>): LoadResult<Int, News> {
    val page = p.key ?: 1
    val data = api.fetchNews(page)
    return LoadResult.Page(data, prevKey = null, nextKey = page + 1)
  }
}

LoadResult.Page 里的 prevKey 和 nextKey 是翻页的关键:最后一页就把 nextKey 设成 null,Paging 就知道到底了、不再请求。数据源出错时返回 LoadResult.Error(e),界面上会自动显示加载失败的状态,可以配合重试按钮。

页面上收集流

用 Pager 构造流,ViewModel 暴露出去。

val flow = Pager(PagingConfig(pageSize = 20)) { NewsSource() }.flow.cachedIn(viewModelScope)
安卓分页:一叠列表卡片从屏幕底部像卷轴逐屏向上加载展开、下方发光加载环的蓝调插画
列表卡片像卷轴逐屏加载,下方有发光加载环。