Paging3是jetpack推出的一个分页加载库,用于方便开发者实现分页加载功能,支持显示加载状态,重试机制,支持协程与RxJava结合使用,相对于传统的分页加载方案,我们不需要关注recyclerview的滑动状态,然后根据状态去实时请求接口,所有相关的判断逻辑,Paging3已经在内部为我们做好了实现,我们只需要实现Paging3的提供的抽象方法,即可实现分页加载功能
// Paging3默认使用协程,如果不需要使用RxJava,则只引入这一个依赖即可
implementation 'androidx.paging:paging-runtime:3.1.1'
// 如果需要使用RxJava,则需要在引入这个依赖
implementation "androidx.paging:paging-rxjava2:3.1.1"
Paging3的使用需要关注三个类:
使用方法与RecyclerView的adapter几乎一致,需要注意的是,PagingDataAdapter默认添加了DiffUtil,需要我们手动实现DiffUtil的对比方法
Paging3提供的封装工具,提供配置分页参数,数据请求形式,页面加载逻辑等配置操作
我们需要实现此类,用于实现数据的请求与加载逻辑
/**
* 时间:2022/5/29 00:33
* 作者:菜籽
* 备注:Paging3的数据适配器,需要实现DiffUtil方法
*/
class AdapterPaging3 : PagingDataAdapter(itemComparator) {
companion object {
val itemComparator = object : DiffUtil.ItemCallback() {
/**
* 用来判断新旧条目是否一样,确定是否需要刷新
* 只有当此方法返回true时,才会执行下面的方法
* 如果此方法返回false,则下面的方法不会执行
* 举个例子:当前item的布局没有发生变化,只是布局里面的数据发生了变化,
* 比如字符串从AAA变成了BBB,则这里返回true,表示不需要重绘当前item的布局
*
*/
override fun areItemsTheSame(oldItem: ItemPaging3, newItem: ItemPaging3): Boolean {
return true
}
/**
* 用来确定是否是同一条目是否一样
* 注意与上面的方法区分
* 这里返回true时,只会刷新当前item布局中发生变化的那一部分UI,其余地方不需要动
* 如果这里返回false,则当前item不会刷新,显示内容也不会发生变化
*/
override fun areContentsTheSame(oldItem: ItemPaging3, newItem: ItemPaging3): Boolean {
return !TextUtils.equals(oldItem.title, newItem.title)
}
}
}
class ViewHolder(view: View) : RecyclerView.ViewHolder(view)
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.itemView.tv_title.text = getItem(position)?.title
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.layout_item_paging3, parent, false)
return ViewHolder(view)
}
}
val pagingConfig = PagingConfig(
/**
* 每页显示的数据数量,
* 注意这个页,并不等同于我们接口中定义的条数
* 这个数量的意思是,每一个页面会加载这么多数据
* 举个例子,如果接口每页返回10条,而这里定义了20条,则Paging3会请求两次接口,
* 用来拼成这20条数据
*/
pageSize = 60,
// 开启占位符,在加载到正确数据之前,显示的item布局
enablePlaceholders = true,
// 预刷新的距离,距离最后一个 item 多远时加载数据
prefetchDistance = 3,
/**
* 初始化加载数量,默认为 pageSize * 3
*
* internal const val DEFAULT_INITIAL_PAGE_MULTIPLIER = 3
* val initialLoadSize: Int = pageSize * DEFAULT_INITIAL_PAGE_MULTIPLIER
*/
initialLoadSize = 60,
/**
* 一次应在内存中保存的最大数据
* 超出这个数字的数据会被销毁,当页面滑回来时,会重新请求此页面的数据,
* 滑动加载更多的数据
*/
maxSize = 200
)
/**
* 时间:2022/5/29 00:35
* 作者:菜籽
* 备注:使用viewModel+协程的形式来请求数据
*/
class Paging3Coroutines {
private val repository by lazy {
val config = PagingConfig(pageSize = 20, initialLoadSize = 5, maxSize = 150)
val sourceFactory = object : PagingSource() {
/**
* 官方解释:每当paging想要加载新的数据来代替当前列表时,会发生刷新操作,回调到这个方法
*
* 使用场景:
* 比如说你当前是第三页,然后用户此时回到第二页时,数据发生变化了,不再前面加载好的数据了,
* 此时就可以在这里返回第二页的索引,它会重新请求第二页的内容用来展示
*
* 目前还没有遇到过这种需求
*/
override fun getRefreshKey(state: PagingState): Int? {
return null
}
/**
* 我这里定义的为每页显示10条数据
* 模拟网络加载失败的情况
*/
override suspend fun load(params: LoadParams): LoadResult {
val currentPage = params.key ?: 0
Log.d("ItemDataSource", "currentPage:$currentPage")
if (currentPage > 0) {
val random = Math.random()
delay((random * 3000).toLong())
}
val list = mutableListOf()
for (i in 0..9) {
list.add(ItemPaging3("第" + (10 * currentPage + i) + "条"))
}
val prevKey = if (currentPage == 0) null else currentPage - 1
val nextKey = currentPage + 1
if (!NetStateHelper.isConnect) {
return LoadResult.Error(ConnectException())
}
if (currentPage == 4) {
// nextKey为null表示数据加载到头了
return LoadResult.Page(list, prevKey, null)
}
return LoadResult.Page(list, prevKey, nextKey)
}
}
Pager(config, pagingSourceFactory = { sourceFactory })
}
fun getData() = repository.flow.asLiveData()
}
Paging3支持设置header和footer用来显示当前的加载状态,我们需要声明一个adapter实现LoadStateAdapter来显示加载状态,具体代码如下:
/**
* 时间:2022/5/28 23:32
* 作者:菜籽
* 备注:网络状态的适配器
*/
class LoadStateAdapterPaging3 : LoadStateAdapter() {
class LoadStateViewHolder(view: View) : RecyclerView.ViewHolder(view)
private var listener: (() -> Unit)? = null
fun setOnReloadClickListener(listener: () -> Unit) {
this.listener = listener
}
/**
* loadState 有三种状态
*
*/
override fun onBindViewHolder(holder: LoadStateViewHolder, loadState: LoadState) {
Log.d("ItemDataSource", "loadState:$loadState")
holder.itemView.progress_bar.visibility = View.VISIBLE
holder.itemView.tv_state.text = "正在加载中..."
if (loadState is LoadState.NotLoading) {
if (loadState.endOfPaginationReached) {
holder.itemView.progress_bar.visibility = View.GONE
holder.itemView.tv_state.text = "数据加载完毕"
} else {
holder.itemView.tv_state.text = "滑到头了,继续加载"
}
return
}
if (loadState is LoadState.Error) {
holder.itemView.tv_state.text = "加载失败,点击重试"
holder.itemView.progress_bar.visibility = View.GONE
holder.itemView.setOnClickListener {
listener?.invoke()
}
return
}
}
override fun onCreateViewHolder(parent: ViewGroup, loadState: LoadState): LoadStateViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.load_state_item_paging3, parent, false)
return LoadStateViewHolder(view)
}
/**
* 是否显示当前的加载状态
* 默认的是只有 loading中,loading失败时才会显示加载状态
* 我们可以改掉super的代码,添加一条当加载完成时,也显示加载状态
*/
override fun displayLoadStateAsItem(loadState: LoadState): Boolean {
//return super.displayLoadStateAsItem(loadState)
return loadState is LoadState.Loading || loadState is LoadState.Error || (loadState is LoadState.NotLoading && loadState.endOfPaginationReached)
}
}
//通过融合两个adapter实现数据适配器与页面加载适配器融合
adapter.withLoadStateFooter(loadAdapter)
/**
* 时间:2022/5/29 00:39
* 作者:菜籽
* 备注:Paging3与RxJava结合使用
*/
class Paging3RxJava {
private val repository by lazy {
val config = PagingConfig(pageSize = 20, initialLoadSize = 5, maxSize = 150)
val sourceFactory = object : RxPagingSource() {
override fun getRefreshKey(state: PagingState): Int? {
return null
}
override fun loadSingle(params: LoadParams): Single> {
val currentPage = params.key ?: 0
val delay = if (currentPage > 0) {
val random = Math.random()
random * 3000
} else {
0f
}.toLong()
return Single.just("")
.delay(delay, TimeUnit.MILLISECONDS)
.map {
val list = mutableListOf()
for (i in 0..9) {
list.add(ItemPaging3("第" + (10 * currentPage + i) + "条"))
}
val prevKey = if (currentPage == 0) null else currentPage - 1
val nextKey = currentPage + 1
if (!NetStateHelper.isConnect) {
return@map LoadResult.Error(ConnectException())
}
if (currentPage == 4) {
// nextKey为null表示数据加载到头了
return@map LoadResult.Page(list, prevKey, null)
}
return@map LoadResult.Page(list, prevKey, nextKey)
}
}
}
Pager(config, pagingSourceFactory = { sourceFactory })
}
fun getData() = repository.flow.asLiveData()
}