我正在显示要按“距离”顺序显示的项目列表。然而,有时当一个项目的距离被更新,导致它移动到列表中的不同位置时,几个项目从列表的底部消失。再过一两次更新,它们又出现了。
我的排序策略是删除项目,在列表中向前或向后线性搜索它的新位置,然后插入它,因为我不希望项目一次移动超过一行或两行。
我只有一个线程更新列表。但可组合线程安全吗?或者我需要在编写列表时同步它吗?
我的视图模型
class TodoViewModel : ViewModel() {
private val _todos = emptyList<Todo>().toMutableStateList()
val todos: List<Todo>
get() = _todos
fun changeTitle(id: Int, title: String) {
var index = _todos.indexOfFirst { todo -> todo.id == id }
if (index == -1)
return
_todos[index] = _todos[index].copy(title = title)
}
fun delete(index: Int) {
_todos.removeAt(index)
}
fun changeDistance(id: Int, delta: Int) {
var index = _todos.indexOfFirst { todo -> todo.id == id }
if (index == -1)
return
val distance = _todos[index].distance + delta
val oldDistance = _todos[index].distance
Log.i("xx", "Update $id at $index from $oldDistance to $distance")
val newTodo = _todos[index].copy(distance = distance)
if ((index == 0 || distance >= _todos[index-1].distance) &&
(index == _todos.size-1 || distance <= _todos[index+1].distance)) {
_todos[index] = newTodo
return
}
Log.i("xx", "Move $id from $index")
delete(index)
while (index > 0 && distance < todos[index-1].distance) {
index--
}
while (index < todos.size && distance > todos[index].distance) {
index++
}
Log.i("xx", "Move $id to $index")
_todos.add(index, newTodo)
}
fun insert(todo: Todo) {
val index = todos.indexOfFirst { t -> t.id == todo.id }
if (index != -1) {
_todos[index] = todo
return
}
var newIndex = todos.binarySearch {t -> t.distance - todo.distance }
if (newIndex < 0) newIndex = (-newIndex - 1)
_todos.add(newIndex, todo)
}
}
字符串
主要活动
private val todoViewModel = TodoViewModel()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MaterialTheme {
TodoScreen(todoViewModel = todoViewModel)
}
}
var nextId = 0
val thread = Thread {
for (i in 0..10) {
todoViewModel.insert(Todo(nextId, "new $nextId", Random.nextInt(25, 50)))
nextId++
}
for (i in 0..100) {
Thread.sleep(500)
val functionNum = Random.nextInt(0, 100)
val index = Random.nextInt(0, todoViewModel.todos.size)
val id = todoViewModel.todos[index].id
todoViewModel.changeDistance(id, Random.nextInt(-10, 10))
}
}
thread.start()
}
}
@Composable
fun TodoScreen(
modifier: Modifier = Modifier,
todoViewModel: TodoViewModel = viewModel()
) {
Surface(color = MaterialTheme.colorScheme.background) {
TodoList(
modifier = modifier.fillMaxSize(),
list = todoViewModel.todos
)
}
}
@Composable
fun TodoList(
list: List<Todo>,
modifier: Modifier = Modifier
) {
LazyColumn(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
items(items = list, key = { todo -> todo.id }) { todo ->
TodoItem(todo = todo, modifier = Modifier.fillMaxWidth() )
}
}
}
@Composable
fun TodoItem(todo: Todo,
modifier: Modifier
) {
Card(
modifier = modifier,
elevation = CardDefaults.cardElevation(8.dp, 8.dp, 8.dp, 8.dp, 8.dp, 8.dp)
) {
Row(modifier = Modifier.padding(8.dp)) {
Text(text = todo.title)
Spacer(modifier = Modifier.weight(1f))
Text(text = todo.distance.toString())
}
}
}
型
1条答案
按热度按时间fcy6dtqo1#
我偶然发现了一个解决办法...在列表上进行同步,再加上使用itemsIndexed而不是Composable中的Items,就可以做到这一点。我不明白为什么ItemsIndexed会有什么不同,但我已经运行了数千次迭代,它没有崩溃。
此外,删除项似乎会导致Composable中的IndexOutofBounds异常。实际上,我通过不删除项目解决了这个问题。我将它们标记为已删除,这样就不会显示,然后在下一次插入时重新使用它们。(令人惊讶的是,用具有不同键的列表项替换列表项似乎是可以接受的)。
如果有人有更好的解决方案,我很想看看。
主活动待办列表函数
字符串
TodoViewModel
型