Jetpack Compose 更新列表元素

5

我目前正在尝试为我的论文编写一个应用程序,并且正在探索不同的方法。由于我非常喜欢Flutter,而且这篇论文要求我使用Java/Kotlin,所以我想使用Jetpack Compose。

目前,我卡在了尝试更新ListElements上。

我想要一个显示实验及其状态/结果的列表。一旦我点击按钮,我想要实验开始运行,在执行完成后更新它们的状态。目前,run方法除了将状态设置为成功之外没有任何操作。 问题是,我不知道如何从ExperimentRow的viewModel中触发recompose一次实验更新其状态。

ExperimentsActivity:

class ExperimentsActivity : AppCompatActivity() {

val exViewModel by viewModels<ExperimentViewModel>()

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    //For now this is just Dummy Data and will be replaced
    exViewModel.experiments += listOf(
        Experiment("Test1", exViewModel::experimentStateChanged),
        Experiment("Strongbox", exViewModel::experimentStateChanged)
    )

    setContent {
        TpmTheme {
            // A surface container using the 'background' color from the theme
            Surface {
                ExperimentScreen(
                    exViewModel.experiments,
                    exViewModel::startTests
                )
            }
        }
    }
}

}

实验视图模型:

class ExperimentViewModel : ViewModel() {

    var experiments by mutableStateOf(listOf<Experiment>())



    fun startTests() {
        for (exp in experiments) {
            exp.run()
        }
    }

    fun experimentStateChanged(experiment: Experiment) {
        Log.i("ViewModel", "Changed expState of ${experiment.name} to ${experiment.state}")
        // HOW DO I TRIGGER A RECOMPOSE OF THE EXPERIMENTROW FOR THE experiment????
        //experiments = experiments.toMutableList().also { it.plus(experiment) }

        Log.i("Vi", "Size of Expirments: ${experiments.size}")
    }

}

实验屏幕:

@Composable
fun ExperimentScreen(
    experiments: List<Experiment>,
    onStartExperiments: () -> Unit
) {
    Column {
        LazyColumnFor(
            items = experiments,
            modifier = Modifier.weight(1f),
            contentPadding = PaddingValues(top = 8.dp),
        ) { ep ->
            ExperimentRow(
                experiment = ep,
                modifier = Modifier.fillParentMaxWidth(),
            )
        }

        Button(
            onClick = { onStartExperiments() },
            modifier = Modifier.padding(16.dp).fillMaxWidth(),
        ) {
            Text("Run Tests")
        }
    }
}


@Composable
fun ExperimentRow(experiment: Experiment, modifier: Modifier = Modifier) {


    Row(
        modifier = modifier
            .padding(horizontal = 16.dp, vertical = 8.dp),
        horizontalArrangement = Arrangement.SpaceBetween
    ) {
        Text(experiment.name)
        Icon(
            asset = experiment.state.vAsset,
        )
    }

实验:

class Experiment(val name: String, val onStateChanged: (Experiment) -> Unit) {
    var state: ExperimentState = ExperimentState.DEFAULT
    set(value) {
        field = value
        onStateChanged(this)
    }


    fun run() {

        state = ExperimentState.SUCCESS;
    }

}

enum class ExperimentState(val vAsset: VectorAsset) {
    DEFAULT(Icons.Default.Info),
    RUNNING(Icons.Default.Refresh),
    SUCCESS(Icons.Default.Done),
    FAILED(Icons.Default.Warning),
}
1个回答

3

解决这个问题的方法有几种,但重要的是需要将状态改变后的元素复制一份并添加到 experiments 中来触发重新组合。

一个可能的例子如下:

data class Experiment(val name: String, val state: ExperimentState,  val onStateChanged: (Experiment) -> Unit) {

    fun run() {
        onStateChanged(this.copy(state = ExperimentState.SUCCESS))
    }
}

然后

    fun experimentStateChanged(experiment: Experiment) {
        val index = experiments.toMutableList().indexOfFirst { it.name == experiment.name }
        experiments = experiments.toMutableList().also {
            it[index] = experiment
        }
    }

尽管我怀疑可能有更清晰的方法来完成这个任务。

谢谢您的回答。这个方法可行,但是我想知道如果我只更新了一个值,是否必须更新整个模型? - Patrick Schmidt
1
在Compose(以及其他地方)中的一般方法是尽可能支持不可变性,因此我认为这是预期的方法。例如,您可以在https://github.com/googlecodelabs/android-compose-codelabs/blob/main/StateCodelab/finished/src/main/java/com/codelabs/state/todo/TodoScreen.kt中看到类似的做法。 - John O'Reilly

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接