本文已参与「新人创作礼」活动,一起开启掘金创作之路。
一、前言
在项目初期,为了统一管理列表的分页加载逻辑,于是写了一个BaseRvActivity/BaseRvFragment去实现。后来随着项目的不断庞大,渐渐地往里面增加了单选/多选,筛选条件配置等等其他的逻辑,甚至于还加入了一些业务相关的逻辑代码,并且还继承于这两个文件实现了几个扩展后的BaseXxActivity/BaseXxFragment,导致某些页面的Activity变成了BaseActivity的第N代子孙,继承关系就成了 MouYeWuActivity -> BaseYwActivity -> BaseXxxxActivity -> BaseXxxActivity -> BaseXxActivity -> BaseActivity
这样。
这是我们想看到的吗?不,并不是。
在这样的情境下,突然想到了最近在学习Jetpack Compose
时看到的一句话,“用组合代替了继承”,显然这个概念并不是我第一次遇到,只是一直没有引起重视罢了。这时想起,在Jetpadk中的Paging
不就是这样的一个库吗?于是开始翻Paging3
的文档照着写实例,最终发现这个库十分贴切地表达了“组合”的思想。
但是,问题来了,之前项目中一直使用的是BaseQuickAdapter
作为适配器,但是Paging3
用的确实RecyclerView.Adapter
的一个子类PagingDataAdapter
,如果要将Paging3
引入项目,项目中如果不想存在两套分页框架,那么就必须将之前所有的列表适配器全部替换掉,这个工程可不小,而且也不是我们愿意看到的。
怎么办呢?那还能怎么办,自己撸一个呗。于是,这200行代码诞生了。
二、接下来,先看一下效果
下面的效果均模拟了耗时2秒的网络请求
-
模拟第3页为最后一页的效果
-
模拟第3页加载出现异常的效果
三、PageHelper 介绍
以RefreshLayout
和BaseQuickAdapter
为基础的分页加载组件,整个组件包含PageHelper
、Pager
、PageUi
、LoadState
四个部分。在使用的时候,只需要对相关的数据进行配置即可,不需要实现任何的逻辑代码。
LoadState
:定义加载状态PageUi
:负责注册相关的UI组件Pager
:包含分页数据和数据源以及相关状态的管理PageHelper
:调用者直接使用的文件,负责了UI组件相关逻辑的初始化
1. LoadState
包含3个子类 | 描述 | 备注 |
---|---|---|
NotLoading |
无加载状态,可作为加载完成的状态处理 | 包含一个noMore 属性,表示是否已经没有更多数据。可在接收到该状态时进行相关操作。在其他两个状态中也有这个属性,但是在目前看起来并没有什么用。 |
Loading |
加载中状态 | |
Error |
加载异常状态,在获取数据过程中出现异常时回调 | 包含一个error 属性,为在获取数据过程中发生的异常信息,可在接收到该状态时,利用该属性执行相应的异常处理逻辑。 |
2. PageUi
属性 | 类型 | 备注 | |||
---|---|---|---|---|---|
scope | kotlinx.coroutines.CoroutineScope |
建议在View层使用,传入lifecycleScope 。虽然在ViewModel层中传入 viewModelScope 也是可以的,但是不推荐这样用。 |
|||
refreshLayout | com.scwang.smart.refresh.layout.api.RefreshLayout |
第三方的上拉下拉组件 | adapter |
com.chad.library.adapter.base.BaseQuickAdapter |
第三方的列表数据适配器组件 |
3. Pager
属性 | 类型 | 备注 |
---|---|---|
pageConfig | PageConfig | 跟分页相关的基础配置 属性: initIndex:首页页码 initSize:每页的条数 prevIndex:上一页页码 nextIndex:下一页页码 方法: reset() — 重置为初始数据 |
request | suspend (Int, Int) -> List | 获取数据的方法,第一个Int 参数为页码page ,第二个Int 参数为每页的条数pageSize 。 |
4. PageHelper
属性 | 描述 |
---|---|
pageUi | 负责注册相关的UI组件 |
pager | 包含分页数据和数据源以及相关状态的管理 |
公开的方法:
方法 | 描述 |
---|---|
fun refresh() | 代码控制刷新 |
suspend fun requestNext() | 代码控制获取下一页 |
四、如下是上面效果图的主要代码
提前说明一下,主要的管理逻辑其实都是在Pager
类中实现的。因此,如果分页的效果要用在其他的控件上,只需要将PageUi
中的属性类型替换掉,然后在PageHelper
对控件实现相应的初始化逻辑即可。
class SimplePageActivity : AppCompatActivity() {
......
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
.....
// 1. 创建PageUi,指定UI组件
val pageUi = PageUi(
scope = lifecycleScope,
refreshLayout = refreshLayout,
adapter = adapter
)
// 2. 创建Pager,定义分页配置,指定获取数据的方法,设置监听器
val pager = Pager(pageConfig = PageConfig(initIndex = 1),
request = { page, size -> SimpleHttpService.requestList(page = page, pageSize = size) }).apply {
// 设置是否没有更多数据的状态监听器
setNoMoreStateListener(object : PageState.NoMoreStateListener {
override fun noMoreState(noMoreState: Boolean) {
Log.e("SimplePageActivity", "noMoreState = $noMoreState")
}
})
// 添加加载状态监听器
addLoadStateListener(object : PageState.LoadStateListener {
override fun updateState(loadState: LoadState) {
when (loadState) {
is LoadState.NotLoading -> {
hideLoading()
showToast("加载完成")
}
is LoadState.Loading -> {
showLoading("加载中")
}
is LoadState.Error -> {
hideLoading()
errorDialog.setMessage(loadState.error.message)
errorDialog.show()
showToast("加载失败,${loadState.error.message}")
}
}
}
})
}
// 3. 创建PageHelper,将pageUi和pager联系起来
pageHelper = PageHelper<String>(pageUi = pageUi, pager = pager)
// 4. 通过代码进行刷新操作
pageHelper.refresh()
}
......
}
五、附上源码
-
LoadState
sealed class LoadState(val noMore: Boolean) { /** * 无加载状态,可作为加载完成的状态处理 */ class NotLoading(noMore: Boolean) : LoadState(noMore) { override fun toString(): String { return "NotLoading(noMore=$noMore)" } override fun equals(other: Any?): Boolean { return other is NotLoading && noMore == other.noMore } override fun hashCode(): Int { return noMore.hashCode() } } /** * 加载中状态 */ object Loading : LoadState(false) { override fun toString(): String { return "Loading(noMore=$noMore)" } override fun equals(other: Any?): Boolean { return other is Loading && noMore == other.noMore } override fun hashCode(): Int { return noMore.hashCode() } } /** * 加载异常状态,在获取数据过程中出现异常时回调 */ class Error( val error: Throwable ) : LoadState(false) { override fun equals(other: Any?): Boolean { return other is Error && noMore == other.noMore && error == other.error } override fun hashCode(): Int { return noMore.hashCode() + error.hashCode() } override fun toString(): String { return "Error(noMore=$noMore, error=$error)" } } }
-
PageUi
class PageUi<T>( val scope: CoroutineScope, val refreshLayout: RefreshLayout, val adapter: BaseQuickAdapter<T, *> )
-
Pager
class Pager<T>( internal val pageConfig: PageConfig, internal val request: suspend (Int, Int) -> List<T> ) { internal val pageState: PageState<T> = PageState() /** * 设置是否没有更多数据的状态监听器 */ fun setNoMoreStateListener(noMoreStateState: PageState.NoMoreStateListener) { pageState.setNoMoreStateListener(noMoreStateListener = noMoreStateState) } /** * 添加加载状态监听器 */ fun addLoadStateListener(loadStateState: PageState.LoadStateListener) { pageState.addLoadStateListener(loadStateListener = loadStateState) } /** * 发送没有更多数据的状态 * * 如果外部需要调用此方法,请使用[PageHelper.callNoMoreState] */ internal fun callNoMoreState(noMoreState: Boolean) { pageState._noMoreStateListener?.noMoreState(noMoreState = noMoreState) } /** * 刷新(重新获取第一页) */ internal suspend fun refresh(): List<T> { pageState.callLoadState(loadState = LoadState.Loading) pageConfig.reset() val result = request.runCatching { invoke(pageConfig.index.run { pageConfig.index++ }, pageConfig.size) } return checkResult(result = result) } /** * 获取下一页 */ internal suspend fun requestNext(): List<T> { pageState.callLoadState(loadState = LoadState.Loading) val result = request.runCatching { invoke(pageConfig.nextIndex.run { pageConfig.index++ }, pageConfig.size) } return checkResult(result = result) } /** * 查验请求的结果 */ private fun checkResult(result: Result<List<T>>): List<T> { val list = if (result.isSuccess) { val _list = result.getOrNull() ?: emptyList() pageState.checkPageList(_list, pageConfig.size) pageState.callLoadState(loadState = LoadState.NotLoading(pageState.noMore)) _list } else { pageConfig.index-- //因为之前在请求数据时,取了页码值后立即对页码进行了+1,因此如果请求数据失败,则需要通过-1将页码进行回退 val throwable = result.exceptionOrNull() ?: Throwable("获取数据失败") pageState.callLoadState(loadState = LoadState.Error(throwable)) throwable.printStackTrace() emptyList() } return list } } /** * 基础配置 * * @property initIndex 首页页码 */ class PageConfig(private val initIndex: Int, initSize: Int = 20) { var index: Int = initIndex var size: Int = initSize //上一页页码 val prevIndex: Int? get() = if (index > initIndex) index - 1 else null //下一页页码 val nextIndex: Int get() = index + 1 /** * 重置数据 */ fun reset() { index = initIndex } } /** * 所有的状态管理器 */ class PageState<T> { //是否没有更多数据的状态 val noMoreFlow = MutableStateFlow(false) val noMore: Boolean get() = noMoreFlow.value /** * 检查获取到的数据 * * 计算出是否还有下一页 */ fun checkPageList(list: List<T>, pageSize: Int) { val _noMore = list.size < pageSize if (noMoreFlow.value != _noMore) { noMoreFlow.value = _noMore } } /*---------- 是否没有更多数据的状态 ----------*/ private var noMoreStateListener: NoMoreStateListener? = null val _noMoreStateListener: NoMoreStateListener? get() = noMoreStateListener fun setNoMoreStateListener(noMoreStateListener: NoMoreStateListener) { this.noMoreStateListener = noMoreStateListener } /** * 是否没有更多数据的状态的回调接口 */ interface NoMoreStateListener { fun noMoreState(noMoreState: Boolean) } /*---------- 加载状态 ----------*/ private val loadStateListeners = mutableSetOf<LoadStateListener>() fun addLoadStateListener(loadStateListener: LoadStateListener) { loadStateListeners.add(loadStateListener) } fun removeLoadStateListener(loadStateListener: LoadStateListener) { loadStateListeners.remove(loadStateListener) } /** * 发送加载状态 * * @param loadState 新的状态 */ fun callLoadState(loadState: LoadState) { loadStateListeners.forEach { it.updateState(loadState = loadState) } } /** * 加载状态回调接口 */ interface LoadStateListener { fun updateState(loadState: LoadState) } }
-
PageHelper
class PageHelper<T>( val pageUi: PageUi<T>, val pager: Pager<T>, ) { init { pageUi.refreshLayout.setOnRefreshLoadMoreListener(object : OnRefreshLoadMoreListener { override fun onRefresh(refreshLayout: RefreshLayout) { pageUi.scope.launch { val list = pager.refresh() pageUi.adapter.setList(list) if (pager.pageState.noMore) { refreshLayout.finishRefreshWithNoMoreData() } else { refreshLayout.finishRefresh() } } } override fun onLoadMore(refreshLayout: RefreshLayout) { pageUi.scope.launch { val list = pager.requestNext() pageUi.adapter.addData(list) if (pager.pageState.noMore) { refreshLayout.finishLoadMoreWithNoMoreData() } else { refreshLayout.finishLoadMore() } } } }) pageUi.scope.launch { pager.pageState.noMoreFlow.collectLatest { pager.callNoMoreState(noMoreState = it) } } } /** * 代码控制刷新 */ fun refresh() { pageUi.refreshLayout.autoRefresh() } /** * 代码控制获取下一页 */ suspend fun requestNext() { pager.requestNext() } /** * 发送没有更多数据的状态 * * 实验性api,不确定在实际使用中的逻辑效果是否能满足实际需求 */ @ExperimentalStdlibApi fun callNoMoreState(noMoreState: Boolean) { pager.callNoMoreState(noMoreState = noMoreState) } }
今天的文章Android端使用200行代码实现的分页加载 — PageHelper分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/22499.html