以下是一个基于 Android Paging 3 库的完整 Kotlin 实现示例,结合网络分页场景,包含核心组件和最佳实践:
1. 添加依赖(build.gradle)
dependencies {
implementation "androidx.paging:paging-runtime-ktx:3.2.1"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3"
implementation "com.squareup.retrofit2:retrofit:2.9.0" // 网络请求
}
2. 定义数据模型
data class Article(
val id: Long,
val title: String,
val content: String
)
3. 实现 PagingSource(网络数据源)
class ArticlePagingSource(
private val apiService: ApiService
) : PagingSource<Int, Article>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Article> {
return try {
val page = params.key ?: 1 // 起始页码
val response = apiService.getArticles(page, params.loadSize)
LoadResult.Page(
data = response.articles,
prevKey = if (page > 1) page - 1 else null, // 上一页
nextKey = if (response.hasMore) page + 1 else null // 下一页
)
} catch (e: Exception) {
LoadResult.Error(e) // 自动传递错误到UI
}
}
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)
}
}
}
4. 在 ViewModel 中配置数据流
class ArticleViewModel : ViewModel() {
private val api = RetrofitClient.create<ApiService>()
val articles: Flow<PagingData<Article>> = Pager(
config = PagingConfig(
pageSize = 20, // 每页数据量
prefetchDistance = 5, // 提前加载下一页的阈值(item数量)
enablePlaceholders = false // 禁用占位符提升性能
),
pagingSourceFactory = { ArticlePagingSource(api) }
).flow
.cachedIn(viewModelScope) // 防止重复创建数据流
}
5. 实现 PagingDataAdapter
class ArticleAdapter : PagingDataAdapter<Article, ArticleAdapter.ViewHolder>(DIFF_CALLBACK) {
companion object {
private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<Article>() {
override fun areItemsTheSame(oldItem: Article, newItem: Article): Boolean =
oldItem.id == newItem.id // ID 相同视为同一项[5](@ref)
override fun areContentsTheSame(oldItem: Article, newItem: Article): Boolean =
oldItem == newItem // 内容完全一致
}
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(article: Article) {
itemView.findViewById<TextView>(R.id.title).text = article.title
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_article, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
getItem(position)?.let { holder.bind(it) }
}
}
6. 在 Activity/Fragment 中集成
class ArticleListFragment : Fragment() {
private lateinit var binding: FragmentArticleListBinding
private val viewModel: ArticleViewModel by viewModels()
private val adapter = ArticleAdapter()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
binding.recyclerView.apply {
layoutManager = LinearLayoutManager(requireContext())
adapter = this@ArticleListFragment.adapter
}
// 监听加载状态(加载中/错误/完成)
adapter.addLoadStateListener { loadState ->
when (loadState.refresh) {
is LoadState.Loading -> showProgressBar()
is LoadState.Error -> showError("加载失败")
is LoadState.NotLoading -> hideProgressBar()
}
}
// 绑定数据流
lifecycleScope.launch {
viewModel.articles.collectLatest { pagingData ->
adapter.submitData(pagingData) // 提交分页数据
}
}
}
// 错误重试逻辑
private fun showError(message: String) {
binding.errorText.text = message
binding.retryButton.setOnClickListener { adapter.retry() } // 自动重试失败页
}
}
关键配置说明
-
PagingConfig 参数优化
-
prefetchDistance:建议设为屏幕可见项数量的 1-2 倍(如 5-10) -
maxSize:设置缓存上限(默认 200),防止内存溢出 -
enablePlaceholders:关闭占位符可减少内存占用
-
-
错误处理机制
-
adapter.retry():自动重试失败的分页请求 -
LoadResult.Error:在 UI 层通过LoadState监听错误
-
-
性能优化
-
cachedIn(viewModelScope):避免重复创建数据流 -
高效 DiffUtil:通过
areItemsTheSame快速比较数据变更
-
647

被折叠的 条评论
为什么被折叠?



