How to correctly drag the view around the circle?

Viewed 116

I want the green dot to follow the touch point in a circular path, but it doesn't seem to be doing it right.

Strange behavior GIF illustration

It seems like there is an unwanted offset somewhere but I can't find it on my own for quite some time.

Here is my code:

@Preview
@Composable
fun Test() {
    val touchPoint = remember { mutableStateOf(Offset.Zero) }

    Scaffold {
        Column() {
            Box(Modifier.height(100.dp).fillMaxWidth().background(Color.Blue))
            Layout(
                modifier = Modifier.aspectRatio(1f).fillMaxSize(),
                content = {
                    Box(
                        Modifier
                            .size(48.dp)
                            .clip(CircleShape)
                            .background(Color.Green)
                            .pointerInput(Unit) {
                                detectDragGestures(
                                    onDrag = { change, dragAmount ->
                                        change.consumeAllChanges()
                                        touchPoint.value += dragAmount
                                    }
                                )
                            }
                    )
                }
            ) { measurables, constraints ->
                val dot = measurables.first().measure(constraints.copy(minHeight = 0, minWidth = 0))

                val width = constraints.maxWidth
                val height = constraints.maxHeight

                val centerX = width / 2
                val centerY = height / 2
                val lengthFromCenter = width / 2 - dot.width / 2

                val touchX = touchPoint.value.x
                val touchY = touchPoint.value.y

                layout(width, height) {
                    // I planned to achieve the desired behaviour with the following steps:

                    // 1. Convert cartesian coordinates to polar ones
                    val r = sqrt(touchX.pow(2) + touchY.pow(2))
                    val angle = atan2(touchY.toDouble(), touchX.toDouble())

                    // 2. Use fixed polar radius
                    val rFixed = lengthFromCenter
                    
                    // 3. Convert it back to cartesian coordinates
                    val x = rFixed * cos(angle)
                    val y = rFixed * sin(angle)
                    
                    // 4. Layout on screen
                    dot.place(
                        x = (x + centerX - dot.width / 2).roundToInt(),
                        y = (y + centerY - dot.height / 2).roundToInt()
                    )
                }
            }
            Box(Modifier.fillMaxSize().background(Color.Blue))
        }
    }
}

I'm definitely missing something but don't know what exactly. What am I doing wrong?

1 Answers
touchPoint.value += dragAmount

Is in pixel values, and you're updating the position of the dot with pixel values, where it requires dp values. If you update that with

private fun Float.pxToDp(context: Context): Dp = // Float or Int, depends on the value you have, or Double
    (this / context.resources.displayMetrics.density).dp

The amount with which it will be moved, will be smaller and reflect the dragging made by the user

Related