Android--paging3的使用记录

250 阅读4分钟

基础使用

paging3

paging3主要是用来简化分页请求的。主要分为三个部分

pagingDataAdapter

UI层

recyclerView使用的adapter,是recyclerView的子类,当作一种特殊的adapter使用

pageSource:

数据层:

数据源。定义分页加载规则和进行数据加载,核心方法load返回结果LoadResult,是对请求结果的封装,内部要封装data,上一页页码,下一页页码。

只有上一页页码和下一页页码存在,才能触发它的自动分页加载。它才能确定第一页和最后一页

load函数

读取参数params【由库封装传递的】,获取分页参数,进行网络请求 将结果封装为LoadResult.page或者LoadResult.error进行返回

getRefreshKey函数

当前数据更新时,要重新加载数据的位置,保证列表不随便滚动

如果返回null,那么就会回到列表顶部

所以要返回的位置应该是当前页码

override fun getRefreshKey(state: PagingState<Int, Article>): Int? {
        // 1. 获取锚点位置【当前可见列表位置,比如第70条】
        val anchorPosition = state.anchorPosition ?: return null

        // 2. 根据锚点找到最近的加载页(假设每页20条)【那么就是第4页】
        val closestPage = state.closestPageToPosition(anchorPosition)
        // 如果最近的加载页有上一页,就返回的是上一页的页码+1,也就是当前页
        // 如果最近的加载页没有上一页,看有没有下一页,有的话,就返回下一页的页码-1,还是当前页
        // 主要是避免数据更新后,页面变化带来的问题
        val currentPage = closestPage?.prevKey?.plus(1) ?: closestPage?.nextKey?.minus(1)

        // 3. 返回当前页的页码(例如第4页)
        return currentPage
}
return closestPage?.prevKey?.plus(1) ?: closestPage?.nextKey?.minus(1)

不是直接返回当前的页面,而是用上一页,下一页计算,来推断当前页面

主要是为了避免在数据更新后,导致页码变化的问题。比如增加或者删除了很多数据。

因为paging3库会根据数据更新,自动动态调整pageData的prevKey和nextKey

pager

中间连接数据和ui的桥梁

配置分页config和数据源

pager构建的时候,需要配置PagingConfig,指定pageSize,预加载等 还要配置数据源。这样需要加载的时候,会调用指定数据源的load方法,将拿到的数据进行处理

它还可以扩展为Flow或者LivdeData使用。

扩展为livedata的时候,内部是调用了pager的扩展函数,调用了flow.asLiveData

val <Key : Any, Value : Any> Pager<Key, Value>.liveData: LiveData<PagingData<Value>>
    get() = flow.asLiveData()
    

paging3是如何实现自动分页加载的

Paging3 的滑动监听与分页加载逻辑由 PagerPagingConfig 控制,而非直接编码在 PagingDataAdapter 的源码中。其核心流程如下:

  • PagingConfig.prefetchDistance:该参数定义了预加载的触发阈值。当用户滚动到列表末尾前 prefetchDistance 项时,自动触发下一页加载(例如默认值为 pageSize,即滚动到倒数第 pageSize 项时加载下一页) 。此时paging3库中会根据配置生成下一页的请求数据,作为参数params传递给pagingSource的load方法,进行下一页的数据请求

  • LayoutManager 的可见项计算:Paging 库通过 RecyclerView 的 LayoutManager(如 LinearLayoutManager)实时计算当前可见项的位置,结合 prefetchDistance 判断是否需要加载新数据

paging3配合ViewModel使用

一般在viewModel中获取数据 所以我在viewModel中进行了pager的扩展。这样,变量就可以直接在页面中使用了

// paging3数据+flow
val userPagerFlow: Flow<PagingData<User>> = MyPager().userPager.flow.cachedIn(viewModelScope)
// paging3数据+livedata
val userPagerLiveData = MyPager().userPager.liveData.cachedIn(viewModelScope)

页面使用注意点。Flow模式的数据,要在协程中使用adapter.submit

// 请求用户列表【livedata的形式可以直接使用,但是submit传参需要多传递lifecycle】
// livedata被观察时,自动触发初始加载
private fun requestUserListFromLiveData() {
    viewModel.userPagerLiveData.observe(this) {
        adapter.submitData(lifecycle, it)
    }
}

// 请求用户列表【flow的形式,需要在协程中进行请求】
private fun requestUserListFromFlow() {
    lifecycleScope.launch(Dispatchers.IO) {
        viewModel.userPagerFlow.collectLatest { pagingData: PagingData<User> ->
            adapter.submitData(pagingData)
        }
    }
}

测试的时候的问题

因为测试的时候,是直接写了一个循环,模拟生成数据,造成recyclerView滑动加载下一页的时候,报错

Cannot call this method while RecyclerView is computing a layout or scrolling androidx.recyclerview.widget.RecyclerView 

简单理解就是recyclerview在滑动或者正在布局的时候,触发了更新数据的操作。就会崩溃。

这里的原因就是,模拟生成数据很快,在recyclerview还在滑动的时候,数据生成,进行数据更新。触发错误

解决方式就是模拟网络请求数据的方法中,延迟了100ms