Android Paging3分页+ConcatAdapter+空数据视图+下拉刷新(SwipeRefreshLayout)+加载更多+错误重试 (示例)
文章目录
- 引入库
- 数据模型定义
- 分页 adapter
- 加载更多 adapter
- 空数据 adapter
- 分页数据源
- ViewModel 提供加载数据源的方法
- 结合以上实现的 Fragment
- 数据重复问题
- 对 `PagingSource` 进行封装
- # 对 `Adapter` 的管理,进行封装 (更新于 230705)
- ViewPager2 中使用发现的 bug(更新于 230719)
引入库
implementation 'androidx.paging:paging-runtime-ktx:3.1.1'implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1'implementation 'androidx.recyclerview:recyclerview:1.3.0'
paging 库,目前还是有点小bug ,后面说
数据模型定义
// 分页请求的数据响应,至少要返回一个总数量; page、pageIndex 都可本地定义,然后 可以计算出当前请求响应后,历史返回的总数量;最终计算出是否还有下一页data class CustomerPageData(val totalCount: Int, val data: List<CustomerData>)data class CustomerData(val id: String, val name: String)
分页 adapter
import android.graphics.Colorimport android.view.LayoutInflaterimport android.view.ViewGroupimport android.widget.TextViewimport androidx.paging.PagingDataAdapterimport androidx.recyclerview.widget.DiffUtilimport com.stone.stoneviewskt.Rimport com.stone.stoneviewskt.common.BaseViewHolderimport com.stone.stoneviewskt.data.CustomerDataimport com.stone.stoneviewskt.util.logiclass PageListAdapter: PagingDataAdapter<CustomerData, BaseViewHolder>(object : DiffUtil.ItemCallback<CustomerData>() { // 是否是同一条 item override fun areItemsTheSame(oldItem: CustomerData, newItem: CustomerData): Boolean { logi("areItemsTheSame") // 没有输出该日志 可能是官方的bug(不然实现这个回调有什么意义) return oldItem.id == newItem.id } // 是否内容相同 override fun areContentsTheSame(oldItem: CustomerData, newItem: CustomerData): Boolean { logi("areContentsTheSame") // 没有输出该日志 可能是官方的bug(不然实现这个回调有什么意义) return oldItem.id == newItem.id }}) { // item 点击事件 private var mBlock: ((position: Int, data: CustomerData) -> Unit)? = null override fun onBindViewHolder(holder: BaseViewHolder, position: Int) { val data = getItem(position) ?: return holder.findView<TextView>(R.id.tv_name).text = data.name holder.itemView.setOnClickListener { mBlock?.invoke(position, data) } if (position % 2 == 0) { holder.itemView.setBackgroundColor(Color.parseColor("#c0ff00ff")) } else { holder.itemView.setBackgroundColor(Color.parseColor("#c0abc777")) } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder { val itemView = LayoutInflater.from(parent.context).inflate(R.layout.item_page_list, parent, false) return BaseViewHolder(itemView) } fun setItemClick(block: (position: Int, data: CustomerData) -> Unit) { this.mBlock = block }}
布局文件就是显示两个TextView,就不贴了
加载更多 adapter
布局文件:
<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="wrap_content" android:paddingTop="10dp" android:paddingBottom="10dp"> <ProgressBar android:id="@+id/progressBar" style="@style/myProgressBarStyle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:indeterminateBehavior="repeat" android:indeterminateDuration="700" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@id/tv_load" app:layout_constraintHorizontal_chainStyle="packed" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/tv_load" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center" android:paddingStart="20dp" android:paddingEnd="20dp" android:text="数据加载中..." app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@id/progressBar" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/tv_all_message" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center" android:paddingStart="20dp" android:paddingEnd="20dp" android:text="已加载全部数据" android:visibility="gone" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <Button android:id="@+id/btn_retry" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center" android:paddingStart="20dp" android:paddingEnd="20dp" android:text="重试" android:textSize="18sp" android:visibility="gone" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" tools:visibility="visible" />androidx.constraintlayout.widget.ConstraintLayout>
adapter 实现:
import android.view.LayoutInflaterimport android.view.Viewimport android.view.ViewGroupimport android.widget.ProgressBarimport android.widget.TextViewimport androidx.paging.LoadStateimport androidx.paging.LoadStateAdapterimport com.stone.stoneviewskt.Rimport com.stone.stoneviewskt.common.BaseViewHolderimport com.stone.stoneviewskt.util.logiclass LoadMoreAdapter(private val retryBlock: () -> Unit) : LoadStateAdapter<BaseViewHolder>() { override fun onBindViewHolder(holder: BaseViewHolder, loadState: LoadState) { when (loadState) { is LoadState.NotLoading -> { // 非加载中 logi("LoadMoreAdapter---onBindViewHolder---LoadState.NotLoading") holder.itemView.findViewById<ProgressBar>(R.id.progressBar).visibility = View.GONE holder.itemView.findViewById<TextView>(R.id.tv_load).visibility = View.GONE holder.itemView.findViewById<TextView>(R.id.tv_all_message).visibility = View.VISIBLE holder.itemView.findViewById<TextView>(R.id.btn_retry).visibility = View.GONE } is LoadState.Loading -> { // 加载中 holder.itemView.findViewById<ProgressBar>(R.id.progressBar).visibility = View.VISIBLE holder.itemView.findViewById<TextView>(R.id.tv_load).visibility = View.VISIBLE holder.itemView.findViewById<TextView>(R.id.tv_all_message).visibility = View.GONE holder.itemView.findViewById<TextView>(R.id.btn_retry).visibility = View.GONE } is LoadState.Error -> { holder.itemView.findViewById<ProgressBar>(R.id.progressBar).visibility = View.GONE holder.itemView.findViewById<TextView>(R.id.tv_load).visibility = View.GONE holder.itemView.findViewById<TextView>(R.id.tv_all_message).visibility = View.GONE val btnRetry = holder.itemView.findViewById<TextView>(R.id.btn_retry) btnRetry.visibility = View.VISIBLE btnRetry.text = "发生了错误:${loadState.error.message},点击重试" btnRetry.setOnClickListener { retryBlock() } } } } override fun onCreateViewHolder(parent: ViewGroup, loadState: LoadState): BaseViewHolder { val itemView = LayoutInflater.from(parent.context).inflate(R.layout.layout_load_more, parent, false) return BaseViewHolder(itemView) } // 是否作为 item 显示 override fun displayLoadStateAsItem(loadState: LoadState): Boolean { logi("loadState -- $loadState") return loadState is LoadState.Loading || loadState is LoadState.Error || (loadState is LoadState.NotLoading && loadState.endOfPaginationReached) // loadState.endOfPaginationReached false 表示有更多的数据要加载 }}
adapter 继承自 LoadStateAdapter,关联的数据对象 LoadState 有三个状态。
- LoadState.NotLoading 表示 非加载中,即加载完成时的状态。如果 displayLoadStateAsItem()中的实现是
|| (loadState is LoadState.NotLoading)
那通过日志会发现,在初次加载分页数据时,会发生一次 LoadState.NotLoading,在加载完成所有数据后,每次滑动到底部时,都会发生一次 LoadState.NotLoading。而改成如上实现|| (loadState is LoadState.NotLoading && loadState.endOfPaginationReached)
则初次加载分页数据时,不会发生 LoadState.NotLoading 。
所以,在onBindViewHolder()
中的实现是,NotLoading
时,显示加载完成文本框 - LoadState.Loading 表示 加载中的状态。显示 ProgressBar和一个文本消息框
- LoadState.Error 表示 加载错误的状态。其关联的是
PagingSource#load()
返回的LoadResult.Error
结果。
显示发生了错误:${loadState.error.message},点击重试
的文本框
displayLoadStateAsItem()
通过返回值判断 是否作为 item 显示。前面说 loadState is LoadState.NotLoading && loadState.endOfPaginationReached
非加载中&& 加载完成所有数据时 LoadState.NotLoading
状态才显示,即内部调用bindViewHolder()
。
空数据 adapter
import com.stone.stoneviewskt.Rimport com.stone.stoneviewskt.adapter.BaseRvAdapterimport com.stone.stoneviewskt.common.BaseViewHolderclass EmptyAdapter: BaseRvAdapter<String>(R.layout.layout_empty) { override fun fillData(holder: BaseViewHolder, position: Int, data: String) { }}
布局文件 里就一个文本框,text=“暂无数据”
分页数据源
import androidx.paging.PagingSourceimport androidx.paging.PagingStateimport com.stone.stoneviewskt.BuildConfigimport com.stone.stoneviewskt.data.CustomerDataimport com.stone.stoneviewskt.data.CustomerPageDataimport com.stone.stoneviewskt.util.logiimport com.stone.stoneviewskt.util.showShortimport kotlin.random.Randomclass PageListPageSource : PagingSource<Int, CustomerData>() { private var flagA = false private var flagB = false companion object { private var mockEmpty = true // 静态字段 模拟空数据 } override fun getRefreshKey(state: PagingState<Int, CustomerData>): Int? { return state.anchorPosition?.let { anchorPosition -> val anchorPage = state.closestPageToPosition(anchorPosition) anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1) } } override suspend fun load(params: LoadParams<Int>): LoadResult<Int, CustomerData> { val currentPage = params.key ?: 1 // 当前页码 val pageSize = params.loadSize // 每页多少条数据 var bean: CustomerPageData? = null // 分页数据 kotlin.runCatching { // 异常发生后,执行 onFailure {} // todo 真实开发时,移除模拟错误代码 // 模拟 第2页 一定抛异常 if (currentPage == 2 && !flagB) { flagA = true } if (flagA) { flagA = false flagB = true throw IllegalStateException("test-error") } if (currentPage % 2 == 0 && Random.nextInt() % 2 == 0) { // 当偶数页时,随机发生异常。 注意异常发生后,对请求的页码 -- currentPage是否有影响 throw IllegalStateException("test-error") }// Api.createRetrofit(CustomerApiService::class.java).customerList(currentPage,pageSize) // 真实网络请求方式 mockData(currentPage, pageSize)// todo 真实开发时,移除模拟数据代码 }.onSuccess { bean = it }.onFailure { it.printStackTrace() // 当数据加载中时,销毁了界面(比如--回退到上一页),就有很大概率触发 CancellationException // 跳过该异常 if (it !is java.util.concurrent.CancellationException) { showShort("出错了" + it.message) return LoadResult.Error(it) // 如果返回 error,那后续 加载更多(即底部上拉)将不会触发 } } val prevKey = if (currentPage > 1) currentPage - 1 else null // 当前页不是第一页的话,前一页为当前页减一 val hasMore = currentPage * pageSize < (bean?.totalCount ?: 0) val nextKey = if (hasMore) currentPage + 1 else null // 当前页有数据的话,下一页就是,当前页加一,反之为空 logi("currentPage:$currentPage pageSize:$pageSize prevKey:$prevKey nextKey:$nextKey") return try { LoadResult.Page( data = bean?.data ?: arrayListOf(), // 数据 prevKey = prevKey, // 为当前页的前一页 nextKey = nextKey // 为当前页的下一页 ) } catch (e: Exception) { e.printStackTrace() return LoadResult.Error(e) } } // 模拟分页数据 private fun mockData(pageIndex: Int, pageSize: Int): CustomerPageData? { if (mockEmpty) { mockEmpty = false return null } logi("分页参数:[pageIndex-$pageIndex] [pageSize-$pageSize]") val list = arrayListOf<CustomerData>() val totalCount = 55 val currentCount = if (totalCount > pageIndex * pageSize) pageSize else totalCount - (pageIndex - 1) * pageSize (1..currentCount).forEach { val currentPosition = (pageIndex - 1) * pageSize + it list.add(CustomerData("id-$currentPosition", "name-$currentPosition")) } return CustomerPageData(totalCount, list) }}
上面的实现中,有一些模拟数据和加载数据出错的代码,都有注释
ViewModel 提供加载数据源的方法
class PageListViewModel : ViewModel() { // todo 截止到 3.1.1版本,官方库有bug:超出 initialLoadSize 所在页码后, 再retry 没问题;反之retry,会数据重复 // todo 为了规避 建议 真实开发时,initialLoadSize 和 pageSize 的值相同。 或看看后续版本是否有修复 fun loadData(): Flow<PagingData<CustomerData>> { return Pager( config = PagingConfig( pageSize = 10, initialLoadSize = 15, // 初始取的 pageSize,通常要大于指定的 pageSize 但大于了,在retry时又会出现bug prefetchDistance = 1 // 滑动到底部的 prefetchDistance 条数据 时自动加载下一页(如果还有下一页) ), // 每页都会调用一次 pagingSource#load(), 请求数据 pagingSourceFactory = { PageListPageSource() } ).flow.cachedIn(viewModelScope) // 设置缓存 }}
因为 pagingSource 加载的bug,导致对应情形下的数据重复。想用
flow.distinctUntilChangedBy
操作符来去重,
可是 该flow类型是Flow
而不是> Flow
,所以然并卵…
结合以上实现的 Fragment
import android.os.Bundleimport androidx.lifecycle.ViewModelProviderimport androidx.lifecycle.lifecycleScopeimport androidx.paging.LoadStateimport androidx.recyclerview.widget.ConcatAdapterimport com.stone.stoneviewskt.Rimport com.stone.stoneviewskt.common.BaseBindFragmentimport com.stone.stoneviewskt.databinding.FragmentPageListBindingimport com.stone.stoneviewskt.util.logiimport com.stone.stoneviewskt.util.showShortimport kotlinx.coroutines.flow.collectLatestclass PageListFragment: BaseBindFragment<FragmentPageListBinding>(R.layout.fragment_page_list) { private val viewModel by lazy { ViewModelProvider(this)[PageListViewModel::class.java] } private lateinit var concatAdapter: ConcatAdapter // 可合并 adapter private val adapter by lazy { PageListAdapter() } private val loadMoreAdapter by lazy { LoadMoreAdapter { // 点击 重试按钮 adapter.retry() } } private val emptyAdapter: EmptyAdapter by lazy { EmptyAdapter() } override fun onPreparedView(savedInstanceState: Bundle?) { super.onPreparedView(savedInstanceState) concatAdapter = adapter.withLoadStateFooter(loadMoreAdapter) // 将 loadMoreAdapter 添加为 footer 效果 emptyAdapter.updateData(listOf("")) // 向 emptyAdapter 添加一项,当其显示时,只显示一个对应视图 mBind.rvPage.adapter = concatAdapter // 这时 已 添加了 loadMoreAdapter // 设置加载状态监听 adapter.addLoadStateListener { logi("监听到loadState -- $it") // 如果 状态是 未加载(加载完成也算) 或 发生错误 if (it.refresh is LoadState.NotLoading) { // 见下文分析 mBind.swipeRefresh.isRefreshing = false if (adapter.itemCount == 0) { // 真实数据为空 if (concatAdapter.adapters.contains(loadMoreAdapter)) { concatAdapter.removeAdapter(loadMoreAdapter) // 移除加载更多 logi("removeAdapter -- loadMoreAdapter") } if (!concatAdapter.adapters.contains(emptyAdapter)) { concatAdapter.addAdapter(emptyAdapter) // 添加空数据视图 仅在未添加过时才添加 } } else { if (concatAdapter.adapters.contains(emptyAdapter)) { concatAdapter.removeAdapter(emptyAdapter) logi("removeAdapter -- emptyAdapter") concatAdapter.addAdapter(loadMoreAdapter) // 只添加一次 loadMore } } } else if (it.refresh is LoadState.Error) { mBind.swipeRefresh.isRefreshing = false } } adapter.setItemClick { position, data -> showShort("点击查看 --- ${data.name}") } mBind.swipeRefresh.setOnRefreshListener { loadData() } mBind.swipeRefresh.isRefreshing = true loadData() } private fun loadData() { lifecycleScope.launchWhenResumed { viewModel.loadData().collectLatest { // 加载数据 adapter.submitData(it) // 绑定分页数据源 } } }}
- 绑定分页数据源的关键代码是
adapter.submitData(data)
- 分页数据源加载数据出错后,重试的关键代码是
adapter.retry()
adapter.withLoadStateFooter(loadMoreAdapter)
返回的是ConcatAdapter
(可合并adapter)。
将loadMoreAdapter
添加为footer
。通过ConcatAdapter
,在数据为空时,添加EmptyAdapter
- addLoadStateListener 状态监听,通过日志发现:
LoadState.NotLoading 每次数据加载开始和结束时都会发生。
当数据真实数据项为0时,如果有 加载更多视图 先移除,如果未 添加空数据视图,添加空数据视图。
当数据真实数据项为非0时,如果有 添加空数据视图,则移除空数据视图,并添加 加载更多视图。初始时,通过concatAdapter = adapter.withLoadStateFooter(loadMoreAdapter)
已添加过 加载更多视图。
如上,不会造成重复添加 空数据视图 或 加载更多视图
注意:这里的NotLoading
触发时机 和LoadMoreAdapter#displayLoadStateAsItem()
相应的实现没有关系
数据重复问题
截止到 3.1.1版本,官方库有bug:超出 initialLoadSize 所在页码后, 再retry 没问题;反之retry,会数据重复。比如 pageSize=10,initialLoadSize=15,初始加载15个数据后,当前页码对应 pageSize=10 的情形下是 2;这时再请求第2页,会把第二页的10个条目加载进来并显示,这就发生了数据重复。
为了规避该问题,建议 真实开发时,initialLoadSize 和 pageSize 的值相同。 或看看后续版本是否有修复
对 PagingSource
进行封装
定义好统一的 PageData 的数据字段格式后,不同的业务 都实现一次 PagingSource
,会有大量的重复的代码。因此需要封装一下
base 分页数据类
open class BasePageData
base 分页数据源
import androidx.paging.PagingSourceimport androidx.paging.PagingStateimport com.stone.stoneviewskt.BuildConfigimport com.stone.stoneviewskt.data.BasePageDataimport com.stone.stoneviewskt.util.logiimport com.stone.stoneviewskt.util.showShortabstract class BasePageSource<T: Any> : PagingSource<Int, T>() { override fun getRefreshKey(state: PagingState<Int, T>): Int? { return state.anchorPosition?.let { anchorPosition -> val anchorPage = state.closestPageToPosition(anchorPosition) anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1) } } abstract fun loadData(pageIndex: Int, pageSize: Int): BasePageData<T>? override suspend fun load(params: LoadParams<Int>): LoadResult<Int, T> { val currentPage = params.key ?: 1 // 当前页码 val pageSize = params.loadSize // 每页多少条数据 var bean: BasePageData<T>? = null // 分页数据 kotlin.runCatching { // 异常发生后,执行 onFailure {} loadData(currentPage, pageSize) }.onSuccess { bean = it }.onFailure { it.printStackTrace() // 当数据加载中时,销毁了界面(比如--回退到上一页),就有很大概率触发 CancellationException // 跳过该异常 if (it !is java.util.concurrent.CancellationException) { showShort("出错了" + it.message) return LoadResult.Error(it) // 如果返回 error,那后续 加载更多(即底部上拉)将不会触发 } } val prevKey = if (currentPage > 1) currentPage - 1 else null // 当前页不是第一页的话,前一页为当前页减一 val hasMore = currentPage * pageSize < (bean?.totalCount ?: 0) val nextKey = if (hasMore) currentPage + 1 else null // 当前页有数据的话,下一页就是,当前页加一,反之为空 logi("currentPage:$currentPage pageSize:$pageSize prevKey:$prevKey nextKey:$nextKey") return try { LoadResult.Page( data = bean?.data ?: arrayListOf(), // 数据 prevKey = prevKey, // 为当前页的前一页 nextKey = nextKey // 为当前页的下一页 ) } catch (e: Exception) { e.printStackTrace() return LoadResult.Error(e) } }}
具体的数据源实现
import com.stone.stoneviewskt.data.BasePageDataimport com.stone.stoneviewskt.data.CustomerDataimport com.stone.stoneviewskt.util.logiclass PageListPageSource2 : BasePageSource<CustomerData>() { override fun loadData(pageIndex: Int, pageSize: Int): BasePageData<CustomerData>? { return mockData(pageIndex, pageSize) } // 模拟分页数据 private fun mockData(pageIndex: Int, pageSize: Int): BasePageData<CustomerData> { logi("分页参数:[pageIndex-$pageIndex] [pageSize-$pageSize]") val list = arrayListOf<CustomerData>() val totalCount = 55 val currentCount = if (totalCount > pageIndex * pageSize) pageSize else totalCount - (pageIndex - 1) * pageSize (1..currentCount).forEach { val currentPosition = (pageIndex - 1) * pageSize + it list.add(CustomerData("id-$currentPosition", "name-$currentPosition")) } return BasePageData(totalCount, list) }}
欧了,松口气 ~~
# 对 Adapter
的管理,进行封装 (更新于 230705)
发现,在 UI 类 中应用时,总是要定义这几个adapter、进行初始化、设置加载状态监听等。
基于这些重复动作,所以需要进行封装,有了如下:
import androidx.paging.LoadStateimport androidx.paging.PagingDataAdapterimport androidx.recyclerview.widget.ConcatAdapterimport com.stone.stoneviewskt.common.BaseViewHolderimport com.stone.stoneviewskt.util.logiabstract class PageUiHelper<T : Any>(private val adapter: PagingDataAdapter<T, BaseViewHolder>) { private lateinit var concatAdapter: ConcatAdapter // 可合并 adapter val loadMoreAdapter by lazy { LoadMoreAdapter { // 点击 重试按钮 adapter.retry() } } val emptyAdapter: EmptyAdapter by lazy { EmptyAdapter() }abstract fun onNotLoadingOrErrorState() fun init() { concatAdapter = adapter.withLoadStateFooter(loadMoreAdapter) // 将 loadMoreAdapter 添加为 footer 效果 emptyAdapter.updateData(listOf("")) // 向 emptyAdapter 添加一项,当其显示时,只显示一个对应视图 // 设置加载状态监听 adapter.addLoadStateListener { logi("监听到loadState -- $it") // 如果 状态是 未加载(加载完成也算) 或 发生错误 if (it.refresh is LoadState.NotLoading) { onNotLoadingOrErrorState() if (adapter.itemCount == 0) { // 真实数据为空 if (concatAdapter.adapters.contains(loadMoreAdapter)) { concatAdapter.removeAdapter(loadMoreAdapter) // 移除加载更多 logi("removeAdapter -- loadMoreAdapter") } if (!concatAdapter.adapters.contains(emptyAdapter)) { concatAdapter.addAdapter(emptyAdapter) // 添加空数据视图 仅在未添加过时才添加 } } else { if (concatAdapter.adapters.contains(emptyAdapter)) { concatAdapter.removeAdapter(emptyAdapter) logi("removeAdapter -- emptyAdapter") concatAdapter.addAdapter(loadMoreAdapter) // 只添加一次 loadMore } } } else if (it.refresh is LoadState.Error) { onNotLoadingOrErrorState() } } } fun getMainAdapter() = concatAdapter}
ViewPager2 中使用发现的 bug(更新于 230719)
TabLayout
+ ViewPager2
的FragmentStateAdapter
中,需要创建 fragment 实例。
相关分页的系列操作就放在了该 Fragment中后,场景:在每切换一次 tab,都需要刷新每个 tab 对应的网络请求;之后,再在当前 tab 中加载更多的分页数据;再切换 tab,来回往复操作… 后来发现个 bug:有些 tab 仅加载一页后,loadMore 视图不显示了
解决方法:先是重置了adapter 的数据源绑定,后来发现,还是会有问题。又重置了所有 adapter 的实例,解决了…
private var isResetAdapter = false // 是否重置private val helper by lazy { object : PageUiHelper<ItemData>(adapter) { override fun onNotLoadingOrErrorState() { dismissLoadingDialog() if (isResetAdapter && adapter.itemCount > 0) { // 由于重置数据源后,若有新增数据,会排在最前面,rv 不会自动滚动,需要代码操作滚动 mBind.rvAll.scrollToPosition(0) isResetAdapter = false } } }}fun loadData() { viewLifecycleOwner.lifecycleScope.launchWhenResumed { helper.init() mBind.rvAll.adapter = helper.getMainAdapter() showLoadingDialog() isResetAdapter = true mViewModel.getAllData().collectLatest { // 加载数据 adapter.submitData(it) // 绑定分页数据源 } }}
来源地址:https://blog.csdn.net/jjwwmlp456/article/details/131142710
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341