基于LiveData与RecyclerView的自定义分页无限滚动实现疑问
不用新Paging库实现无限滚动:LiveData与Adapter的几种配合方案
嘿,我之前在项目里也这么干过——没用到最新的Paging库,靠滚动监听触发加载更多,用LiveData传数据。其实这几种处理方式各有优劣,咱们挨个聊:
方案1:LiveData推送增量数据,Adapter实现addAll方法
这是我最常用的方案,性能最优,因为不用刷新整个列表,只插入新数据。
首先在你的RecyclerView Adapter里加个专门的方法:
class YourAdapter : RecyclerView.Adapter<YourViewHolder>() { private val items = mutableListOf<YourDataType>() // 替换成你自己的ViewHolder和绑定逻辑 override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): YourViewHolder { ... } override fun onBindViewHolder(holder: YourViewHolder, position: Int) { ... } override fun getItemCount() = items.size // 核心方法:添加新数据并局部刷新 fun addAll(newItems: List<YourDataType>) { val startPos = items.size items.addAll(newItems) notifyItemRangeInserted(startPos, newItems.size) } // 可选:初始化列表(比如首次加载) fun setItems(newItems: List<YourDataType>) { items.clear() items.addAll(newItems) notifyDataSetChanged() } }
然后在ViewModel里,维护一个加载状态(避免重复请求),每次请求到新数据后,只推送这一批增量数据:
class YourViewModel : ViewModel() { private val _loadMoreData = MutableLiveData<List<YourDataType>>() val loadMoreData: LiveData<List<YourDataType>> = _loadMoreData private var isLoading = false private var currentPage = 1 fun loadNextPage() { if (isLoading) return // 防止重复请求 isLoading = true // 模拟网络请求,替换成你实际的API调用 viewModelScope.launch(Dispatchers.IO) { try { val newItems = apiService.getPage(currentPage) _loadMoreData.postValue(newItems) currentPage++ } catch (e: Exception) { // 处理加载失败逻辑,比如通知UI显示错误 } finally { isLoading = false } } } }
在UI层(比如Activity/Fragment)观察LiveData:
viewModel.loadMoreData.observe(this) { newItems -> adapter.addAll(newItems) }
优点:局部刷新,性能好,不会出现列表闪屏;缺点:需要区分首次加载和加载更多的逻辑(首次加载用setItems,加载更多用addAll)。
方案2:LiveData推送合并后的完整列表
这种方案逻辑更简单,ViewModel里维护整个数据集,每次加载更多后把新数据合并进去,再推送完整列表。
ViewModel代码:
class YourViewModel : ViewModel() { private val _fullDataList = mutableListOf<YourDataType>() private val _liveData = MutableLiveData<List<YourDataType>>() val liveData: LiveData<List<YourDataType>> = _liveData private var isLoading = false private var currentPage = 1 fun loadNextPage() { if (isLoading) return isLoading = true viewModelScope.launch(Dispatchers.IO) { try { val newItems = apiService.getPage(currentPage) _fullDataList.addAll(newItems) _liveData.postValue(_fullDataList.toList()) // 转成不可变列表,避免外部修改 currentPage++ } catch (e: Exception) { // 错误处理 } finally { isLoading = false } } } // 首次加载可以单独写方法,或者在init里调用 fun initLoad() { // 清空旧数据,加载第一页 _fullDataList.clear() currentPage = 1 loadNextPage() } }
UI层观察时,直接替换Adapter的整个列表:
viewModel.liveData.observe(this) { fullList -> adapter.setItems(fullList) }
优点:逻辑简单,ViewModel统一管理所有数据,UI层不用区分首次加载和加载更多;缺点:每次都刷新整个列表,数据量大时可能有性能问题,列表会闪屏。
方案3:用包装类区分更新类型
如果你的场景里有多种状态(比如下拉刷新、加载更多、首次加载),可以用密封类包装数据,让LiveData推送不同的更新事件。
先定义密封类:
sealed class DataUpdate { data class InitialLoad(val data: List<YourDataType>) : DataUpdate() data class LoadMore(val data: List<YourDataType>) : DataUpdate() data class Refresh(val data: List<YourDataType>) : DataUpdate() object LoadFailed : DataUpdate() }
ViewModel里推送对应的事件:
class YourViewModel : ViewModel() { private val _dataUpdate = MutableLiveData<DataUpdate>() val dataUpdate: LiveData<DataUpdate> = _dataUpdate private var isLoading = false private var currentPage = 1 fun loadNextPage() { if (isLoading) return isLoading = true viewModelScope.launch(Dispatchers.IO) { try { val newItems = apiService.getPage(currentPage) _dataUpdate.postValue(DataUpdate.LoadMore(newItems)) currentPage++ } catch (e: Exception) { _dataUpdate.postValue(DataUpdate.LoadFailed) } finally { isLoading = false } } } fun refresh() { isLoading = true viewModelScope.launch(Dispatchers.IO) { try { val newItems = apiService.getPage(1) _dataUpdate.postValue(DataUpdate.Refresh(newItems)) currentPage = 2 } catch (e: Exception) { _dataUpdate.postValue(DataUpdate.LoadFailed) } finally { isLoading = false } } } }
UI层根据事件类型处理:
viewModel.dataUpdate.observe(this) { update -> when (update) { is DataUpdate.InitialLoad -> adapter.setItems(update.data) is DataUpdate.LoadMore -> adapter.addAll(update.data) is DataUpdate.Refresh -> adapter.setItems(update.data) DataUpdate.LoadFailed -> { // 显示错误提示,比如Toast或者重试按钮 } } }
优点:灵活性极高,能覆盖各种场景;缺点:需要额外定义密封类,代码量稍多。
几个关键注意点
- 防止重复请求:一定要加
isLoading标志位,滚动到底部时先判断是否正在加载,避免多次触发请求。 - 数据一致性:ViewModel里的列表最好用不可变列表推送给UI(比如
toList()),避免UI层意外修改数据。 - 错误重试:加载失败时,要给用户重试的入口,比如在列表底部显示一个“重试”按钮,点击后重新调用
loadNextPage()。 - 滚动监听优化:RecyclerView的滚动监听可以用
addOnScrollListener,判断最后一个可见项是否是列表末尾:
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { super.onScrolled(recyclerView, dx, dy) val layoutManager = recyclerView.layoutManager as LinearLayoutManager val lastVisibleItemPos = layoutManager.findLastVisibleItemPosition() if (lastVisibleItemPos == adapter.itemCount - 1 && !viewModel.isLoading) { viewModel.loadNextPage() } } })
内容的提问来源于stack exchange,提问作者Florencia Ferrari
相关产品推荐
相关产品推荐

