一、Paging3介绍
Paging3是jetpack推出的一个分页加载库,用于方便开发者实现分页加载功能,支持显示加载状态,重试机制,支持协程与RxJava结合使用,相对于传统的分页加载方案,我们不需要关注recyclerview的滑动状态,然后根据状态去实时请求接口,所有相关的判断逻辑,Paging3已经在内部为我们做好了实现,我们只需要实现Paging3的提供的抽象方法,即可实现分页加载功能
二、Paging3的依赖添加
// Paging3默认使用协程,如果不需要使用RxJava,则只引入这一个依赖即可
implementation 'androidx.paging:paging-runtime:3.1.1'// 如果需要使用RxJava,则需要在引入这个依赖
implementation "androidx.paging:paging-rxjava2:3.1.1"
三、Paging3的使用
Paging3的使用需要关注三个类:
1、PagingDataAdapter
使用方法与RecyclerView的adapter几乎一致,需要注意的是,PagingDataAdapter默认添加了DiffUtil,需要我们手动实现DiffUtil的对比方法
2、Pager
Paging3提供的封装工具,提供配置分页参数,数据请求形式,页面加载逻辑等配置操作
3、SourceFactory
我们需要实现此类,用于实现数据的请求与加载逻辑
/*** 时间:2022/5/29 00:33 * 作者:菜籽* 备注:Paging3的数据适配器,需要实现DiffUtil方法*/class AdapterPaging3 : PagingDataAdapter<ItemPaging3, AdapterPaging3.ViewHolder>(itemComparator) {companion object {val itemComparator = object : DiffUtil.ItemCallback<ItemPaging3>() {/*** 用来判断新旧条目是否一样,确定是否需要刷新* 只有当此方法返回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<Int, ItemPaging3>() {/*** 官方解释:每当paging想要加载新的数据来代替当前列表时,会发生刷新操作,回调到这个方法** 使用场景:* 比如说你当前是第三页,然后用户此时回到第二页时,数据发生变化了,不再前面加载好的数据了,* 此时就可以在这里返回第二页的索引,它会重新请求第二页的内容用来展示** 目前还没有遇到过这种需求*/override fun getRefreshKey(state: PagingState<Int, ItemPaging3>): Int? {return null}/*** 我这里定义的为每页显示10条数据* 模拟网络加载失败的情况*/override suspend fun load(params: LoadParams<Int>): LoadResult<Int, ItemPaging3> {val currentPage = params.key ?: 0Log.d("ItemDataSource", "currentPage:$currentPage")if (currentPage > 0) {val random = Math.random()delay((random * 3000).toLong())}val list = mutableListOf<ItemPaging3>()for (i in 0..9) {list.add(ItemPaging3("第" + (10 * currentPage + i) + "条"))}val prevKey = if (currentPage == 0) null else currentPage - 1val nextKey = currentPage + 1if (!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()}
四、高级用法:
1、显示加载状态:
Paging3支持设置header和footer用来显示当前的加载状态,我们需要声明一个adapter实现LoadStateAdapter来显示加载状态,具体代码如下:
/*** 时间:2022/5/28 23:32* 作者:菜籽* 备注:网络状态的适配器*/class LoadStateAdapterPaging3 : LoadStateAdapter<LoadStateAdapterPaging3.LoadStateViewHolder>() {class LoadStateViewHolder(view: View) : RecyclerView.ViewHolder(view)private var listener: (() -> Unit)? = nullfun 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.VISIBLEholder.itemView.tv_state.text = "正在加载中..."if (loadState is LoadState.NotLoading) {if (loadState.endOfPaginationReached) {holder.itemView.progress_bar.visibility = View.GONEholder.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.GONEholder.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)}}
2、与数据展示相结合:
//通过融合两个adapter实现数据适配器与页面加载适配器融合
adapter.withLoadStateFooter(loadAdapter)
3、与RxJava相结合
/*** 时间: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<Int, ItemPaging3>() {override fun getRefreshKey(state: PagingState<Int, ItemPaging3>): Int? {return null}override fun loadSingle(params: LoadParams<Int>): Single<LoadResult<Int, ItemPaging3>> {val currentPage = params.key ?: 0val delay = if (currentPage > 0) {val random = Math.random()random * 3000} else {0f}.toLong()return Single.just("").delay(delay, TimeUnit.MILLISECONDS).map {val list = mutableListOf<ItemPaging3>()for (i in 0..9) {list.add(ItemPaging3("第" + (10 * currentPage + i) + "条"))}val prevKey = if (currentPage == 0) null else currentPage - 1val nextKey = currentPage + 1if (!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()}