引言
Paging2的使用
这是很早之前写过的一篇关于Paging2的分页加载使用,最近JectPack组件中,更新了Paging库,升级到了3,已经全面支持协程
1.1 与Paging2有什么不同
a.全面支持协程
b.PagingSource单一
c.支持添加Header和Footer
d.支持数据转换
1.2 PagingSource单一
数据源单一体现在,像Paging2需要根据业务场景选择不同的数据源,而Paging3只有一个数据源PagingSource,具体的分页逻辑处理,在load中进行
class ContentPageSource(
var videoType:String,
var chapterId:String,
var subjectId:String
): PagingSource<Int,VideoListInfo>(){
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, VideoListInfo> {
//默认第一页的位置
val page = params.key?:PAGE
//分页请求数据
val data = ContentRepository.instance.getVideoList(videoType,page,chapterId,subjectId)
return try {
LoadResult.Page(
//当前页的数据
data.list,
//前一页的页码
prevKey = null,
//后一页的页码
nextKey = if(data.list.isNullOrEmpty()) null else data.page.toInt() + 1
)
}catch (e:Exception){
OkLogger.e("TAG","$e")
LoadResult.Error(e)
}
}
}
(1)PagingSource<Key : Any, Value : Any>,需要2个泛型,第一个参数为页码的参数类型,例如Int或者String,第二个参数为当前页面Item的数据结构Bean,而不是一个列表。
(2)load方法是suspend挂起函数,支持协程,可在load方法中执行耗时操作;params.key参数表示当前的页码,如果为空就默认第一页;默认返回的数据为LoadResult
1.3 数据转换
fun paging(videoType: String, chapterId: String, subjectId: String) : LiveData<PagingData<VideoListInfo>>? {
return try {
Pager(
PagingConfig(6)
)
{
ContentPageSource(videoType,chapterId,subjectId)
}.flow.cachedIn(viewModelScope).asLiveData(viewModelScope.coroutineContext)
}catch (e:Exception){
OkLogger.e("ContentViewModel","$e")
null
}
}
在ViewModel中,通过构造Pager对象,传入PagingConfig,可以设置一页保存的item数量,超过这个数据就会执行下一页的请求,具体的请求在ContentPageSource中执行;
获取到数据后,可通过cachedIn缓存在当前的ViewModel中,asLiveData可将Flow转换为LiveData,可根据自身喜好转换
1.4 PagingDataAdapter
Paging支持RecyclerView加载分页数据,PagingDataAdapter是分页加载特有的适配器,需要初始化一个DiffUtil.ItemCallback,来判断数据是否一致来刷新界面,通过submitData来加载
class ContentPageListAdapter(
var context: Context,
) : PagingDataAdapter<VideoListInfo,ContentViewHolder>(DIFFCALLBACK) {
companion object{
val DIFFCALLBACK = object : DiffUtil.ItemCallback<VideoListInfo>(){
override fun areItemsTheSame(oldItem: VideoListInfo, newItem: VideoListInfo): Boolean {
OkLogger.e("ContentPageListAdapter","areItemsTheSame ${oldItem.id == newItem.id}")
return oldItem.id == newItem.id
}
override fun areContentsTheSame(
oldItem: VideoListInfo,
newItem: VideoListInfo
): Boolean {
OkLogger.e("ContentPageListAdapter","areContentsTheSame ${oldItem.id == newItem.id}")
return oldItem.id == newItem.id
}
}
}
1.5 错误处理
Paging3有一个奇怪的问题就是,即便在分页中传入一个null,也不会报错,那么怎么判断如果第一页数据就全为空的场景?
viewModel.paging(
"short_video",
it!!,
subjectMap[subject]!!)?.
observe(this){
lifecycleScope.launchWhenCreated {
//数据加载
adapter.submitData(it)
adapter.loadStateFlow.map { it.refresh }
.distinctUntilChanged()
.collect {
if(it is LoadState.NotLoading){
if(adapter.itemCount == 0){
binding.contentEmptyPage.llEmptyPage.visibility = View.VISIBLE
binding.contentPageShow.background = context?.getDrawable(R.drawable.empty_page_background)
}else{
binding.contentEmptyPage.llEmptyPage.visibility = View.GONE
binding.contentPageShow.background = null
}
}
}
}
}
使用PagingDataAdapter的loadStateFlow,等待页面加载完成之后,判断当前页面是否存在数据(adapter.itemCount),如果不存在,那么就做相应的业务处理
Ending…