You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 07:38:05