Jetpack Compose拖拽列表可变状态更新异常问题求助
Jetpack Compose多列拖拽列表项数据显示异常问题
我是Jetpack Compose新手,正在实现一个可在多列间拖拽移动列表项的界面,编写了如下代码。预期功能是将黑色方块在列间移动,但实际问题是:移动第一个列表项正常,但若移动后续项(原第二项变为列表第一项),显示的值与之前移动的项相同,如动画所示:

问题代码
TaskColumns.kt
@Preview @Composable fun TaskColumns() { var columnWidth by remember { mutableStateOf(0.dp) } val density = LocalDensity.current val items = remember { mutableListOf( mutableListOf(5,6,7,8), mutableListOf(), mutableListOf(), mutableListOf()) } Surface( Modifier.fillMaxSize() ) { LongPressDraggable { Row(Modifier.padding(4.dp)) { repeat(4){ val column = it DropTarget<IntArray>(modifier = Modifier.weight(1f)) { isInBound, data -> val n = data?.get(0) val i = data?.get(1) val c = data?.get(2) var color = if (isInBound) Color.Green else Color.Blue if (i != null && n != null && c != null) { if (c != column){ Log.d("DND", "${data[0]}, ${data[1]}") items[column].add(n) items[c].remove(n) } color = Color.Blue } Column( Modifier .fillMaxHeight() .fillMaxWidth() .padding(4.dp) .background(color) .onPlaced { columnWidth = with(density) { it.size.width.toDp() } } ) { items[column].forEachIndexed { index, item -> DragTarget( modifier = Modifier, dataToDrop = intArrayOf(item, index, column), onDragCustomAction = { Log.d("DND", "$item") } ) { Column( Modifier .size(columnWidth) .aspectRatio(1f / 1f) .background(Color.Black), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { Text(text = "$item", color = Color.White) } } Spacer(modifier = Modifier.height(8.dp)) } } } } } } } }
DragAndDrop.kt
internal val LocalDragTargetInfo = compositionLocalOf { DragTargetInfo() } @Composable fun LongPressDraggable( modifier: Modifier = Modifier, content: @Composable BoxScope.() -> Unit ) { val state = remember { DragTargetInfo() } CompositionLocalProvider( LocalDragTargetInfo provides state ) { Box(modifier = modifier.fillMaxSize(), contentAlignment = Alignment.TopStart) { content() if (state.isDragging) { var targetSize by remember { mutableStateOf(IntSize.Zero) } Box(modifier = Modifier .graphicsLayer { val offset = (state.dragPosition + state.dragOffset) alpha = if (targetSize == IntSize.Zero) 0f else .9f translationX = offset.x.minus(targetSize.width / 2) translationY = offset.y.minus(targetSize.height / 2) } .onGloballyPositioned { targetSize = it.size } ) { state.draggableComposable?.invoke() } } } } } @Composable fun <T> DragTarget( modifier: Modifier, dataToDrop: T, onDragCustomAction: ()->Unit = {}, onDragCancelCustomAction: ()->Unit = {}, content: @Composable () -> Unit ) { var currentPosition by remember { mutableStateOf(Offset.Zero) } val currentState = LocalDragTargetInfo.current Box(modifier = modifier .onGloballyPositioned { currentPosition = it.localToWindow(Offset.Zero) } .pointerInput(Unit) { detectDragGesturesAfterLongPress(onDragStart = { currentState.dataToDrop = dataToDrop currentState.isDragging = true currentState.dragPosition = currentPosition + it currentState.draggableComposable = content }, onDrag = { change, dragAmount -> change.consume() currentState.dragOffset += Offset(dragAmount.x, dragAmount.y) onDragCustomAction() }, onDragEnd = { currentState.isDragging = false currentState.dragOffset = Offset.Zero }, onDragCancel = { onDragCancelCustomAction() currentState.dragOffset = Offset.Zero currentState.isDragging = false }) }) { content() } } @Composable fun <T> DropTarget( modifier: Modifier, content: @Composable BoxScope.(isInBound: Boolean, data: T?) -> Unit ) { val dragInfo = LocalDragTargetInfo.current val dragPosition = dragInfo.dragPosition val dragOffset = dragInfo.dragOffset var isCurrentDropTarget by remember { mutableStateOf(false) } Box(modifier = modifier.onGloballyPositioned { it.boundsInWindow().let { rect -> isCurrentDropTarget = rect.contains(dragPosition + dragOffset) } }) { val data = if (isCurrentDropTarget && !dragInfo.isDragging) dragInfo.dataToDrop as T? else null content(isCurrentDropTarget, data) } } internal class DragTargetInfo { var isDragging: Boolean by mutableStateOf(false) var dragPosition by mutableStateOf(Offset.Zero) var dragOffset by mutableStateOf(Offset.Zero) var draggableComposable by mutableStateOf<(@Composable () -> Unit)?>(null) var dataToDrop by mutableStateOf<Any?>(null) }
问题原因与解决方案
核心问题分析
- Compose状态感知缺失:使用普通
mutableListOf存储列数据,Compose无法感知列表内容的修改,导致数据变化后界面不重组。 - 列表项标识缺失:
forEachIndexed生成DragTarget时未指定唯一key,Compose无法区分不同列表项,重组时复用旧节点,数据未更新。 - 代码语法错误:原DragAndDrop.kt中存在函数定义不完整的语法问题,影响逻辑执行。
修改方案
- 替换为可观察列表:将
mutableListOf替换为mutableStateListOf,让Compose追踪列表变化:
val items = remember { mutableStateListOf( mutableStateListOf(5,6,7,8), mutableStateListOf(), mutableStateListOf(), mutableStateListOf() ) }
- 为DragTarget添加唯一Key:在
forEachIndexed中给DragTarget的Modifier添加key,确保每个列表项被正确识别:
items[column].forEachIndexed { index, item -> DragTarget( modifier = Modifier.key("$column-$index-$item"), // 用列索引+项索引+值组合作为唯一key dataToDrop = intArrayOf(item, index, column), onDragCustomAction = { Log.d("DND", "$item") } ) { // 原有内容不变 } Spacer(modifier = Modifier.height(8.dp)) }
- 修正语法错误:完善DragAndDrop.kt中所有函数的定义(比如补全
-> Unit、修复变量类型定义),确保代码能正常编译运行。
修改后效果
- 拖拽列表项后,
mutableStateListOf会触发Compose重组,界面实时更新数据。 - 唯一
key让Compose正确区分每个DragTarget,避免复用错误节点,保证拖拽数据与显示内容一致。
内容的提问来源于stack exchange,提问作者Bernard Borio
相关产品推荐
相关产品推荐

