Search code examples
android-emulatorandroid-jetpack-compose

Wrong position send to MotionEvent


I have a code, that draws a line from the box to the cursor. Everything worked well, until I decided to Modulize the code from the monolith. Motion event gives incorrect values of y coordinate of cursor.

Here is the code I use to draw:

    val positions = remember {
        mutableStateMapOf<Position, Position>()
    }
    val list = remember {
        mutableStateListOf<PositionRange>()
    }
    Canvas(modifier = Modifier.fillMaxSize(), onDraw = {
        positions.forEach {
            val startPosition = it.key
            val endPosition = it.value
            if (startPosition.isNotEmpty() && endPosition.isNotEmpty()) {
                this.drawLine(
                    Color.Black,
                    Offset(startPosition.x, startPosition.y),
                    Offset(endPosition.x, endPosition.y),
                    10f,
                    StrokeCap.Round,
                )
            }
        }
    })

Here how I take position of the touch:

@Composable
private fun FromBox(
    list: MutableList<PositionRange>,
    onDragLineEvent: (Pair<Position, Position>) -> Unit,
) {
    var startPosition by Position.rememberEmptyPosition()
    var endPosition by Position.rememberEmptyPosition()
    Box(
        modifier = Modifier
            .onGloballyPositioned {
                val size = it.size
                val coords = it.positionInRoot()
                val posRange =
                    PositionRange(coords.toPosition(), size.width.toFloat(), size.height.toFloat())
                startPosition = posRange.getCenter()
                onDragLineEvent(startPosition to endPosition)
            }
            .pointerInteropFilter {
                if (it.pointerCount == 1) {
                    when (it.action) {
                        MotionEvent.ACTION_MOVE -> {
                            val pos = Position(it.x, it.y)
                            list.forEach { posRange ->
                                if (posRange.inRange(pos)) {
                                    val center = posRange.getCenter()
                                    endPosition = endPosition.copy(
                                        x = center.x,
                                        y = center.y
                                    )
                                    onDragLineEvent(startPosition to endPosition)
                                    return@forEach
                                } else {
                                    endPosition = endPosition.copy(x = pos.x, y = pos.y)
                                    onDragLineEvent(startPosition to endPosition)
                                }
                            }
                        }

                        MotionEvent.ACTION_UP,
                        MotionEvent.ACTION_CANCEL,
                        -> {
                            endPosition = Position.EMPTY
                            onDragLineEvent(startPosition to endPosition)
                        }
                    }
                } else {
                    endPosition = Position.EMPTY
                }
                true
            }
    )
}

Position and PositionRange is just data classes with extra functions like

    fun inRange(position: Position): Boolean {
        val topEnd = Position(startPoint.x + width, startPoint.y)
        val bottomEnd = Position(startPoint.x, startPoint.y + height)
        val endPos = Position(startPoint.x + width, startPoint.y + height)
        return  (position.x >= startPoint.x && position.y >= startPoint.y) &&
                (position.x <= topEnd.x && position.y >= topEnd.y) &&
                (position.x >= bottomEnd.x && position.y <= bottomEnd.y) &&
                (position.x <= endPos.x && position.y <= endPos.y)
    }

    fun getCenter(): Position {
        return Position(startPoint.x + width / 2, startPoint.y + height / 2)
    }

Here is how it draws:

Wrong line draw

Wrong line draw


Solution

  • The solution is not the best, but at least it works.

    Get your coordinate like this:

    MotionEvent.ACTION_MOVE -> {
        val pos = Position(it.x, it.rawY - it.yPrecision * 4.5f)
        ...
    }