Android Kotlin:如何不重建Adapter更新RecyclerView传入的数据?
解决RecyclerView数据更新问题(不重建Adapter)
核心问题分析
你之前的问题根源在于:直接在Fragment中修改ViewModel的items集合(同一MutableList引用),StateFlow不会感知到集合内容的变化(因为StateFlow是基于值相等判断是否发射新数据,引用不变的话不会触发发射),所以必须手动调用setRecyclerView,但这种全量更新容易导致重复、闪烁等问题。
正确的实现方式
1. 让ViewModel管理数据变更,发射不可变集合
把ViewModel中的items改为发射不可变List,所有数据修改逻辑放到ViewModel内,每次修改都生成新的集合实例,确保StateFlow能正确发射新值:
// ViewModel示例 class MyViewModel : ViewModel() { // 用MutableStateFlow存储不可变List private val _items = MutableStateFlow<List<Item>>(emptyList()) val items: StateFlow<List<Item>> = _items // 添加条目的方法,生成新集合触发StateFlow发射 fun addItem(newItem: Item) { _items.update { oldList -> oldList + newItem } } // 其他修改逻辑(删除、更新)同理,都生成新集合 fun updateItem(updatedItem: Item) { _items.update { oldList -> oldList.map { if (it.id == updatedItem.id) updatedItem else it } } } }
2. 给RecyclerView Adapter实现高效更新逻辑
不要每次重建Adapter或全量设置数据,而是给Adapter添加submitList方法,用DiffUtil计算新旧数据差异,只更新变化的条目:
// Adapter示例 class MyAdapter : RecyclerView.Adapter<MyAdapter.MyViewHolder>() { private var currentItems = emptyList<Item>() inner class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { fun bind(item: Item) { // 绑定数据逻辑,会在条目需要更新时自动调用 itemView.tvTitle.text = item.title // ...其他视图绑定 } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { val view = LayoutInflater.from(parent.context).inflate(R.layout.item_layout, parent, false) return MyViewHolder(view) } override fun onBindViewHolder(holder: MyViewHolder, position: Int) { holder.bind(currentItems[position]) } override fun getItemCount(): Int = currentItems.size // 提交新列表,用DiffUtil高效更新 fun submitList(newItems: List<Item>) { val diffCallback = ItemDiffCallback(currentItems, newItems) val diffResult = DiffUtil.calculateDiff(diffCallback) currentItems = newItems diffResult.dispatchUpdatesTo(this) } } // DiffUtil回调类,判断条目是否相同、内容是否变化 class ItemDiffCallback(private val oldList: List<Item>, private val newList: List<Item>) : DiffUtil.Callback() { override fun getOldListSize(): Int = oldList.size override fun getNewListSize(): Int = newList.size override fun areItemsTheSame(oldPos: Int, newPos: Int): Boolean { // 用条目的唯一标识判断是否为同一个条目(比如id) return oldList[oldPos].id == newList[newPos].id } override fun areContentsTheSame(oldPos: Int, newPos: Int): Boolean { // 判断条目内容是否一致(可重写Item的equals方法,或手动比较字段) return oldList[oldPos] == newList[newPos] } }
3. Fragment中订阅StateFlow,更新Adapter
在Fragment的collect回调中,只需调用Adapter的submitList方法,无需重建Adapter或全量设置:
// Fragment示例 class MyFragment : Fragment() { private lateinit var adapter: MyAdapter private val viewModel: MyViewModel by viewModels() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) adapter = MyAdapter() recyclerView.adapter = adapter // 订阅StateFlow,自动更新列表 lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.items.collect { newItems -> adapter.submitList(newItems) } } } // 示例:添加条目时调用ViewModel的方法,而非直接修改集合 btnAddItem.setOnClickListener { viewModel.addItem(Item(id = System.currentTimeMillis(), title = "新条目")) } } }
为什么这样有效?
- ViewModel中每次修改数据都生成新的不可变List,StateFlow会自动感知到值变化并发射新数据,确保Fragment能收到更新。
- DiffUtil会计算新旧列表的差异,只通知Adapter更新变化的条目,避免全量刷新导致的重复、闪烁问题,同时
onBindViewHolder会自动为需要更新的条目重新绑定数据。
内容的提问来源于stack exchange,提问作者onesector
相关产品推荐
相关产品推荐

