androidandroid-jetpack-composeandroid-jetpack-compose-gesture

Prevent dragging box out of the screen with Jetpack Compose


I have such code from here: https://developer.android.com/jetpack/compose/gestures

    Box(modifier = Modifier.fillMaxSize()) {
        var offsetX by remember { mutableStateOf(0f) }
        var offsetY by remember { mutableStateOf(0f) }

        Box(
            Modifier
                .offset { IntOffset(offsetX.roundToInt(), offsetY.roundToInt()) }
                .background(Color.Red)
                .size(120.dp) // makes it rectangle. wrap_content without it
                .align(Alignment.BottomEnd)
                .pointerInput(Unit) {
                    detectDragGestures { change, dragAmount ->
                        change.consume()
                        offsetX += dragAmount.x
                        offsetY += dragAmount.y
                    }
                }

        ) {
            // todo
        }
    }

So for end side of x I create something like this:

val newOffsetX = if ((offsetX + dragAmount.x) < 0) { offsetX + dragAmount.x } else { 0 }
offsetX = newOffsetX

But how can I found start of x and prevent my draggable box go out of screen? Is there a way to do it for both X and Y?


Solution

  • If you align your draggable box with Alignment.TopStart you can coerce min and max width and height between 0 and parent size - box size

    @Composable
    private fun DragSample() {
    
        BoxWithConstraints(modifier = Modifier.fillMaxSize()) {
            var offsetX by remember { mutableStateOf(0f) }
            var offsetY by remember { mutableStateOf(0f) }
    
            val parentWidth = constraints.maxWidth
            val parentHeight = constraints.maxHeight
            Box(
                Modifier
                    .offset { IntOffset(offsetX.roundToInt(), offsetY.roundToInt()) }
                    .background(Color.Red)
                    .size(120.dp) // makes it rectangle. wrap_content without it
                    .align(Alignment.TopStart)
                    .pointerInput(Unit) {
    
                        val boxSize = this.size
                        detectDragGestures { _, dragAmount ->
                            offsetX = (offsetX + dragAmount.x).coerceIn(
                                0f,
                                parentWidth - boxSize.width.toFloat()
                            )
                            offsetY = (offsetY + dragAmount.y).coerceIn(
                                0f,
                                parentHeight - boxSize.height.toFloat()
                            )
                        }
                    }
    
            ) {
                // todo
            }
        }
    }
    

    If you wish to start from Alignemnt.BottomEnd you should do it as

    @Composable
    private fun DragSample() {
    
        BoxWithConstraints(modifier = Modifier.fillMaxSize()) {
            var offsetX by remember { mutableStateOf(0f) }
            var offsetY by remember { mutableStateOf(0f) }
    
            val parentWidth = constraints.maxWidth
            val parentHeight = constraints.maxHeight
            Box(
                Modifier
                    .offset { IntOffset(offsetX.roundToInt(), offsetY.roundToInt()) }
                    .background(Color.Red)
                    .size(120.dp) // makes it rectangle. wrap_content without it
                    .align(Alignment.BottomEnd)
                    .pointerInput(Unit) {
    
                        val boxSize = this.size
                        detectDragGestures { _, dragAmount ->
                            offsetX = (offsetX + dragAmount.x).coerceIn(
                                 boxSize.width.toFloat() -parentWidth,
                                0f
                            )
                            offsetY = (offsetY + dragAmount.y).coerceIn(
                                 boxSize.height.toFloat() -parentHeight,
                                0f
                            )
                        }
                    }
    
            ) {
                // todo
            }
        }
    }