如何在MutableStateFlow<MutableList>中仅更新变更元素对应的UI组件?
实现方案
1. 先搞定StateFlow的列表更新逻辑
你已经知道要深拷贝列表,这里再强调下:必须用不可变列表(List<Int>)代替MutableList<Int>作为StateFlow的泛型,避免意外修改原列表导致StateFlow不触发更新。更新列表时生成全新的列表实例:
class MyViewModel : ViewModel() { private val _listFlow = MutableStateFlow<List<Int>>(emptyList()) val listFlow = _listFlow.asStateFlow() // 更新列表的正确姿势:生成新的不可变列表 fun updateList(newList: List<Int>) { _listFlow.value = newList.toList() } }
2. 两种实现“仅更新变化Fragment”的方式
方式一:让每个Fragment监听自己位置的元素流
在ViewModel中提供根据索引获取单个元素的StateFlow,每个Fragment只监听自己对应位置的元素变化:
class MyViewModel : ViewModel() { // ... 上面的listFlow代码 // 根据索引返回对应元素的StateFlow fun getElementFlow(index: Int): StateFlow<Int?> { return listFlow.map { list -> list.getOrNull(index) }.stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5000), initialValue = null ) } }
Fragment侧监听对应索引的流,只有元素变化时才更新UI:
class ItemFragment : Fragment() { private val index by lazy { arguments?.getInt("INDEX") ?: 0 } private val viewModel: MyViewModel by viewModels() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.getElementFlow(index).collect { value -> value?.let { binding.tvContent.text = it.toString() } } } } } companion object { fun newInstance(index: Int) = ItemFragment().apply { arguments = Bundle().apply { putInt("INDEX", index) } } } }
方式二:对比新旧列表,手动通知变化位置
如果用FragmentStateAdapter管理Fragment(比如ViewPager2场景),可以在ViewModel中对比新旧列表,找出变化的索引,再通知Adapter刷新对应位置:
class MyViewModel : ViewModel() { private val _listFlow = MutableStateFlow<List<Int>>(emptyList()) val listFlow = _listFlow.asStateFlow() // 用于发送变化的索引集合 private val _changedIndices = MutableSharedFlow<List<Int>>() val changedIndices = _changedIndices.asSharedFlow() fun updateList(newList: List<Int>) { val oldList = _listFlow.value val changedIndices = mutableListOf<Int>() val maxSize = maxOf(oldList.size, newList.size) // 遍历对比每个位置的元素 for (i in 0 until maxSize) { if (oldList.getOrNull(i) != newList.getOrNull(i)) { changedIndices.add(i) } } // 更新列表 _listFlow.value = newList.toList() // 发送变化索引 if (changedIndices.isNotEmpty()) { viewModelScope.launch { _changedIndices.emit(changedIndices) } } } }
Adapter侧监听变化索引,只刷新对应Fragment:
class ItemPagerAdapter(fragment: Fragment) : FragmentStateAdapter(fragment) { private val viewModel: MyViewModel by fragment.viewModels() private var currentList = emptyList<Int>() init { fragment.lifecycleScope.launch { fragment.repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.listFlow.collect { currentList = it notifyDataSetChanged() // 仅在列表长度变化时触发 } } } fragment.lifecycleScope.launch { fragment.repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.changedIndices.collect { indices -> indices.forEach { notifyItemChanged(it) } } } } } override fun getItemCount() = currentList.size override fun createFragment(position: Int) = ItemFragment.newInstance(position) }
3. 关键注意点
- 永远用不可变列表作为StateFlow的泛型,杜绝MutableList带来的意外修改问题;
- 用
repeatOnLifecycle监听Flow,避免内存泄漏,符合Jetpack生命周期最佳实践; - 如果Fragment数量动态变化,要确保索引始终对应正确的元素,避免越界。
内容的提问来源于stack exchange,提问作者yuz
相关产品推荐
相关产品推荐

