JetPack 之 Pagging3.你可以不用,但是不能不会

JetPack 之 Pagging3

1.Pagging3是什么

Pagging3是帮助用来做加载和显示 本地存储或者处理更大的数据集(网络)的数据页面.

简单来讲,就是一个分页加载库.

具有的好处为:

1.只需要按照它提供的架构去编写业务逻辑,就可以轻松实现分页功能。

2.非监听列表滑动事件.

实现效果为:
在这里插入图片描述

2.Pagging3的依赖

def paging_version = "3.1.1"
//Java依赖
implementation "androidx.paging:paging-runtime:$paging_version"
//Kotlin依赖
implementation "androidx.paging:paging-runtime-ktx:$paging_version"

Paging 库概览 | Android 开发者 | Android Developers

网络的请求可参考以下博客:

Jetpack新成员,Paging3从吐槽到真香_guolin的博客-CSDN博客_paging

3.Pagging3的用法

主要包含以下内容:

  • 配置数据源:PagingSource
  • 构建分页数据:Pager、PagingData
  • 构建RecyclerView Adapter:PagingDataAdapter
  • 展示分页UI列表数据

这里我们选用自己的任意一个List作为目标数据

 class PaggingViewModel : ViewModel() {

 val lazyInitStateFlow bylazy{
var list:MutableList<String> = ArrayList()
        for(d in 0..60){list.add(d.toString())}
MutableStateFlow(list).also{
            it.value = list
}
}
}

配置数据源Source:

这里需要对代码进行解释,继承该PaggingSource需要声明两个范类,Int→key代表当前页数,String为每一项的数据.

可以看到

nextPageNumber 为我当前的页数

response为我每一页的数据

最后需要调用LoadResult.Page()函数,构建一个LoadResult对象并返回

data是你需要展示的数据

prevKey为上一页的页数

nextKey 就是下一页的页数

对于nextKey 的理解可以是,你去判断什么情况下可以有下一页,这一页的数据是什么.

//配置数据源.
// Key为分页标识 ,Value为返回列表元素类型
class ExamplePagingSource(
        val viewmodel: PaggingViewModel,
) : PagingSource<Int, String>() {
    val TAG = "ExamplePagingSource"
    var SplitLocation = 0

    override suspend fun load(
            params: LoadParams<Int>
    ): LoadResult<Int, String> {
        //如果加载成功,则返回 LoadResult.Page 对象。
        //如果加载失败,则返回 LoadResult.Error 对象。
        try {
            // Start refresh at page 1 if undefined.
            val nextPageNumber = params.key ?: 1
            val response = viewmodel.lazyInitStateFlow.value.subList(SplitLocation,SplitLocation+params.loadSize -1 )
            val ListX = viewmodel.lazyInitStateFlow.value
            SplitLocation = SplitLocation + params.loadSize
            return LoadResult.Page(
                    data =response,
                    prevKey = null, // Only paging forward.if(nextPageNumber >1) nextPageNumber -1 else null
                    nextKey = if (response.isNotEmpty()) nextPageNumber + 1 else null
            )
        } catch (e: Exception) {
            // Handle errors in this block and return LoadResult.Error if it is an
            // expected error (such as a network failure).
            return LoadResult.Error(e)
        }
    }
//    override fun getRefreshKey(state: PagingState<Int, String>): Int? = null

    override fun getRefreshKey(state: PagingState<Int, String>): Int? {
        // Try to find the page key of the closest page to anchorPosition, from
        // either the prevKey or the nextKey, but you need to handle nullability
        // here:
        //  * prevKey == null -> anchorPage is the first page.
        //  * nextKey == null -> anchorPage is the last page.
        //  * both prevKey and nextKey null -> anchorPage is the initial page, so
        //    just return null.
        return state.anchorPosition?.let{anchorPosition->
val anchorPage = state.closestPageToPosition(anchorPosition)
            anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1)
}
}
}

构建分页的数据Pagger,PagingData:

基本解释都在注释内了,可以自己稍微看一下

//作为分页数据
val AllData: Flow<PagingData<String>> = Pager(
        PagingConfig(
                // 每页显示的数据的大小。对应 PagingSource 里 LoadParams.loadSize
                pageSize = 10,

                // 预刷新的距离,距离最后一个 item 多远时加载数据,默认为 pageSize
                prefetchDistance = 3,

                // 初始化加载数量,默认为 pageSize * 3
                initialLoadSize = 20,

                // 一次应在内存中保存的最大数据,默认为 Int.MAX_VALUE
                maxSize = 200
        ),
){
// 数据源,要求返回的是 PagingSource 类型对象
    ExamplePagingSource(this)
}.flow.cachedIn(viewModelScope) // 最后构造的和外部交互对象,有 flow 和 liveData 两种

构建RecyclerView Adapter→PagingDataAdapter:

这里比较特殊的是,它必须继承于PagingDataAdapter,并且构造需要传入DiffUtil.

DiffUtil为计算刷新的工具类.

Android RecyclerView的正确打开方式–DiffUtil、AsyncListDiff以及最新ListAdapter使用_e电动小马达e的博客-CSDN博客

getItem(position)就是获取到设置的每一项中数据源是什么

// 构建RecyclerView Adapter
class PaggingAdapter : PagingDataAdapter<String, PaggingAdapter.PaggingViewHolder>(UserDiffCallback()) {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PaggingViewHolder {
        val binding = AdapterItemButtonBinding.inflate(LayoutInflater.from(parent.context),  parent, false)

        return PaggingViewHolder(binding)
    }

    override fun onBindViewHolder(holder: PaggingViewHolder, position: Int) {
//        Log.d("TAG", "onBindViewHolder: ${getItem(position)}")
        (holder.binding as AdapterItemButtonBinding).adapterItemButton.setText(getItem(position).toString())
        (holder.binding as AdapterItemButtonBinding).adapterItemButton.setOnClickListener{Log.d("TAG", "onBindViewHolder: 我是按钮RecyclView")}
}

    override fun getItemCount(): Int {
        return super.getItemCount()
    }

    class PaggingViewHolder(val binding: ViewBinding) : RecyclerView.ViewHolder(binding.root) {
        init {
        }
    }
}

//计算位置刷新的工具类
private class UserDiffCallback : DiffUtil.ItemCallback<String>() {
    // 返回两个item是否相同
    // 例如:此处两个item的数据实体是User类,所以以id作为两个item是否相同的依据
    // 即此处返回两个user的id是否相同
    override fun areItemsTheSame(oldItem: String, newItem: String): Boolean {
        return oldItem == newItem
    }

    // 当areItemsTheSame返回true时,我们还需要判断两个item的内容是否相同
    // 此处以User的age作为两个item内容是否相同的依据
    // 即返回两个user的age是否相同
    override fun areContentsTheSame(oldItem: String, newItem: String): Boolean {
        return oldItem == newItem
    }
}

展示分页UI列表数据

然后就简简单单的去设置RecycleView的adapter就可以了

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
bind.PaggingRecycleView.layoutManager = LinearLayoutManager(requireContext())
bind.PaggingRecycleView.adapter = PaggingAdapter
viewModel.viewModelScope.launch{
	viewModel.AllData.collectLatest{
	PaggingAdapter.submitData(it)
	}
}
}

4.Pagging3的扩展

  • 监听数据加载的状态
  • 设置Header和Footer,在底部显示加载状态

监听数据加载的状态:

如果想要监听数据获取的状态,可以使用该方法

addLoadStateListener:这个方法就是当新的PagingData被提交并且显示的回调

  • refresh:LoadState:刷新时的状态,因为可以调用 PagingDataAdapter#refresh() 方法进行数据刷新
  • append:LoadState:可以理解为 RecyclerView 向下滑时数据的请求状态。
  • prepend:LoadState:可以理解为RecyclerView 向上滑时数据的请求状态。
PaggingAdapter.addLoadStateListener{
when (it.append) {
        is LoadState.NotLoading -> {
            Log.d(TAG, "append: 未加载状态")}
        is LoadState.Loading -> {
            Log.d(TAG, "append: 正在加载")}
        is LoadState.Error -> {
            Log.d(TAG, "append: 加载失败")
            val state =it.append as LoadState.Error
            Toast.makeText(requireContext(), "Load Error: ${state.error.message}", Toast.LENGTH_SHORT).show()
        }
    }

设置Header和Footer,在底部显示加载状态

一般来讲,如果不是下拉再获取数据,而是提前加载好了更多的数据,是不会在底部看到加载状态的.因为Pagging会在还未滑到底部的时候去提前加载数据了.

但是网络总有不好的时候.此时应该在列表底部增加一个Loading和重试按钮

FooterAdapter作为RecyeleView的底部适配器,注意它需继承LoadStateAdapter

class FooterAdapter(val retry: () -> Unit) : LoadStateAdapter<PaggingAdapter.PaggingViewHolder>() {

    override fun onCreateViewHolder(parent: ViewGroup, loadState: LoadState): PaggingAdapter.PaggingViewHolder {
        val binding = PaggingAdapterFootLayoutBinding.inflate(LayoutInflater.from(parent.context),  parent, false)

        return PaggingAdapter.PaggingViewHolder(binding)

    }

    override fun onBindViewHolder(holder: PaggingAdapter.PaggingViewHolder, loadState: LoadState) {
        (holder.binding as PaggingAdapterFootLayoutBinding).retryButton.setOnClickListener{retry()}
holder.binding.progressBar.isVisible= loadState is LoadState.Loading
        holder.binding.retryButton.isVisible= loadState is LoadState.Error

    }

}

然后设置进去就可以了.

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
///....
bind.PaggingRecycleView.adapter = InternalAdapter.withLoadStateFooter(FooterAdapter { PaggingAdapter.retry() })
///....

}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值