Search code examples
androidkotlinandroid-jetpack-composeandroid-jetpack

First draggable item is not on top of other items in a Row while it's dragged on Jetpack Compose


I have a collection of five items, all of which are draggable. I want to achieve behavior in which the dragged item will overlap all the others. I tried changing the zIndex parameter but it didn't work, what am I doing wrong?

item code:

@Composable
fun <T> DragTarget(
dataToDrop: T,
content: @Composable () -> Unit) {

var dragOffset by remember { mutableStateOf(Offset.Zero) }
var currentPosition by remember { mutableStateOf(Rect.Zero) }
val currentState = LocalDragTargetInfo.current

Box(
    modifier = Modifier
        .zIndex(if (currentState.isDragging) 5f else 0f) // my first try
        .offset {
            IntOffset(
                dragOffset.x.roundToInt(),
                dragOffset.y.roundToInt()
            )
        }
        .onGloballyPositioned { layout ->
            layout
                .boundsInWindow()
                .let { rect ->
                    currentPosition = rect
                }
        }
        .pointerInput(dataToDrop) {
            detectDragGestures(
                onDragStart = {
                    currentState.isDragging = true
                    currentState.dataToDrop = dataToDrop
                    currentState.dragPosition = currentPosition
                },
                onDrag = { change, dragAmount ->
                    change.consume()
                    dragOffset += dragAmount
                    currentState.dragPosition = currentPosition
                },
                onDragEnd = {
                    currentState.isDragging = false
                    dragOffset = Offset.Zero
                    currentState.dragPosition = Rect.Zero
                }
            )
        },
    contentAlignment = Alignment.Center
) {
    content()
}}

item collection code

@Composable
fun TestView() {
Row(
    modifier = Modifier.fillMaxSize(),
    horizontalArrangement = Arrangement.spacedBy(16.dp, alignment = Alignment.CenterHorizontally),
    verticalAlignment = Alignment.CenterVertically
) {
    DragTarget(dataToDrop = null) {
        Box(modifier = Modifier.zIndex(5f) // my second try
                         .size(50.dp).background(Color.Blue))
    }
    DragTarget(dataToDrop = null) {
        Box(modifier = Modifier.size(50.dp).background(Color.Green))
    }
    DragTarget(dataToDrop = null) {
        Box(modifier = Modifier.size(50.dp).background(Color.LightGray))
    }
    DragTarget(dataToDrop = null) {
        Box(modifier = Modifier.size(50.dp).background(Color.Yellow))
    }
    DragTarget(dataToDrop = null) {
        Box(modifier = Modifier.size(50.dp).background(Color.Red))
    }
}}

and my DragTargetInfo:

class DragTargetInfo {
var isDragging by mutableStateOf(false)
var dragPosition by mutableStateOf(Rect.Zero)
var dataToDrop by mutableStateOf<Any?>(null)}

val LocalDragTargetInfo = compositionLocalOf { DragTargetInfo() }

I will be grateful for any tips as I have been trying to untangle this tangle for several days now and achieve the desired result. Peace to all friends)


Solution

  • One solution is moving zIndex into a state variable and making it independent of the currentState.isDragging and changing its value directly on onDragStart and onDragEnd lambdas.

    @Composable
    fun <T> DragTarget(
        dataToDrop: T,
        content: @Composable BoxScope.() -> Unit
    ) {
        var dragOffset by remember { mutableStateOf(Offset.Zero) }
        var currentPosition by remember { mutableStateOf(Rect.Zero) }
        val currentState = LocalDragTargetInfo.current
    
        var zIndex by remember { mutableFloatStateOf(0f) }
    
        Box(
            modifier = Modifier
                .zIndex(zIndex)
                .offset {
                    IntOffset(
                        dragOffset.x.roundToInt(),
                        dragOffset.y.roundToInt()
                    )
                }
                .onGloballyPositioned { layout ->
                    layout
                        .boundsInWindow()
                        .let { rect ->
                            currentPosition = rect
                        }
                }
                .pointerInput(dataToDrop) {
                    detectDragGestures(
                        onDragStart = {
                            currentState.isDragging = true
                            currentState.dataToDrop = dataToDrop
                            currentState.dragPosition = currentPosition
                            zIndex = 1f
                        },
                        onDrag = { change, dragAmount ->
                            change.consume()
                            dragOffset += dragAmount
                            currentState.dragPosition = currentPosition
                        },
                        onDragEnd = {
                            currentState.isDragging = false
                            dragOffset = Offset.Zero
                            currentState.dragPosition = Rect.Zero
                            zIndex = 0f
                        }
                    )
                },
            contentAlignment = Alignment.Center,
            content = content
        )
    }
    
    @Composable
    fun TestView() {
        Row(
            modifier = Modifier.fillMaxSize(),
            horizontalArrangement = Arrangement.spacedBy(
                16.dp,
                alignment = Alignment.CenterHorizontally
            ),
            verticalAlignment = Alignment.CenterVertically
        ) {
            DragTarget(dataToDrop = "Blue") {
                Box(
                    modifier = Modifier
                        .size(50.dp)
                        .background(Color.Blue)
                )
            }
            DragTarget(dataToDrop = "Green") {
                Box(
                    modifier = Modifier
                        .size(50.dp)
                        .background(Color.Green)
                )
            }
            DragTarget(dataToDrop = "Gray") {
                Box(
                    modifier = Modifier
                        .size(50.dp)
                        .background(Color.LightGray)
                )
            }
            DragTarget(dataToDrop = "Yellow") {
                Box(
                    modifier = Modifier
                        .size(50.dp)
                        .background(Color.Yellow)
                )
            }
            DragTarget(dataToDrop = "Red") {
                Box(
                    modifier = Modifier
                        .size(50.dp)
                        .background(Color.Red)
                )
            }
        }
    }