Изменяемое состояние списка не обновляется правильно

Я новичок в 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)) { a, data ->
                        val n = data?.get(0)
                        val i = data?.get(1)
                        val c = data?.get(2)


                        var color = if (a) 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)
//                        scaleX = 1.3f
//                        scaleY = 1.3f
                        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)
}

С помощью этого кода я смогу переместить черный квадрат из столбца в другой. Проблема в том, что если я, например, перемещаю первый элемент, это работает, но затем, если я попытаюсь переместить второй элемент (который теперь стал первым в списке), отображаемое значение будет таким же, как и у предыдущего элемента.

Пример вы можете увидеть на этой гифке:

Изменяемое состояние списка не обновляется правильно

Похоже, проблема в том, что значение, переданное в dataToDrop в DragTarget, не обновляется. Что я делаю не так?

Примечания: Jetpack Compose недавно представил модификаторы dragAndDropSource и dragAndDropTarget. Вы можете использовать их для достижения того же поведения. Подробнее об этом можно прочитать в этой публикации в блоге.

BenjyTec 03.05.2024 08:15
2
1
69
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я подозреваю, что ошибка возникает из-за того, как вы храните структуру данных items. В официальной документации сказано:

Внимание:
Использование изменяемых объектов, таких как ArrayList<T> или mutableListOf(), в качестве состояния в Compose, приводит к тому, что ваши пользователи видят неправильные или устаревшие данные в вашем приложении. Изменяемые объекты, которые не являются наблюдаемыми, такие как ArrayList или изменяемый класс данных, не наблюдаются Compose и не вызывают рекомпозицию при их изменении.

Пожалуйста, попробуйте вместо этого использовать mutableStateListOf():

val items = remember {
    mutableStateListOf(
        mutableStateListOf(5,6,7,8),
        mutableStateListOf(),
        mutableStateListOf(),
        mutableStateListOf()
    )
}

Кроме того, вам необходимо определить key для DragTarget Composables следующим образом:

items[column].forEachIndexed { index, item ->

    key(item) {
        DragTarget(
            //...
        ) {
            //...
        }
    }
}

Я сначала подумал, но это не сработало.

Leviathan 30.04.2024 12:54

Да, я подтверждаю, что это не работает. Самое странное то, что состояние обновляется для текста, который вы видите внутри черных плиток, но не для данных, передаваемых в dataToDrop.

Bernard Borio 30.04.2024 13:19

Другие вопросы по теме