版权声明
本文来自博客园,作者:观心静 ,转载请注明原文链接: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
本文来自博客园,作者:观心静 ,转载请注明原文链接:https://www.cnblogs.com/guanxinjing/p/16014823.html