Paging分页库加载网络数据

官方文档:https://developer.android.google.cn/topic/libraries/architecture/paging/v3-overview?hl=zh-cn

使用

  • 定义数据流 PagingDataSource
/**
 * 网络返回数据定义 eg
 */
data class ListItemBean(val data: String? = null)

/**
 * 数据流获取定义
 * @param extraParams 定义自己需要传递的参数 Any?自己需要什么类型就传什么类型
 */
class ListDataPagingSource(private val extraParams: Any?) : PagingSource<Int, ListItemBean>() {
    /**
     *  getRefreshKey 请求刷新 或失败重试时的 LoadParams.key
     */
    override fun getRefreshKey(state: PagingState<Int, ListItemBean>): Int? {
        Log.e(">>>>>", "getRefreshKey: --> ${state.pages.size}  ${state.anchorPosition}")
        //失败或刷新时 得到当前要重新获取的页
        return null //直接返回null也行 就是刷新  这里返回数据不对容易造成刷新数据混乱
    }

    /**
     * 加载数据
     */
    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, ListItemBean> {
        Log.e(">>>>>", "load: --> pageNumber:${params.key} pageSize:${params.loadSize}")
        //检查自己传递的请求参数
        if (extraParams == null)
            return LoadResult.Error(Exception("参数缺失,extraParams is null"))

        return try {
            //当前请求页数信息
            val targetPage = params.key ?: 1
            //网络请求过程 这里调用自己的api来获取数据
            val dataList = withContext(Dispatchers.Default) {
                //模拟网络请求延时
                delay(Random.nextLong(400L, 1000L))
                //模拟数据
                mutableListOf<ListItemBean>().apply {
                    for (i in 0 until params.loadSize) {
                        add(ListItemBean("第${targetPage}页,item${i + 1}"))
                    }
                }
            }

            //返回数据
            LoadResult.Page(
                data = dataList,
                prevKey = (targetPage - 1).let {//上一页 实际按接口返回分页信息处理
                    if (it > 0) it else null
                },
                nextKey = (targetPage + 1).let {//下一页(模拟只有5页数据) 实际按接口返回分页信息处理
                    if (it <= 5) it else null
                }
            )
        } catch (e: Exception) {
            LoadResult.Error(e)
        }
    }
}
  • 定义viewModel中 Flow<PagingData<T>>
class PagingViewModel : ViewModel() {
    /**
     * 获取数据流
     * @param params 获取接口数据需要的参数 这里是通过PagingSource的构造方法传递过去
     */
    fun getListData(params: String?): Flow<PagingData<ListItemBean>> {
        return Pager(
            config = PagingConfig(
                pageSize = 10,         //每页加载个数  对应PagingSource的load()方法中的 params.loadSize
                initialLoadSize = 10,  //第一次加载个数  不配置默认第一次加载分页个数的3倍 直接和分页数一致也许
                //enablePlaceholders = true   //占位是否开启
            ),
            // initialKey = null,
            pagingSourceFactory = {
                ListDataPagingSource(params)
            }
        )
            .flow
            .cachedIn(viewModelScope)
    }
}
  • 定义PagingDataAdapter
/**
 * pagingAdapter
 */
class SourceListAdapter : PagingDataAdapter<ListItemBean, RecyclerView.ViewHolder>(
    diffCallback = object : DiffUtil.ItemCallback<ListItemBean>() { //用来做数据比较判断界面刷新
        override fun areItemsTheSame(oldItem: ListItemBean, newItem: ListItemBean): Boolean {
            return oldItem.hashCode() == newItem.hashCode()  //比较唯一标识 demo随便写的
        }

        override fun areContentsTheSame(oldItem: ListItemBean, newItem: ListItemBean): Boolean {
            return oldItem == newItem   //比较内容 最好重写对象的equals方法来做比较
        }
    }) {

    //点击事件回调函数
    private var clickCallback: ((Int, ListItemBean) -> Unit)? = null

    //设置点击事件
    fun addItemClickListener(listener: (Int, ListItemBean) -> Unit) {
        this.clickCallback = listener
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        getItem(position)?.let { item ->
            holder.itemView.findViewById<TextView>(R.id.content).text = item.data ?: "***"
            //点击回调
            holder.itemView.setOnClickListener {
                clickCallback?.invoke(position, item)
            }
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        //ViewHolder图方便  实际项目还是继承之后重写一个
        return object : RecyclerView.ViewHolder(
            LayoutInflater.from(parent.context)
                .inflate(R.layout.item_paging_list, parent, false)
        ) {}
    }
}
  • 使用
    <--下拉刷新事件用SwipeRefreshLayout监听处理-->
    <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
        android:id="@+id/refresh_layout"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_marginTop="2dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/img_back">

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/rcv_list"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
class PagingActivity : BaseActivity<ActivityPagingBinding>(ActivityPagingBinding::inflate) {

    private val viewModel by viewModels<PagingViewModel>()
    private var listAdapter: SourceListAdapter? = null

    override fun initView() {
        listAdapter = SourceListAdapter()
        mBinding.rcvList.apply {
            layoutManager = LinearLayoutManager(this@PagingActivity, RecyclerView.VERTICAL, false)
            adapter = listAdapter
        }
    }

    override fun initEvent() {
        mBinding.refreshLayout.setOnRefreshListener {
            //下拉刷新
            listAdapter?.refresh()
        }
        listAdapter?.addItemClickListener { position, itemBean ->
            //item点击
            Log.e(">>>>>", " --> click $position ${itemBean.toString()}")
        }
    }

    override fun initData() {
        lifecycleScope.launch {
            viewModel.getListData("user Get Service Data Params").collectLatest { //Flow<PagingData>
                //关闭刷新动画
                mBinding?.refreshLayout?.isRefreshing = false
                //数据展示
                listAdapter?.submitData(it)
            }
        }
    }

    override fun releaseData() {
        listAdapter = null
    }
}

以上已实现使用paging加载网络数据展示,下拉刷新和上拉自动加载更多的功能

  • 当然也可以增加上拉加载的提示footer
  • 定义加载提示LoadStateAdapter
class SimpleLoadStateAdapter : LoadStateAdapter<RecyclerView.ViewHolder>() {

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, loadState: LoadState) {
        if (loadState.endOfPaginationReached) {
            (holder.itemView as TextView).text = "已加载全部数据~"
        } else {
            (holder.itemView as TextView).text = "loading~"
        }
    }

    /**
     * 显示加载试图的时机
     */
    override fun displayLoadStateAsItem(loadState: LoadState): Boolean {
        return super.displayLoadStateAsItem(loadState)
                //这个加上才会回调加载完全部数据
                || (loadState is LoadState.NotLoading && loadState.endOfPaginationReached)
    }

    override fun onCreateViewHolder(
        parent: ViewGroup,
        loadState: LoadState
    ): RecyclerView.ViewHolder {
        //自定义加载提示视图
        val view = TextView(parent.context).apply {
            layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
            setPadding(10.px().toInt())
            textSize = 16.px()
            setTextColor("#666666".toColorInt())
            gravity = Gravity.CENTER
        }
        return object : RecyclerView.ViewHolder(view) {}
    }
}
  • 改造RecycleView的Adapter设置 adapter = listAdapter?.withLoadStateFooter(SimpleLoadStateAdapter())
override fun initView() {
        listAdapter = SourceListAdapter()
        mBinding.rcvList.apply {
            layoutManager = LinearLayoutManager(this@PagingActivity, RecyclerView.VERTICAL, false)
            //将原本的PagingDataAdapter拼接上加载提示LoadStateAdapter变成 ConcatAdapter设置给recycleView
            adapter = listAdapter?.withLoadStateFooter(SimpleLoadStateAdapter())
        }
    }
  • 实际运行效果图:


    下拉刷新.png
上拉加载.png
加载完全部数据.png
  • 更多的使用就自己慢慢摸索了
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容