观心静

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

版权声明

本文来自博客园,作者:观心静 ,转载请注明原文链接:https://www.cnblogs.com/guanxinjing/p/16014823.html

本文版权归作者和博客园共有,欢迎转载,但必须给出原文链接,并保留此段声明,否则保留追究法律责任的权利。

前言

    Paging是在jetpack系列中的分页框架,在之前Android上做分页框架一般都是需要自己封装RecyclerView来实现。自己封装有一定程度上的复杂性,代码的碎片性与风格不统一性。封装后其他人对代码难以快速理解。这是本人的封装 https://www.cnblogs.com/guanxinjing/p/13204403.html

Paging的出现就是为了在Android平台上标准化分页加载功能。说句实话,其实Paging也相当的复杂与碎片。 但是Paging也有闪光点比如数据预加载、性能消耗低、稳定性高。

google文档 https://developer.android.google.cn/jetpack/androidx/releases/paging

依赖

这是主要关键库

pagingRuntimeKtx = "3.3.6"
androidx-paging-runtime-ktx = { group = "androidx.paging", name = "paging-runtime-ktx", version.ref = "pagingRuntimeKtx" }

代码

 效果图

 

 

 

 

PagingSource

数据来源类

class HelpListSource: PagingSource<Long, HelpBean>() {

    override fun getRefreshKey(state: PagingState<Long, HelpBean>): Long? {
        return null
    }

    override suspend fun load(params: LoadParams<Long>): LoadResult<Long, HelpBean> {
        try {
            val currentPage = params.key ?: 0
            //上一页
            val prevKey = if (currentPage == 1L) null else currentPage - 1
            //下一页
            val helpRepository = HelpRepository()
            Log.e("zh", "params.loadSize === ${params.loadSize} ", )
            val (success, msg, dataList) = helpRepository.getHelpList(currentPage.toInt(), params.loadSize)
            if(!success){
                return LoadResult.Error(Exception("网络异常,获取帮助列表失败${msg}"))
            }
            val nextKey = if (dataList.isNullOrEmpty()) null else currentPage + 1
            if(dataList.isNullOrEmpty()){
                return LoadResult.Page(
                    arrayListOf(),
                    null,
                    null
                )
            }
            return LoadResult.Page(
                dataList,
                prevKey,
                nextKey
            )
        } catch (e: Exception) {
            return LoadResult.Error(e)
        }
    }
}

ViewModel

package net.wt.gate.main.ui.activity.help

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.cachedIn
import net.wt.gate.common.utils.SingleLiveEvent
import net.wt.gate.main.data.bean.HelpBean
import net.wt.gate.main.repository.HelpListSource

class HelpViewModel : ViewModel() {
    fun getHelpList(): LiveData<PagingData<HelpBean>> {
        //pageSize=一页的数量 initialLoadSize=初始获取的数量   initialKey=初始页码
        return Pager(PagingConfig(pageSize = 20, initialLoadSize = 20), initialKey = 1) { HelpListSource() }
            .flow
            .cachedIn(viewModelScope)
            .asLiveData(viewModelScope.coroutineContext)
    }
}

Adapter

package net.wt.gate.main.ui.activity.help

import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.paging.PagingDataAdapter
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import kotlinx.coroutines.CoroutineDispatcher
import net.wt.gate.main.data.bean.HelpBean
import net.wt.gate.main.databinding.MainItemHelpBinding
import net.wt.gate.main.ui.activity.help.HelpListAdapter.ViewHolder

class HelpListAdapter2 : PagingDataAdapter<HelpBean, HelpListAdapter2.ViewHolder> {

    constructor() : super(object : DiffUtil.ItemCallback<HelpBean>() {
        override fun areItemsTheSame(oldItem: HelpBean, newItem: HelpBean): Boolean {
            return oldItem.id == newItem.id // 根据 ID 判断是否为同一项
        }

        override fun areContentsTheSame(oldItem: HelpBean, newItem: HelpBean): Boolean {
            return oldItem == newItem
        }
    })

    private var mOnClickListener : ((HelpBean?) -> Unit)? = null


    fun setOnClickListener(listener: (HelpBean?) -> Unit) {
        mOnClickListener = listener
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.binding.title.text = getItem(position)?.title
        if(itemCount - 1 == position){
            holder.binding.line.visibility = View.GONE
        } else {
            holder.binding.line.visibility = View.VISIBLE
        }
    }


    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val binding = MainItemHelpBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        val viewHolder = ViewHolder(binding)
        binding.root.setOnClickListener {
            val position = viewHolder.bindingAdapterPosition
            if (position == RecyclerView.NO_POSITION) {
                return@setOnClickListener
            }
            mOnClickListener?.invoke(getItem(position))
        }
        return viewHolder
    }

    class ViewHolder(val binding: MainItemHelpBinding) : RecyclerView.ViewHolder(binding.root)

}

xml

<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".paging.PagingActivity">

    <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
        android:id="@+id/refresh"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recyclerView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>

    </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

Activity

mViewModel.getHelpList().observe(this) {
    lifecycleScope.launch {
        repeatOnLifecycle(Lifecycle.State.RESUMED) {
            mAdapter.submitData(it)
        }
    }
}

 

添加页头页脚

withLoadStateHeader 添加页脚,可以用于loadmore

withLoadStateHeaderAndFooter 可以添加页头/页脚

withLoadStateFooter 添加页头

这里需要注意的是,具体页头页脚的实现方式也是创建一个ViewHolder然后放到LoadStateAdapter中去,我们常见的底部loadmore就是添加页脚,但是这里的Header不是我们项目中在列表最顶部添加一个item的意思,而是和loadmore类似的概念。也就是说如果我们添加了一个页头,那么只有在PagingSource中返回LoadResult.Page的时候prevKey不为null才会显示出来,所以如果我们从第一页开始加载是看不到这个Header的,如果我们一开始加载的页数是第5页,那么我们在往上滑动的时候,才能看到我们的Header

监听状态

想要监听数据获取的状态在PagingDataAdapter里有两个方法

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

addLoadStateListener这个相较于上面那个比较复杂,listener中的回调会返回一个CombinedLoadStates对象

监听加载状态:  

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 { ... }
}

从数据库中分页的例子

数据库的Dao  

关键是使用offset进行分页

    //根据id从大到小排序,分页拿取数据
    @Query("select * from EcgBean order by id DESC limit :count offset :offset")
    fun queryDataByIdLimitCount(count: Int, offset: Int): List<EcgBean>

仓库类

package com.android.xxx.repository

import android.util.Log
import com.android.yujie.bean.EcgUIBean
import com.android.yujie.data.db.AppDB
import com.android.yujie.data.db.ecg.EcgBean
import com.android.yujie.data.db.ecgItem.EcgItemBean
import com.android.yujie.ext.getYYYYMMDDHHMM
import com.android.yujie.libs.log.logE
import com.google.gson.Gson
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch

class EcgRepository {
    val mEcgDao by lazy { AppDB.getDatabase().getEcgDao() }
    val mGson by lazy { Gson() }

    /**
     * 根据id从大到小排序,分页拿取数据
     */
    suspend fun queryDataByIdLimitCount(count:Int, offset:Int): List<EcgUIBean>{
       try {
           val ecgList = mEcgDao.queryDataByIdLimitCount(count, offset)
           return ecgList.map {
               EcgUIBean(
                   id = it.id,
                   startTime = it.startTime,
                   endTime = it.endTime,
                   deviceMac = it.deviceMac,
                   heartRate = it.heartRate,
                   qt = it.qt,
                   ecgData = listOf()
               )
           }
       } catch (e:Exception){
           "queryDataByIdLimitCount 异常 >>> ${e.message}".logE()
           e.printStackTrace()
           return emptyList()
       }
    }
}

 PagingSource

package com.android.xxx.repository

import android.util.Log
import androidx.paging.PagingSource
import androidx.paging.PagingState
import com.android.yujie.bean.EcgUIBean
import com.android.yujie.ext.findTheDailyMaxTimestamp
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.text.SimpleDateFormat
import java.util.Locale

class EcgListPagingSource: PagingSource<Long, EcgUIBean>() {
    private val mIsAddTitleDateList = mutableListOf<String>()
    private val mDateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())

    override fun getRefreshKey(state: PagingState<Long, EcgUIBean>): Long? {
        return null
    }

    override suspend fun load(params: LoadParams<Long>): LoadResult<Long, EcgUIBean> {
        try {
            val currentPage = params.key ?: 0
            //上一页
            val prevKey = if (currentPage == 1L) null else currentPage - 1
            //下一页
            val ecgRepository = EcgRepository()
            Log.e("zh", "params.loadSize === ${params.loadSize} ", )
            val dataList =withContext(Dispatchers.IO){
                 ecgRepository.queryDataByIdLimitCount(params.loadSize, (currentPage * params.loadSize).toInt())
            }
            //这部分处理数据是否需要显示日期标题
            if(dataList.isNotEmpty()){
                val list = dataList.findTheDailyMaxTimestamp{
                    it.startTime
                }
                val notAddTitleList = list.filter { mDateFormat.format(it.startTime) !in mIsAddTitleDateList }
                for (item in notAddTitleList){
                    val bean = dataList.findLast { it.startTime == item.startTime }
                    bean?.isShowDateTitle = true
                    bean?.let {
                        mIsAddTitleDateList.add(mDateFormat.format(it.startTime))
                    }
                }
            }
            val nextKey = if (dataList.isEmpty()) null else currentPage + 1
            if(dataList.isEmpty()){
                return LoadResult.Page(
                    arrayListOf(),
                    null,
                    null
                )
            }
            if(dataList.size < params.loadSize){
                return LoadResult.Page(
                    dataList,
                    null,
                    null
                )
            }
            return LoadResult.Page(
                dataList,
                prevKey,
                nextKey
            )
        } catch (e: Exception) {
            return LoadResult.Error(e)
        }
    }
}

 

 

End

posted on 2022-03-16 22:19  观心静  阅读(935)  评论(0)    收藏  举报