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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 09:57:36