首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Paging 3 分页加载:从配置到实战

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

原创
作者头像
hunter android
发布2026-08-28 10:27:48
发布2026-08-28 10:27:48
90
举报

一、什么是 Paging 3

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

核心优势:

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

二、核心组件

1. PagingSource

数据源抽象,负责从网络或数据库分页拉取数据。

代码语言:javascript
复制
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<PagingData>。

代码语言:javascript
复制
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 的适配器,自动处理数据更新和差异对比。

代码语言:javascript
复制
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 集成

代码语言:javascript
复制
class ArticleViewModel(
    private val repository: ArticleRepository
) : ViewModel() {

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

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

四、Activity/Fragment 使用

代码语言:javascript
复制
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 显示加载进度和错误提示。

代码语言:javascript
复制
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 中挂载:

代码语言:javascript
复制
binding.recyclerView.adapter = adapter.withLoadStateFooter(
    footer = LoadStateAdapter { adapter.retry() }
)

六、刷新与重试

代码语言:javascript
复制
// 下拉刷新
binding.swipeRefresh.setOnRefreshListener {
    adapter.refresh()
}

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

七、Room 本地缓存 + RemoteMediator

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

代码语言:javascript
复制
@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 配置:

代码语言:javascript
复制
@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 让分页加载变得简单高效,适合处理列表、信息流等大数据场景。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 一、什么是 Paging 3
  • 二、核心组件
    • 1. PagingSource
    • 2. Pager
    • 3. PagingDataAdapter
  • 三、ViewModel 集成
  • 四、Activity/Fragment 使用
  • 五、加载状态处理
  • 六、刷新与重试
  • 七、Room 本地缓存 + RemoteMediator
  • 八、总结
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档