| 分类 android  | 标签 Paging3  分页  RecyclerView  Android  Jetpack 

title

引言

Paging 3 是 Android Jetpack 的分页库,用于高效加载和显示分页数据。与手动实现分页逻辑相比,Paging 3 内置加载状态(加载中、错误、无更多数据)、重试机制、预加载和边界回调,大幅减少样板代码。它支持从网络、数据库或两者结合(RemoteMediator)加载数据,与 RecyclerView、Flow、LiveData 无缝集成。本文将深入探讨 Paging 3 的 PagingSource、Pager 配置、LoadState 处理以及 RemoteMediator 的用法。

为什么使用 Paging 3

手动分页需要管理页码、加载状态、重试、列表与加载状态的 UI 同步等,代码冗长且易出错。Paging 3 将”何时加载”、”加载多少”、”如何展示状态”抽象为可配置的组件,开发者只需实现数据源(PagingSource 或 RemoteMediator)和 UI 绑定。Paging 3 基于 Kotlin 协程和 Flow 设计,与 ViewModel、Repository 的现代架构自然契合。内置的 LoadState 可方便地展示加载中、错误、重试等状态,提升用户体验。

添加依赖

dependencies {
    implementation("androidx.paging:paging-runtime-ktx:3.2.1")
}

PagingSource 实现

PagingSource 是数据源抽象,根据 LoadParams(包含 key、loadSize 等)加载一页数据,返回 LoadResult.PageLoadResult.Errorkey 是上一页/下一页的标识,通常为页码;prevKeynextKey 用于告诉 Paging 如何加载上一页和下一页,为 null 表示没有更多数据。网络分页时,根据 API 的页码或游标约定实现;数据库分页可使用 PagingSource 的 Room 集成(Room 可自动生成 PagingSource)。

class ArticlePagingSource(
    private val api: ApiService
) : PagingSource<Int, Article>() {
    
    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Article> {
        return try {
            val page = params.key ?: 1
            val response = api.getArticles(page, params.loadSize)
            
            LoadResult.Page(
                data = response.articles,
                prevKey = if (page == 1) null else page - 1,
                nextKey = if (response.hasMore) page + 1 else null
            )
        } catch (e: Exception) {
            LoadResult.Error(e)
        }
    }
}

创建 Pager

Pager 是 Paging 3 的入口,接收 PagingConfig(pageSize、enablePlaceholders、initialLoadSize 等)和 pagingSourceFactoryflow 属性返回 Flow<PagingData>,可在 ViewModel 中暴露给 UI 层。enablePlaceholders 为 true 时,未加载的项会占位,适合固定尺寸的列表;为 false 时列表长度随加载动态变化,更常见。

val articles: Flow<PagingData<Article>> = Pager(
    config = PagingConfig(
        pageSize = 20,
        enablePlaceholders = false
    ),
    pagingSourceFactory = { ArticlePagingSource(api) }
).flow

在 RecyclerView 中使用

使用 PagingDataAdapter 替代普通 Adapter,通过 submitData 提交 PagingData。withLoadStateFooter 可在列表底部添加加载状态(加载中、错误、重试)。收集 Flow 时使用 repeatOnLifecycle(Lifecycle.State.STARTED) 确保仅在界面可见时收集,避免泄漏和后台执行。adapter.loadStateFlow 可观察整体加载状态,用于显示全局加载指示器或错误提示。

@AndroidEntryPoint
class ArticleFragment : Fragment() {
    
    private val viewModel: ArticleViewModel by viewModels()
    
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        
        val adapter = ArticleAdapter()
        binding.recyclerView.adapter = adapter.withLoadStateFooter(
            footer = ArticleLoadStateAdapter { adapter.retry() }
        )
        
        viewLifecycleOwner.lifecycleScope.launch {
            viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.articles.collect { pagingData ->
                    adapter.submitData(pagingData)
                }
            }
        }
    }
}

RemoteMediator 网络+数据库

RemoteMediator 用于”网络 + 本地数据库”的分页:本地数据库作为单一数据源,RemoteMediator 在需要时从网络加载并写入数据库,Paging 从数据库读取。适用于离线优先、缓存优先的场景。load 根据 LoadType(REFRESH、PREPEND、APPEND)决定加载策略,返回 MediatorResult.SuccessMediatorResult.Error。需配合 Room 的 RemoteMediatorPagingSource 使用,配置稍复杂,但能实现完整的分页缓存方案。

@ExperimentalPagingApi
class ArticleRemoteMediator(
    private val database: AppDatabase,
    private val api: ApiService
) : RemoteMediator<Int, Article>() {
    
    override suspend fun load(
        loadType: LoadType,
        state: PagingState<Int, Article>
    ): MediatorResult {
        return try {
            val page = when (loadType) {
                LoadType.REFRESH -> 1
                LoadType.PREPEND -> return MediatorResult.Success(true)
                LoadType.APPEND -> state.lastItemOrNull()?.nextKey ?: return MediatorResult.Success(false)
            }
            
            val response = api.getArticles(page, state.config.pageSize)
            database.articleDao().insertAll(response.articles)
            
            MediatorResult.Success(endOfPaginationReached = !response.hasMore)
        } catch (e: Exception) {
            MediatorResult.Error(e)
        }
    }
}

总结

Paging 3 简化了分页数据的加载和展示,是处理列表类数据的最佳选择。掌握 PagingSource、LoadState 和 RemoteMediator,可以构建高效、用户友好的分页列表,并支持网络与本地缓存的组合策略。


上一篇     下一篇