kotlin 编写排序列表:项目消失和重新出现

svmlkihl  于 2023-08-07  发布在  Kotlin
关注(0)|答案(1)|浏览(139)

我正在显示要按“距离”顺序显示的项目列表。然而,有时当一个项目的距离被更新,导致它移动到列表中的不同位置时,几个项目从列表的底部消失。再过一两次更新,它们又出现了。
我的排序策略是删除项目,在列表中向前或向后线性搜索它的新位置,然后插入它,因为我不希望项目一次移动超过一行或两行。
我只有一个线程更新列表。但可组合线程安全吗?或者我需要在编写列表时同步它吗?
我的视图模型

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())
        }
    }
}

fcy6dtqo

fcy6dtqo1#

我偶然发现了一个解决办法...在列表上进行同步,再加上使用itemsIndexed而不是Composable中的Items,就可以做到这一点。我不明白为什么ItemsIndexed会有什么不同,但我已经运行了数千次迭代,它没有崩溃。
此外,删除项似乎会导致Composable中的IndexOutofBounds异常。实际上,我通过不删除项目解决了这个问题。我将它们标记为已删除,这样就不会显示,然后在下一次插入时重新使用它们。(令人惊讶的是,用具有不同键的列表项替换列表项似乎是可以接受的)。
如果有人有更好的解决方案,我很想看看。
主活动待办列表函数

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun TodoList(
    viewModel: TodoViewModel,
    modifier: Modifier = Modifier
) {
    with(viewModel) {
        if (todos.isEmpty()) {
            Text("No data")
        } else
            LazyColumn(
                state = rememberLazyListState(),
                modifier = modifier,
            ) {
                synchronized(viewModel.todos) {
                    itemsIndexed(items = viewModel.todos) { _, item ->
                        if (!item.deleted)
                            TodoItem(
                                todo = item,
                                modifier = Modifier
                                    .fillMaxWidth()
                                    .animateItemPlacement()
                            )
                    }
                }
            }
    }
}

字符串
TodoViewModel

class TodoViewModel : ViewModel() {
    private val _todos = emptyList<Todo>().toMutableStateList()
    val todos: List<Todo>
        get() = _todos

    fun changeTitle(id: Int, title: String) {
        synchronized(todos) {
            val index = _todos.indexOfFirst { todo -> todo.id == id }
            if (index == -1)
                return
            _todos[index] = todos[index].copy(title = title)
        }
    }

    fun deleteAt(index: Int) {
        synchronized(todos) {
            _todos[index] = _todos[index].copy(deleted = true)
        }
    }

    fun changeDistance(id: Int, distance: Int) {
        synchronized(todos) {
            val from = _todos.indexOfFirst { todo -> todo.id == id }
            if (from == -1) {
                Log.e("xx", "Not found: $id")
                return
            }
            val newTodo = _todos[from].copy(distance = distance)
            val oldDistance = _todos[from].distance
            var to = from
            if (distance < oldDistance) {
                while (to > 0 && distance < _todos[to - 1].distance) {
                    to--
                }
            } else {
                while (to < _todos.size && distance > _todos[to].distance) {
                    to++
                }
            }
            if (to == from)
                _todos[to] = newTodo
            else {
                _todos.removeAt(from)
                _todos.add(if (to > from) to -1 else to, newTodo)
            }
//            Log.i("xx", "Move " + newTodo.id + " ($oldDistance -> $distance) from $from -> $to")
        }
    }

    fun replaceIndexed(from: Int, newTodo: Todo) {
            val oldDistance = _todos[from].distance
            var to = from
            if (newTodo.distance < oldDistance) {
                while (to > 0 && newTodo.distance < _todos[to - 1].distance) {
                    to--
                }
            } else {
                while (to < _todos.size && newTodo.distance > _todos[to].distance) {
                    to++
                }
            }
            if (to == from)
                _todos[to] = newTodo
            else {
                _todos.removeAt(from)
                _todos.add(if (to > from) to -1 else to, newTodo)
            }
//            Log.i("xx", "Move " + newTodo.id + " ($oldDistance -> $distance) from $from -> $to")
    }

    fun insert(todo: Todo) {
        synchronized(todos) {
            val index = _todos.indexOfFirst { t -> t.id == todo.id }
            if (index != -1) {
                _todos[index] = todo
                return
            }
            val delIndex = _todos.indexOfFirst { t -> t.deleted }
            if (delIndex != -1) {
                replaceIndexed(delIndex, todo)
                return
            }
            var newIndex = _todos.binarySearch { t -> t.distance - todo.distance }
            if (newIndex < 0) newIndex = (-newIndex - 1)
            Log.i("xx", "Adding " + todo.id + " (" + todo.distance + ")")
            _todos.add(newIndex, todo)
        }
    }
}

相关问题