孟老板 Paging3 (一) 入门
阅读原文时间:2021年06月23日阅读:1

前言:

官方分页工具,  确实香.   但数据源不开放, 无法随意增删改操作;  只能借助 Room;  但列表数据不一定都要用 Room吧;

如果偏查询的分页数据用 Paging3 ;  其他一概用 老Adapter;  这倒也算个方案. [苦笑]

目录:

  1. 简单使用  -  数据源,Viewmodel,Adapter 等
  2. LoadResult  -  Error, Page.  Error 用法等
  3. PagingConfig
  4. 监听列表加载状态
  5. LoadStateAdapter  -  loading, 加载失败, 没有更多等
  6. Map  -  数据预处理

官方 Pagings 优势:

  • 分页数据的内存中缓存。该功能可确保您的应用在处理分页数据时高效利用系统资源。
  • 内置的请求重复信息删除功能,可确保您的应用高效利用网络带宽和系统资源。
  • 可配置的 RecyclerView 适配器,会在用户滚动到已加载数据的末尾时自动请求数据。
  • 对 Kotlin 协程和 Flow 以及 LiveData 和 RxJava 的一流支持。
  • 内置对错误处理功能的支持,包括刷新和重试功能。

导包:

dependencies {
val paging_version = "3.0.0"

//唯一必导包
implementation("androidx.paging:paging-runtime:$paging_version")

// 测试用
testImplementation("androidx.paging:paging-common:$paging_version")

// optional - RxJava2 support
implementation("androidx.paging:paging-rxjava2:$paging_version")

// optional - RxJava3 support
implementation("androidx.paging:paging-rxjava3:$paging_version")

// 适配 Guava 库 - 高效java扩展库
implementation("androidx.paging:paging-guava:$paging_version")

// 适配 Jetpack Compose - 代码构建View; 干掉 layout
implementation("androidx.paging:paging-compose:1.0.0-alpha09")
}

1. 简单使用:

1.1 数据源  PagingSource

自定义数据源, 继承 PagingSource

它有两个泛型参数,  1. 页码key,  没有特殊需求的话一般就是 Int 类型;  2.集合实体类型

重写两个方法:  1.load()  加载数据的方法;   2.getRefreshKey  初始加载的页码;  暂且返回 1 或 null

LoadResult.Page 后面再讲;

class DynamicDataSource: PagingSource() {

//模拟最大页码  
private var maxPage = 2

//模拟数据  
private fun fetchItems(startPosition: Int, pageSize: Int): MutableList<DynamicTwo> {  
    Log.d("ppppppppppppppppppppp", "startPosition=${startPosition};;;pageSize=${pageSize}")  
    val list: MutableList<DynamicTwo> = ArrayList()  
    for (i in startPosition until startPosition + pageSize) {  
        val concert = DynamicTwo()  
        concert.title = "我是标题${i}"  
        concert.newsInfo = "我是内容${i}"  
        concert.nickName = "小王${i}"  
        list.add(concert)  
    }  
    return list  
}

override fun getRefreshKey(state: PagingState<Int, DynamicTwo>): Int? = null

override suspend fun load(params: LoadParams<Int>): LoadResult<Int, DynamicTwo> {  
    val nextPageNumber = params.key ?: 1  
    val size = params.loadSize  
    Log.d("ppppppppppppppppppppp", "nextPageNumber=${nextPageNumber};;;size=${size}")  
    val response = fetchItems((nextPageNumber-1) \* size, size)

    return LoadResult.Page(  
        data = response,  
        prevKey = null, // Only paging forward.  只向后加载就给 null  
        //nextKey 下一页页码;  尾页给 null;  否则当前页码加1  
        nextKey = if(nextPageNumber >= maxPage) null else (nextPageNumber + 1)  
    )  
}  

}

1.2 ViewModel

代码比较简单.  内容我们一会再讲

class DynamicPagingModel(application: Application) : AndroidViewModel(application) {
val flow = Pager(
//配置
PagingConfig(pageSize = 10, prefetchDistance = 2,initialLoadSize = 10)
) {
//我们自定义的数据源
DynamicDataSource()
}.flow
.cachedIn(viewModelScope)
}

1.3 前台使用:

初始化 Adapter 及 RecycleView

mViewModel?.flow?.collectLatest  绑定监听,  然后通过 submitData() 刷新列表;

mAdapter = SimplePagingAdapter(R.layout.item_dynamic_img_two, null)

mDataBind.rvRecycle.let {
it.layoutManager = LinearLayoutManager(mActivity)
it.adapter = mAdapter
}

//Activity 用 lifecycleScope
//Fragments 用 viewLifecycleOwner.lifecycleScope
viewLifecycleOwner.lifecycleScope.launchWhenCreated {
mViewModel?.flow?.collectLatest {
mAdapter.submitData(it)
}
}

1.4 Adapter

必须继承  paging 的 PagingDataAdapter

DiffCallback() 或 handler  NewViewHolder 不了解的可以看我的 ListAdapter 封装系列

open class SimplePagingAdapter(
private val layout: Int,
protected val handler: BaseHandler? = null
) :
PagingDataAdapter(DiffCallback()) {

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {  
    return NewViewHolder(  
        DataBindingUtil.inflate(  
            LayoutInflater.from(parent.context), layout, parent, false  
        ), handler  
    )  
}

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {  
    if(holder is NewViewHolder){  
        holder.bind(getItem(position))  
    }  
}  

}

over  简单的分页模拟数据已完成;

2. LoadResult

它是一个密封类;   它表示加载操作的结果;

2.1 LoadResult.Error

表示加载失败;  需提供 Throwable 对象.

public data class Error(
val throwable: Throwable
) : LoadResult()

可用于:

  • 异常时返回,  HTTP, IO, 数据解析等异常;
  • 服务器错误码响应
  • 没有更多数据

2.1 LoadResult.Page

表示加载成功;

参数:

data 数据集合;

prevKey 前页页码 key;   //向下一页加载 给null

nextKey 后页页码 key;    //向上一页加载 给null

public data class Page constructor(
/**
* Loaded data
*/
val data: List,
/**
* [Key] for previous page if more data can be loaded in that direction, `null`
* otherwise.
*/
val prevKey: Key?,
/**
* [Key] for next page if more data can be loaded in that direction, `null` otherwise.
*/
val nextKey: Key?,
/**
* Optional count of items before the loaded data.
*/
@IntRange(from = COUNT_UNDEFINED.toLong())
val itemsBefore: Int = COUNT_UNDEFINED,
/**
* Optional count of items after the loaded data.
*/
@IntRange(from = COUNT_UNDEFINED.toLong())
val itemsAfter: Int = COUNT_UNDEFINED
) : LoadResult() {

3.PagingConfig

分页配置

参数:

pageSize:  每页容量

prefetchDistance:  当RecycleView 滑动到底部时, 会自动加载下一页.   如果能提前预加载, 可以省去部分等待加载的时间.

        prefetchDistance 就是距离底部提前加载的距离.  默认 = pageSize;   = 0 时将不会加载更多

enablePlaceholders:  允许使用占位符.  想了解的点这里

initialLoadSize: 初始加载数量,  默认 = pageSize * 3

maxSize:   似乎意义没有那么简单.  还没看源码,不清楚;  不能 < pageSize + prefetchDistance * 2****

jumpThreshold: 某阈值!  好吧我摊牌了, 我不知道. [奸笑]

4.监听加载状态:

LoadState:  表示加载状态密封类;

LoadState.NotLoading:  加载完毕,  并且界面也已相应更新

LoadState.Error: 加载失败.

LoadState.Loading:  正在加载..

lifecycleScope.launch {
mAdapter.loadStateFlow.collectLatest { loadStates ->
when(loadStates.refresh){
is LoadState.Loading -> {
Log.d("pppppppppppppp", "加载中")
}
is LoadState.Error -> {
Log.d("pppppppppppppp", "加载失败")
}
is LoadState.NotLoading -> {
Log.d("pppppppppppppp", "完事了")
}
else -> {
Log.d("pppppppppppppp", "这是啥啊")
}
}
}

//或者:  
mAdapter.addLoadStateListener { ... }  

}

5. 状态适配器  LoadStateAdapter

用于直接在显示的分页数据列表中呈现加载状态。 例如:  尾部显示 正在加载, 加载失败, 没有更多等;

5.1 自定义 MyLoadStateAdapter  继承 LoadStateAdapter

重写 onCreateViewHolder,  onBindViewHolder

retry:  如果加载失败, 想要重试,  则提供该高阶函数参数;  否则不需要它

class MyLoadStateAdapter(
/**
* 当下一页加载失败时, 继续尝试加载下一页;
*/
private val retry: () -> Unit
) : LoadStateAdapter() {

override fun onCreateViewHolder(  
    parent: ViewGroup,  
    loadState: LoadState  
) = LoadStateViewHolder(parent, retry)

override fun onBindViewHolder(  
    holder: LoadStateViewHolder,  
    loadState: LoadState  
) = holder.bind(loadState)  

}

5.2 自定义 LoadStateViewHolder

功能:

  • 加载中 显示 Loading;
  • 加载失败  显示 错误信息.    包括 http, IO 异常,  后台给的错误 msg 等;
  • 没有更多

class LoadStateViewHolder (
parent: ViewGroup,
retry: () -> Unit
) : RecyclerView.ViewHolder(
LayoutInflater.from(parent.context)
.inflate(R.layout.view_loading_more, parent, false)
) {
private val binding = ViewLoadingMoreBinding.bind(itemView)

init {  
    //当点击重试按钮时, 调用 PagingDataAdapter 的 retry() 重新尝试加载  
    binding.btnLoadingRetry.setOnClickListener {  
        retry()  
    }  
}

fun bind(loadState: LoadState) {  
    // 当加载失败时.  
    if(loadState is LoadState.Error){  
        // 将没有更多封装成 NoMoreException;  此时显示没有更多 View  
        if(loadState.error is NoMoreException){  
            hideNoMoreUi(false) //显示 没有更多 View  
            hideErrUi(true)     //隐藏 失败 View  
        }else{  
            hideNoMoreUi(true)  
            hideErrUi(false, loadState.error.message)   //显示失败 View时, 填充错误 msg  
        }  
    }else{  
        hideNoMoreUi(true)  
        hideErrUi(true)  
    }

    //加载中..  
    binding.pbLoadingBar.visibility = if(loadState is LoadState.Loading){  
        View.VISIBLE  
    }else{  
        View.GONE  
    }  
}

/\*\*  
 \* 隐藏没有更多View;  
 \*/  
private fun hideNoMoreUi(hide: Boolean){  
    if(hide){  
        binding.tvLoadingHint.visibility = View.GONE  
    }else{  
        binding.tvLoadingHint.visibility = View.VISIBLE  
    }  
}

/\*\*  
 \* 隐藏 加载失败View;  
 \*/  
private fun hideErrUi(hide: Boolean, msg: String? = null){  
    if(hide){  
        binding.tvLoadingError.visibility = View.GONE  
        binding.btnLoadingRetry.visibility = View.GONE  
    }else{  
        binding.tvLoadingError.text = msg  
        binding.tvLoadingError.visibility = View.VISIBLE  
        binding.btnLoadingRetry.visibility = View.VISIBLE  
    }  
}  

}

顺便补一下  NoMoreException;  用法? 在下面 PagingSource 喽.

class NoMoreException: RuntimeException()

5.3 layout  view_loading_more.xml

包含:   TextView: 没有更多;    ProgressBar: 加载中;   TextView: 错误信息;   Button: 重试按钮

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章