【发布时间】:2021-08-18 13:13:14
【问题描述】:
在 ViewModel 中,我维护一个字符串列表。如果将执行 onAddTag 或 onRemoveTag,则 mutableState 将被刷新。这会导致重组。
问题是,如果我使用onRemoveTag 删除一个元素(例如第三个),更新的列表会到达我的“自定义可组合”(已调试)中,但渲染只会从列表中删除最后一项。
例子:
- 初始列表:1,2,3,4 -> onRemove(2) -> 更新列表:1,3,4 -> 渲染:1,2,3
- 初始列表:1,2,3,4 -> onRemove(1) -> 更新列表:2,3,4 -> 渲染:1,2,3
在LazyListScope.items 的文档中有这个key 参数说:
key - 代表项目的稳定且唯一的密钥工厂。不允许对列表中的多个项目使用相同的键。密钥的类型应该可以通过 Android 上的 Bundle 保存。如果传递了 null,则列表中的位置将代表键。当您指定键时,滚动位置将基于该键保持,这意味着如果您在当前可见项之前添加/删除项目,则具有给定键的项目将保留为第一个可见项。 p>
也许Row 做了类似的事情,它无法正确决定哪个元素需要删除,哪个元素只保留位置信息。但是如何解决呢?
我不想在这里使用LazyRow,因为我只有几个项目要渲染。除此之外,我想了解这个问题! :)
代码
视图模型
class MyViewModel : ViewModel() {
var selectedTags = mutableStateOf(listOf<String>())
fun onRemoveTag(tag: String) {
selectedTags.value = selectedTags.value.toMutableList().apply { remove(tag) }
}
fun onAddTag(tag: String) {
selectedTags.value = selectedTags.value.toMutableList().apply { add(tag) }
}
}
主屏幕
fun MainScreen(viewModel: MyViewModel) {
val selectedTags by remember { viewModel.selectedTags }
TagRow(tags = selectedTags)
}
自定义组合
@Composable
fun TagRow(
tags: List<String>,
modifier: Modifier = Modifier
) {
Row(modifier = modifier) {
tags.forEach {
Text(text = it)
}
}
}
希望有人知道如何解决这个问题!
问候, 克里斯
编辑
根据@Philip 的反馈我准备了一个独立的例子:
@Composable
fun StackOverflowPreview() {
val tags = remember { mutableStateListOf("1", "2", "3") }
Row {
tags.forEach {
AndroidView(factory = { context ->
EmojiTextView(context).apply {
textAlignment = View.TEXT_ALIGNMENT_CENTER
setTextColor(android.graphics.Color.BLACK)
layoutParams =
LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)
text = it
}
})
}
}
LaunchedEffect(key1 = tags) {
delay(2000)
tags.remove("1")
delay(2000)
tags.remove("2")
delay(2000)
tags.remove("3")
}
}
在这里你可以看到我使用了EmojiTextView。如果我用基本的Text 可组合替换该视图。它按预期工作。使用EmojiTextView,项目将始终从右到左移除。
编辑 2
我能够将我的问题缩小到 AndroidView 集成:
@Preview
@Composable
fun StackOverflowPreview() {
val tags = remember { mutableStateListOf("1", "2", "3") }
Row {
tags.forEach { tag ->
// does NOT work
// AndroidView(factory = { TextView(it).apply { text = tag } })
// works
AndroidView(factory = { TextView(it) }, update = { v -> v.text = tag })
}
}
LaunchedEffect(key1 = tags) {
delay(2000)
tags.remove("1")
delay(2000)
tags.remove("2")
delay(2000)
tags.remove("3")
}
}
【问题讨论】:
-
如果您希望您的问题得到解答,您需要让专家尽快获得具有可重现问题的工作样本。完美地,我应该将您的代码粘贴到我的示例项目中,并在我运行它时尽快看到问题。不确定
TagChip是什么,但使用Text,您的代码完全可以正常工作。请将其更新为minimal reproducible example。你可以用LaunchedEffect模拟用户动作,比如:加1,2,3,4,延迟1秒,删除2,这样我们就可以很容易看到问题了 -
您也可以将
mutableStateOf(listOf<String>())替换为mutableStateListOf<String>()以更轻松地处理修改。 -
那么你的问题到底是什么?
AndroidViewfactory 只被调用一次,并在任何下一次重组中重用。update在每次重组时都会被调用,这就是您需要更新AndroidView中的任何状态更改的方式 -
我的问题是一个移动的目标! :) 实现 AndroidView 集成的工作原理解决了我的问题。谢谢
标签: android kotlin android-jetpack-compose