Paging 3 分页加载:从配置到实战

简介: Paging 3 是 Jetpack 高效分页库,支持自动分页、状态管理、网络+本地双缓存及 RecyclerView 无缝集成。本文详解 PagingSource、Pager、PagingDataAdapter 等核心组件,涵盖纯网络分页、RemoteMediator 离线缓存、加载状态展示与下拉刷新实战。

Paging 3 分页加载:从配置到实战

一、什么是 Paging 3

Paging 3 是 Jetpack 组件库中专门用于分页加载的库,可以高效处理大数据集,避免一次性加载过多数据导致内存溢出或界面卡顿。

核心优势:

  • 自动管理分页逻辑
  • 内置加载状态(加载中、成功、失败)
  • 支持网络 + 本地双层缓存
  • 与 RecyclerView 无缝集成

二、核心组件

1. 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.data,
                prevKey = if (page == 1) null else page - 1,
                nextKey = if (response.data.isEmpty()) null else page + 1
            )
        } catch (e: Exception) {
            LoadResult.Error(e)
        }
    }

    override fun getRefreshKey(state: PagingState<Int, Article>): Int? {
        return state.anchorPosition?.let { anchorPosition ->
            state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1)
                ?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1)
        }
    }
}

2. Pager

配置分页参数,返回 Flow。

class ArticleRepository(private val api: ApiService) {

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

3. PagingDataAdapter

RecyclerView 的适配器,自动处理数据更新和差异对比。

class ArticleAdapter : PagingDataAdapter<Article, ArticleAdapter.ViewHolder>(DiffCallback) {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val binding = ItemArticleBinding.inflate(
            LayoutInflater.from(parent.context), parent, false
        )
        return ViewHolder(binding)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val article = getItem(position)
        article?.let { holder.bind(it) }
    }

    class ViewHolder(private val binding: ItemArticleBinding) : 
        RecyclerView.ViewHolder(binding.root) {

        fun bind(article: Article) {
            binding.title.text = article.title
            binding.author.text = article.author
        }
    }

    companion object DiffCallback : DiffUtil.ItemCallback<Article>() {
        override fun areItemsTheSame(oldItem: Article, newItem: Article): Boolean {
            return oldItem.id == newItem.id
        }

        override fun areContentsTheSame(oldItem: Article, newItem: Article): Boolean {
            return oldItem == newItem
        }
    }
}

三、ViewModel 集成

class ArticleViewModel(
    private val repository: ArticleRepository
) : ViewModel() {

    val articles: Flow<PagingData<Article>> = repository.getArticleStream()
        .cachedIn(viewModelScope)
}

cachedIn(viewModelScope) 确保在配置变更(如旋转屏幕)时缓存数据,避免重复请求。

四、Activity/Fragment 使用

class ArticleActivity : AppCompatActivity() {

    private lateinit var binding: ActivityArticleBinding
    private val viewModel: ArticleViewModel by viewModels()
    private val adapter = ArticleAdapter()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityArticleBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.recyclerView.adapter = adapter

        lifecycleScope.launch {
            viewModel.articles.collectLatest { pagingData ->
                adapter.submitData(pagingData)
            }
        }
    }
}

五、加载状态处理

Paging 3 提供 LoadStateAdapter 显示加载进度和错误提示。

class LoadStateAdapter(
    private val retry: () -> Unit
) : LoadStateAdapter<LoadStateAdapter.ViewHolder>() {

    override fun onCreateViewHolder(parent: ViewGroup, loadState: LoadState): ViewHolder {
        val binding = ItemLoadStateBinding.inflate(
            LayoutInflater.from(parent.context), parent, false
        )
        return ViewHolder(binding, retry)
    }

    override fun onBindViewHolder(holder: ViewHolder, loadState: LoadState) {
        holder.bind(loadState)
    }

    class ViewHolder(
        private val binding: ItemLoadStateBinding,
        private val retry: () -> Unit
    ) : RecyclerView.ViewHolder(binding.root) {

        fun bind(loadState: LoadState) {
            binding.progressBar.isVisible = loadState is LoadState.Loading
            binding.retryButton.isVisible = loadState is LoadState.Error
            binding.errorText.isVisible = loadState is LoadState.Error

            binding.retryButton.setOnClickListener { retry() }
        }
    }
}

在 Activity 中挂载:

binding.recyclerView.adapter = adapter.withLoadStateFooter(
    footer = LoadStateAdapter { adapter.retry() }
)

六、刷新与重试

// 下拉刷新
binding.swipeRefresh.setOnRefreshListener {
    adapter.refresh()
}

// 监听加载状态
lifecycleScope.launch {
    adapter.loadStateFlow.collectLatest { loadStates ->
        binding.swipeRefresh.isRefreshing = loadStates.refresh is LoadState.Loading
    }
}

七、Room 本地缓存 + RemoteMediator

结合 Room 数据库实现离线缓存和网络加载的混合方案。

@OptIn(ExperimentalPagingApi::class)
class ArticleRemoteMediator(
    private val api: ApiService,
    private val database: AppDatabase
) : 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(endOfPaginationReached = true)
                LoadType.APPEND -> {
                    val lastItem = state.lastItemOrNull()
                        ?: return MediatorResult.Success(endOfPaginationReached = true)
                    lastItem.page + 1
                }
            }

            val response = api.getArticles(page, state.config.pageSize)

            database.withTransaction {
                if (loadType == LoadType.REFRESH) {
                    database.articleDao().clearAll()
                }
                database.articleDao().insertAll(response.data)
            }

            MediatorResult.Success(endOfPaginationReached = response.data.isEmpty())
        } catch (e: Exception) {
            MediatorResult.Error(e)
        }
    }
}

Pager 配置:

@OptIn(ExperimentalPagingApi::class)
fun getArticleStream(): Flow<PagingData<Article>> {
    return Pager(
        config = PagingConfig(pageSize = 20),
        remoteMediator = ArticleRemoteMediator(api, database),
        pagingSourceFactory = { database.articleDao().pagingSource() }
    ).flow
}

八、总结

场景 方案
纯网络分页 PagingSource + Pager
网络 + 本地缓存 RemoteMediator + Room
加载状态展示 LoadStateAdapter
下拉刷新 adapter.refresh()

Paging 3 让分页加载变得简单高效,适合处理列表、信息流等大数据场景。

相关文章
人工智能 缓存 前端开发
12023 63
人工智能 JavaScript 开发工具
4805 17
Web App开发 人工智能 API
1381 1
人工智能 Java BI
1467 1
开发工具 Swift git
1972 6
人工智能 JavaScript 测试技术
2402 2
人工智能 自然语言处理 安全
980 0
人工智能 JavaScript 测试技术
1199 4
缓存 JavaScript Shell
2099 3

热门文章

最新文章