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

Android MVVM架构下RecyclerView更新数据时滚动位置跳转问题

解决 RecyclerView+MVVM 选中项导致滚动位置跳转的问题

这个问题我之前在项目里也踩过坑!核心原因就是你每次更新**整个List<ListItem>**的时候,RecyclerView会判定整个数据集发生了全局变化,触发全量刷新,最终导致滚动位置意外跳转。下面给你几个实用的解决方案:

1. 使用 DiffUtil 实现局部刷新

DiffUtil可以帮RecyclerView精准识别出哪些数据项发生了变化,只刷新对应的Item,而不是整个列表。

步骤:

  • 首先创建一个DiffUtil.Callback的实现类,对比新旧列表的差异:
class ListItemDiffCallback(
    private val oldList: List<ListItem>,
    private val newList: List<ListItem>
) : DiffUtil.Callback() {
    override fun getOldListSize() = oldList.size
    override fun getNewListSize() = newList.size

    override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        // 用唯一标识判断是不是同一个Item,比如id
        return oldList[oldItemPosition].id == newList[newItemPosition].id
    }

    override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        // 判断Item的内容是否一致,这里要包含isselected状态
        val oldItem = oldList[oldItemPosition]
        val newItem = newList[newItemPosition]
        return oldItem.isSelected == newItem.isSelected && oldItem.content == newItem.content
    }
}
  • 然后在Adapter里更新数据的时候,用DiffUtil计算差异并刷新:
fun updateData(newList: List<ListItem>) {
    val diffResult = DiffUtil.calculateDiff(ListItemDiffCallback(currentList, newList))
    currentList = newList
    diffResult.dispatchUpdatesTo(this)
}
  • ViewModel里更新LiveData的时候,依然可以传递新列表,但Adapter只会刷新状态变化的那个Item,不会影响滚动位置。

2. 直接更新单个Item的状态,而非整个列表

如果你的ListItem是可观察的(比如用ObservableField或者Data Binding的双向绑定),可以直接修改选中项的isSelected状态,让Data Binding自动刷新该Item,不需要更新整个LiveData列表。

示例:

  • 先让ListItem类的isSelected变成可观察的:
data class ListItem(
    val id: String,
    val content: String,
    val isSelected: ObservableBoolean = ObservableBoolean(false)
)
  • ViewModel里处理选中事件时,直接修改对应Item的状态:
fun onItemSelected(item: ListItem) {
    // 切换选中状态
    item.isSelected.set(!item.isSelected.get())
    // 不需要更新整个LiveData<List<ListItem>>
}
  • 在Item的布局里用Data Binding绑定状态:
<TextView
    android:id="@+id/tv_content"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="@{item.content}"
    android:background="@{item.isSelected ? @color/selected_color : @color/white}" />

这种方式更高效,完全不会触发列表的全局刷新,滚动位置自然不会跳。

3. 避免创建新的List实例,修改原有List的状态后通知更新

如果你不想用DiffUtil或者Data Binding,也可以在ViewModel里修改原有List中对应Item的isSelected状态,然后调用liveData.value = currentList(注意要保证List是同一个实例,但内部元素状态变化),不过这时候需要Adapter支持局部刷新,比如调用notifyItemChanged(position)。

注意:

这种方式需要你能获取到选中Item在列表中的位置,ViewModel里可能需要额外维护位置信息,相对前两种方式稍麻烦,但也是可行的。

总结

最推荐的是第一种+第二种结合:用DiffHandle处理列表的整体更新(比如加载更多、刷新数据),用可观察的Item状态处理单个选中事件,这样既保证性能,又能完美解决滚动跳转的问题。

内容的提问来源于stack exchange,提问作者itarill

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 07:30:27