一.什么是paging Jetpack Paging提供了列表中分页数据加载的解决方案,已经被广泛熟知和使用,目前这个库升级到了3.0版本。Paging3 基于Kotlin协程进行了重写,兼容Flow、Rxjava、LiveData等多种API方式。
每一页的数据会缓存至内存中,以此保证处理分页数据时更有效的使用系统资源
内置请求重复数据删除功能,确保应用有效地使用网络带宽和系统资源
支持Kotlin协程、Flow、LiveData以及RxJava
内置错误处理支持,如刷新和重试功能。
二.引入依赖 1 2 3 4 //java implementation 'androidx.paging:paging-runtime:3.0.0-alpha07' //kotlin implementation 'androidx.paging:paging-runtime-ktx:3.0.0-alpha07'
三.初步使用 1. 配置数据源 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class ArticleDataSource : PagingSource<Int, DemoReqData.DataBean.DatasBean>() { override suspend fun load(params: LoadParams<Int>): LoadResult<Int, DemoReqData.DataBean.DatasBean> { return try { //param.key页码未定义置为1 默认加载第1页数据。 var currentPage = params.key ?: 1 //仓库层请求数据 Log.d("PagingSource", "请求第${currentPage}页") var demoReqData = RetrofitService.api.getArticleListData(currentPage) //数据获取成功返回LoadResult数据 LoadResult.Page( //需要加载的数据 data = demoReqData.data.datas, //如果可以往上加载更多就设置该参数,否则不设置 prevKey = null, //加载下一页的key 如果传null就说明到底了 nextKey = if (currentPage < demoReqData.data?.pageCount ?: 0) { //当前页码 小于 总页码 页面加1 currentPage + 1 } else { null } ) } catch (e: Exception) { Log.d("PagingSource", "----error--->${e.message}") LoadResult.Error(throwable = e) } } }
2.生成可观察的数据集 观察数据集包括 LiveData 、Flow 以及 RxJava 中的 Observable 和 Flowable,其中,RxJava 需要单独引入扩展库去支持的。
1 2 3 4 5 class PagingViewModel : ViewModel() { fun getArticleData(): Flow<PagingData<DemoReqData.DataBean.DatasBean>> = Pager(PagingConfig(pageSize = 1)) { ArticleDataSource() }.flow }
PagerConfig 可以提供分页的参数,比如每一页的加载数、初始加载数和最大数等。
pagingSourceFactory 和 remoteMediator 都是数据源,我们使用其中的一个即可。
PagingData是数据源和RecyclerView Adapter之间的一个桥梁,每一页数据就是一个PagingData首先创建Flow<PagingData>,可以通过Pager().flow()去实现 ,可以传入PagingConfig到Pager中去设置一些分页的参数,比如:
Flow有一个方便的cachedIn()方法,使我们可以将内容缓存Flow在CoroutineScope中,如果我们放在viewmodel中, 这样我们就可以在配置被更改之后,activity能够接收到原有的数据而不用重新开始获取,如果你要在flow上用map等操作符的时候,要确保cachedIn()是在最后面的, 如果没有缓存,那么在切换横竖屏的时候就会重新开始请求.
3.创建Adapter 用Paging库时需要recyclerview的adapter继承PagingDataAdapter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 // 首先构造方法里需要传入diffutil的callback class PagingWanAdapter : PagingDataAdapter<ArticleDataListData.DataBean.DatasBean, PagingWanAdapter.CustomViewHolder>(ArticleDiffCallback()) { override fun onBindViewHolder(holder: CustomViewHolder, position: Int) { val dataBean = getItem(position) holder.txtInfo.text = dataBean?.desc } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomViewHolder { val view = LayoutInflater.from(parent.context).inflate(R.layout.test_item_paging, parent, false) return CustomViewHolder(view) } class CustomViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val txtInfo: TextView = itemView.findViewById(R.id.txt_view) } } //这是判断item是否需要更新的部分 class ArticleDiffCallback : DiffUtil.ItemCallback<ArticleDataListData.DataBean.DatasBean>() { override fun areItemsTheSame(oldItem: ArticleDataListData.DataBean.DatasBean, newItem: ArticleDataListData.DataBean.DatasBean): Boolean { return oldItem.id == newItem.id } @SuppressLint("DiffUtilEquals") override fun areContentsTheSame(oldItem: ArticleDataListData.DataBean.DatasBean, newItem: ArticleDataListData.DataBean.DatasBean): Boolean { return oldItem == newItem } }
paging3的设计理念是不建议对列表数据直接修改;而是对数据源进行操作,数据源的变化会自动更新到列表; DiffUtil.ItemCallback 就是用来比对数据变化,从而决定更新对应UI;并执行条目动画;
areItemsTheSame 比对新旧条目是否是同一个条目; 一般比对条目的唯一标示id即可,谨慎对待,如果条目不同则可能不会更新UI;
areContentsTheSame 当上面的方法确定是同一个条目之后,这里比对条目的内容是否一样,不一样则会更新条目UI 建议这里的比对把UI展示的数据都写上,写漏了会导致UI不更新对应字段;
getChangePayload (可选) 用于条目内部的局部刷新;
4.在界面中使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class PagingHomeMainActivity : AppCompatActivity(), CoroutineScope by MainScope() { private val btnLoadData by lazy { findViewById<Button>(R.id.btn_load_data) } private val rlvData by lazy { findViewById<RecyclerView>(R.id.rlv_data) } private val mPagingWanAdapter: PagingWanAdapter by lazy { PagingWanAdapter() } private val mPagingViewModel: PagingViewModel by lazy { ViewModelProvider(this).get(PagingViewModel::class.java) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.test_activity_paging_home_main) rlvData.adapter = mPagingWanAdapter btnLoadData.setOnClickListener { launch { // 在activity中订阅pager的数据流,并且通过submitData方法把数据传给adapter mPagingViewModel?.getArticleData()?.collectLatest { // mPagingData=it // 把数据转化成itemview然后让adapter刷新ui mPagingWanAdapter.submitData(it) } } } } override fun onDestroy() { super.onDestroy() cancel() } }
四.注意事项 1.调整预取阈值 构建Pager时,使用到PagingConfig,其中有一个属性值为prefetchDistance,用于表示距离底部多少条数据开始预加载,设置0则表示滑到底部才加载。默认值为分页大小。 若要让用户对加载无感,适当增加预取阈值即可。比如调整到分页大小的5倍。
1 2 3 4 5 class PagingViewModel : ViewModel() { fun getArticleData() = Pager(PagingConfig(pageSize = 10, prefetchDistance = 50)) { ArticleDataSource() }.flow.cachedIn(viewModelScope) }
2.Paging的加载状态 想要监听数据获取的状态在PagingDataAdapter里有两个方法
上面我们在Activity中创建了dataRecycleViewAdapter来显示页面数据,我们可以使用addLoadStateListener方法添加加载状态的监听事件,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 mPagingWanAdapter.addLoadStateListener { when (it.refresh) { is LoadState.NotLoading -> { Log.d(TAG, "is NotLoading") } is LoadState.Loading -> { Log.d(TAG, "is Loading") } is LoadState.Error -> { Log.d(TAG, "is Error") } } }
状态返回的参数 CombinedLoadStates,包含了
refresh:刷新(在初始化刷新的使用)
prepend:向前加载更多
append:向后加载更多(在加载更多的时候使用)
source:数据源
mediator:调解员 每个行为分为3中状态:
LoadState.Loading 加载中 (加载数据时候回调)
LoadState.NotLoading 没有加载中 (加载数据前和加载数据完成后回调)
LoadState.Error 加载失败 (加载数据失败回调)
也就是说如果监测的是it.refresh,当加载第二页第三页的时候,状态是监听不到的,这里只以it.refresh为例。
监听方式除了addLoadStateListener外,还可以直接使用loadStateFlow的方式,由于flow内部是一个挂起函数 所以我们需要在协程中执行(,代码如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 lifecycleScope.launch { mPagingWanAdapter.loadStateFlow.collectLatest { when (it.refresh) { is LoadState.NotLoading -> { } is LoadState.Loading -> { } is LoadState.Error -> { } } } }
3.刷新和重试 (1.) 使用第三方刷新库 在下拉刷新控制的下拉监听中直接调用adapter.refresh()方法就可以完成刷新了,那什么时候关闭刷新动画呢,需要调用adapter.loadStateFlow.collectLatest方法来监听
1 2 3 4 5 6 7 8 lifecycleScope.launchWhenCreated { @OptIn(ExperimentalCoroutinesApi::class) adapter.loadStateFlow.collectLatest { if(it.refresh !is LoadState.Loading){ refreshView.finishRefresh() } } }
收集流的状态,如果是不是Loading状态的说明加载完成了,可以关闭动画了。
(2.)使用PagingDataAdapter添加刷新和加载更多
方法
使用
fun withLoadStateHeader(header: LoadStateAdapter<*> )
头部添加状态适配器
fun withLoadStateFooter(footer: LoadStateAdapter<*> )
底部添加状态适配器
fun withLoadStateHeaderAndFooter(header: LoadStateAdapter<*>, footer: LoadStateAdapter<*> )
头尾都添加状态适配器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 class ArticleLoadStateAdapter(val retrycallback: () -> Unit) : LoadStateAdapter<ArticleLoadStateAdapter.ArticleLoadStateViewHolder>() { class ArticleLoadStateViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val pbLoading = itemView.findViewById<ProgressBar>(R.id.pb_loading) val btnerror = itemView.findViewById<Button>(R.id.btn_error) val txtEnd = itemView.findViewById<TextView>(R.id.txt_end) } override fun onBindViewHolder(holder: ArticleLoadStateViewHolder, loadState: LoadState) { when (loadState) { is LoadState.Error -> { LogTest.d("LoadState.Error") holder.pbLoading.visibility = View.GONE holder.btnerror.visibility = View.VISIBLE holder.txtEnd.visibility = View.GONE holder.btnerror.setOnClickListener { retrycallback() } } is LoadState.Loading -> { LogTest.d("LoadState.Loading") holder.pbLoading.visibility = View.VISIBLE holder.btnerror.visibility = View.GONE holder.txtEnd.visibility = View.GONE } is LoadState.NotLoading -> { LogTest.d("LoadState.NotLoading") holder.pbLoading.visibility = View.GONE holder.btnerror.visibility = View.GONE holder.txtEnd.visibility = View.VISIBLE } } } override fun onCreateViewHolder(parent: ViewGroup, loadState: LoadState): ArticleLoadStateViewHolder { val rootView = LayoutInflater.from(parent.context).inflate(R.layout.test_item_loadstate, parent, false) return ArticleLoadStateViewHolder(rootView) } // override fun displayLoadStateAsItem(loadState: LoadState): Boolean { // // return super.displayLoadStateAsItem(loadState) // return true // } }
参考资料:Android Paging3 LoadStateAdapter显示已加载完所有数据