Paging3的使用踩坑记录

一、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(itemComparator) {

    companion object {

        val itemComparator = object : DiffUtil.ItemCallback() {
            /**
             * 用来判断新旧条目是否一样,确定是否需要刷新
             * 只有当此方法返回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() {
            /**
             * 官方解释:每当paging想要加载新的数据来代替当前列表时,会发生刷新操作,回调到这个方法
             *
             * 使用场景:
             * 比如说你当前是第三页,然后用户此时回到第二页时,数据发生变化了,不再前面加载好的数据了,
             * 此时就可以在这里返回第二页的索引,它会重新请求第二页的内容用来展示
             *
             * 目前还没有遇到过这种需求
             */
            override fun getRefreshKey(state: PagingState): Int? {
                return null
            }

            /**
             * 我这里定义的为每页显示10条数据
             * 模拟网络加载失败的情况
             */
            override suspend fun load(params: LoadParams): LoadResult {
                val currentPage = params.key ?: 0
                Log.d("ItemDataSource", "currentPage:$currentPage")
                if (currentPage > 0) {
                    val random = Math.random()
                    delay((random * 3000).toLong())
                }
                val list = mutableListOf()
                for (i in 0..9) {
                    list.add(ItemPaging3("第" + (10 * currentPage + i) + "条"))
                }
                val prevKey = if (currentPage == 0) null else currentPage - 1
                val nextKey = currentPage + 1
                if (!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() {

    class LoadStateViewHolder(view: View) : RecyclerView.ViewHolder(view)

    private var listener: (() -> Unit)? = null

    fun 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.VISIBLE
        holder.itemView.tv_state.text = "正在加载中..."
        if (loadState is LoadState.NotLoading) {
            if (loadState.endOfPaginationReached) {
                holder.itemView.progress_bar.visibility = View.GONE
                holder.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.GONE
            holder.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() {
            override fun getRefreshKey(state: PagingState): Int? {
                return null
            }

            override fun loadSingle(params: LoadParams): Single> {
                val currentPage = params.key ?: 0
                val delay = if (currentPage > 0) {
                    val random = Math.random()
                    random * 3000
                } else {
                    0f
                }.toLong()
                return Single.just("")
                    .delay(delay, TimeUnit.MILLISECONDS)
                    .map {
                        val list = mutableListOf()
                        for (i in 0..9) {
                            list.add(ItemPaging3("第" + (10 * currentPage + i) + "条"))
                        }
                        val prevKey = if (currentPage == 0) null else currentPage - 1
                        val nextKey = currentPage + 1
                        if (!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()

}

你可能感兴趣的:(android,paging3)