class DataSource():PagingSource<Int,DemoReqData.DataBean.DatasBean>(){
override suspend fun load(params: LoadParams): LoadResult<Int, DemoReqData.DataBean.DatasBean> {
TODO(“Not yet implemented”)
}
}
我们可以看到PagingSource中有两个参数Key 和 Value,这里Key我们定义为Int类型Value DemoReqData 是接口返回数据对应的实体类,这里的意思就是
我们传Int类型的值(如页码)得到返回的数据信息DemoReqData对象。
这里需要提醒的是如果你使用的不是Kotlin 协程而是Java,则需要继承对应的PagingSource如RxPagingSource或ListenableFuturePagingSource。
DataSource为我们自动生成了load方法,我们主要的请求操作就在load方法中完成。主要代码如下所示:
override suspend fun load(params: LoadParams): LoadResult<Int, DemoReqData.DataBean.DatasBean> {
return try {
//页码未定义置为1
var currentPage = params.key ?: 1
//仓库层请求数据
var demoReqData = DataRespority().loadData(currentPage)
//当前页码 小于 总页码 页面加1
var nextPage = if (currentPage < demoReqData?.data?.pageCount ?: 0) {
currentPage + 1
} else {
//没有更多数据
null
}
if (demoReqData != null) {
LoadResult.Page(
data = demoReqData.data.datas,
prevKey = null,
nextKey = nextPage
)
} else {
LoadResult.Error(throwable = Throwable())
}
} catch (e: Exception) {
LoadResult.Error(throwable = e)
}
}
上面代码我们可以看到在datasource中我们通过DataRespority()仓库层,去请求数据,如果没有更多数据就返回null,最后使用 LoadResult.Page将结果返回,如果加载失败则用LoadResult.Error返回,由于 LoadResult.Page中的data 必须是非空类型的,所以我们需要判断返回是否为null。
接下来我们看下DataRespority仓库层的代码,代码比较简单,如下所示:
class DataRespority {
private var netWork = RetrofitService.createService(
DataApi::class.java
)
/**
- 查询护理数据
*/
suspend fun loadData(
pageId: Int
): DemoReqData? {
return try {
netWork.getData(pageId)
} catch (e: Exception) {
//在这里处理或捕获异常
null
}
}
}
Load调用官方给出的流程如下所示:
从上图可以知道,load的方法 是我们通过Paging的配置自动触发的,不需要我们每次去调用,那么我们如何来使用DataSource呢?
调用PagingSource
The Pager
object calls the load()
method from the PagingSource
object, providing it with the LoadParams object and receiving the LoadResult object in return.
这句话翻译过来的意思就是:Pager对象从PagingSource对象调用load()方法,为它提供LoadParams对象,并作为回报接收LoadResult对象。
所以我们在创建viewModel对象,并创建pager对象从而调用PagingSource方法 ,代码如下所示:
class MainActivityViewModel : ViewModel() {
/**
- 获取数据
*/
fun getData() = Pager(PagingConfig(pageSize = 1)) {
DataSource()
}.flow
}
在viewmodel中我们定义了一个getData的方法,Pager中通过配置PagingConfig来实现特殊的定制,我们来看下PagingConfig中的参数如下:
pageSize:定义从 PagingSource 一次加载的项目数。
prefetchDistance:预取距离,简单解释就是 当距离底部还有多远的时候自动加载下一页,即自动调用load方法,默认值和pageSize相等
enablePlaceholders:是否显示占位符,当网络不好的时候,可以考到页面的框架,从而提升用户体验
还有一些其他参数这里就不一一介绍了,从构造方法的源码中可以看出pageSize这个参数是必填的,其他的是可选项,所以我们这里传了1
定义RecycleViewAdapter
这一步,和我们平时定义普通的RecycleViewAdapter没有太大的区别,只是我们继承的是PagingDataAdapter,主要代码如下所示:
class DataRecycleViewAdapter :
PagingDataAdapter<DemoReqData.DataBean.DatasBean, RecyclerView.ViewHolder>(object :
DiffUtil.ItemCallback<DemoReqData.DataBean.DatasBean>() {
override fun areItemsTheSame(
oldItem: DemoReqData.DataBean.DatasBean,
newItem: DemoReqData.DataBean.DatasBean
): Boolean {
return oldItem.id == newItem.id
}
@SuppressLint(“DiffUtilEquals”)
override fun areContentsTheSame(
oldItem: DemoReqData.DataBean.DatasBean,
newItem: DemoReqData.DataBean.DatasBean
): Boolean {
return oldItem == newItem
}
}) {
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
var dataBean = getItem(position)
(holder as DataViewHolder).binding.demoReaData = dataBean
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TestViewHolder {
return TestViewHolder(
DataBindingUtil.inflate(
LayoutInflater.from(parent.context),
R.layout.health_item_test,
parent,
false
)
)
}
inner class DataViewHolder(private val dataBindingUtil: ItemDataBinding) :
RecyclerView.ViewHolder(dataBindingUtil.root) {
var binding = dataBindingUtil
}
}
这里我们要提醒的是DiffUtil这个参数,用于计算列表中两个非空项目之间的差异的回调。无特殊情况一般都是固定写法。
View层数据请求并将结果显示在View上
到这里,基本工作已经差不多了,当然我们说的差不多了只是快能看到成果了,其中需要讲解的地方还有很多,最后一步我们在view中请求数据,并将结果绑定在adapter上
我们在View代码中调用viewModel中的getData方法,代码如下所示:
val manager = LinearLayoutManager(this)
rv_data.layoutManager = manager
rv_data.adapter = dataRecycleViewAdapter
btn_get.setOnClickListener {
lifecycleScope.launch {
mainActivityViewModel.getData().collectLatest {
dataRecycleViewAdapter.submitData(it)
}
}
}
我们在协程中调用getData方法,接收最新的数据,通过PagingAdapter的submitData方法为adapter提供数据,运行结果如下所示(忽略丑陋的UI.jpg)
当我们往下滑动时,当底部还剩1个(pageSize)数据的时候会自动加载下一页。
当然对于这个接口不需要传pageSize,所以返回的数据大小并不会受pageSize的影响,如此一来,我们就使用Paging3 完成了简单的数据分页请求。
Paging的加载状态
Paging3 为我们提供了获取Paging加载状态的方法,其中包含添加监听事件的方式以及在adapter中直接显示的方式,首先我们来看监听事件的方式
使用监听事件方式获取加载状态
上面我们在Activity中创建了dataRecycleViewAdapter来显示页面数据,我们可以使用addLoadStateListener方法添加加载状态的监听事件,如下所示:
dataRecycleViewAdapter.addLoadStateListener {
when (it.refresh) {
is LoadState.NotLoading -> {
Log.d(TAG, “is NotLoading”)
}
is LoadState.Loading -> {
Log.d(TAG, “is Loading”)
}
is LoadState.Error -> {
Log.d(TAG, “is Error”)
}
}
}
这里的it是CombinedLoadStates数据类,有refresh、Append、Prepend 区别如下表格所示:
refresh | 在初始化刷新的使用 |
append | 在加载更多的时候使用 |
prepend | 在当前列表头部添加数据的时候使用 |
也就是说如果监测的是it.refresh,当加载第二页第三页的时候,状态是监听不到的,这里只以it.refresh为例。
LoadState的值有三种,分别是NotLoading:当没有加载动作并且没有错误的时候
Loading和Error顾名思义即对应为正在加载 和加载错误的时候,监听方式除了addLoadStateListener外,还可以直接使用loadStateFlow的方式,由于flow内部是一个挂起函数 所以我们需要在协程中执行(Kotlin Flow 看这一篇 带你入门~),代码如下所示:
lifecycleScope.launch {
dataRecycleViewAdapter.loadStateFlow.collectLatest {
when (it.refresh) {
is LoadState.NotLoading -> {
}
is LoadState.Loading -> {
}
is LoadState.Error -> {
}
}
}
}
接下来我们运行上节的示例,运行成功后,点击查询按钮,将数据显示出来,我们看打印如下:
2020-11-14 16:39:19.841 23729-23729/com.example.pagingdatademo D/MainActivity: is NotLoading
2020-11-14 16:39:24.529 23729-23729/com.example.pagingdatademo D/MainActivity: 点击了查询按钮
2020-11-14 16:39:24.651 23729-23729/com.example.pagingdatademo D/MainActivity: is Loading
2020-11-14 16:39:25.292 23729-23729/com.example.pagingdatademo D/MainActivity: is NotLoading
首先是NotLoading 状态,因为我们什么都没有操作,点击了查询按钮后变成Loading状态因为正在加载数据,查询结束后再次回到了NotLoading的状态,符合我们的预期,那这个状态有什么用呢? 我们在Loading状态显示一个progressBar过渡提升用户体验等,当然最重要的还是Error状态,因为我们需要Error状态下告知用户。
我们重新打开App,断开网络连接,再次点击查询按钮,打印日志如下:
2020-11-14 16:48:25.943 26846-26846/com.example.pagingdatademo D/MainActivity: is NotLoading
2020-11-14 16:48:27.218 26846-26846/com.example.pagingdatademo D/MainActivity: 点击了查询按钮
2020-11-14 16:48:27.315 26846-26846/com.example.pagingdatademo D/MainActivity: is Loading
2020-11-14 16:48:27.322 26846-26846/com.example.pagingdatademo D/MainActivity: is Error
这里要注意的是什么呢,就是这个Error的状态,不是Paging为我们自动返回的,而是我们在DataSource中捕获异常后,使用LoadResult.Error方法告知的。
我们也需要在Error状态下监听具体的错误,无网络的话就显示无网络UI 服务器异常的话 就提示服务器异常,代码如下所示:
is LoadState.Error -> {
Log.d(TAG, “is Error:”)
when ((it.refresh as LoadState.Error).error) {
is IOException -> {
Log.d(TAG, “IOException”)
}
else -> {
Log.d(TAG, “others exception”)
}
}
}
我们在断网状态下,点击查询,日志如下所示:
2020-11-14 17:29:46.234 12512-12512/com.example.pagingdatademo D/MainActivity: 点击了查询按钮
2020-11-14 17:29:46.264 12512-12512/com.example.pagingdatademo D/MainActivity: 请求第1页
2020-11-14 17:29:46.330 12512-12512/com.example.pagingdatademo D/MainActivity: is Loading
2020-11-14 17:29:46.339 12512-12512/com.example.pagingdatademo D/MainActivity: is Error:
2020-11-14 17:29:46.339 12512-12512/com.example.pagingdatademo D/MainActivity: IOException
在adapter中显示
Paging3 为我们提供了添加底部、头部adapter的方法,分别为 withLoadStateFooter、withLoadStateHeader以及同时添加头部和尾部方法withLoadStateHeaderAndFooter,这里我们以添加尾部方法为例
首先我们创建viewHolder LoadStateViewHolder绑定布局是底部显示的布局,一个正在加载的显示以及一个重试按钮,xml布局如下所以:
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:app=“http://schemas.android.com/apk/res-auto”
android:layout_width=“match_parent”
android:layout_height=“match_parent”>
<LinearLayout
android:id=“@+id/ll_loading”
android:layout_width=“match_parent”
android:layout_height=“wrap_content”
android:gravity=“center”
android:orientation=“horizontal”
android:visibility=“gone”
app:layout_constraintEnd_toEndOf=“parent”
app:layout_constraintStart_toStartOf=“parent”
app:layout_constraintTop_toTopOf=“parent”>
<TextView
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:text=“正在加载数据… …”
android:textSize=“18sp” />
<ProgressBar
android:layout_width=“20dp”
android:layout_height=“20dp” />
<Button
android:id=“@+id/btn_retry”
android:layout_width=“match_parent”
android:layout_height=“wrap_content”
android:text=“加载失败,重新请求”
android:visibility=“gone”
app:layout_constraintStart_toStartOf=“parent”
app:layout_constraintTop_toBottomOf=“@id/ll_loading” />
</androidx.constraintlayout.widget.ConstraintLayout>
正在加载提示和重新请求的布局默认都是隐藏,LoadStateViewHolder代码如下所示:
class LoadStateViewHolder(parent: ViewGroup, var retry: () -> Void) : RecyclerView.ViewHolder(
LayoutInflater.from(parent.context)
.inflate(R.layout.item_loadstate, parent, false)
) {
var itemLoadStateBindingUtil: ItemLoadstateBinding = ItemLoadstateBinding.bind(itemView)
fun bindState(loadState: LoadState) {
if (loadState is LoadState.Error) {
itemLoadStateBindingUtil.btnRetry.visibility = View.VISIBLE
itemLoadStateBindingUtil.btnRetry.setOnClickListener {
retry()
}
} else if (loadState is LoadState.Loading) {
itemLoadStateBindingUtil.llLoading.visibility = View.VISIBLE
}
}
}
我们这里是和Adapter分为两个类中的,所以我们要将adapter中的parent当做参数传过来,retry()是一个高阶函数,便于点击重试后,在adapter中做重试逻辑。
bindState 即为设置数据,根据State的状态来显示不同的UI。
接着我们来创建LoadStateFooterAdapter 继承自LoadStateAdapter,对应的viewHolder即为LoadStateViewHolder,代码如下所示:
class LoadStateFooterAdapter(private val retry: () -> Void) :
LoadStateAdapter() {
override fun onBindViewHolder(holder: LoadStateViewHolder, loadState: LoadState) {
(holder as LoadStateViewHolder).bindState(loadState)
}
override fun onCreateViewHolder(parent: ViewGroup, loadState: LoadState): LoadStateViewHolder {
return LoadStateViewHolder(parent, retry)
}
}
这里的代码比较简单,就不作讲解了,最后我们来添加这个adapter
rv_data.adapter =
dataRecycleViewAdapter.withLoadStateFooter(footer = LoadStateFooterAdapter(retry = {
dataRecycleViewAdapter.retry()
}))
这里要注意的是,应该把withLoadStateFooter返回的adapter设置给recyclerview,如果你是这样写:dataRecycleViewAdapter.withLoadStateFooter后 在单独设置recycleView的adapter,则会是没有效果的。
最后
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。
因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
这里要注意的是,应该把withLoadStateFooter返回的adapter设置给recyclerview,如果你是这样写:dataRecycleViewAdapter.withLoadStateFooter后 在单独设置recycleView的adapter,则会是没有效果的。
最后
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。
因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-eZFXbJny-1715757125447)]
[外链图片转存中…(img-hu2zOblK-1715757125448)]
[外链图片转存中…(img-yrXYgbHY-1715757125449)]
[外链图片转存中…(img-dribdPj0-1715757125450)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!