Как создать рисунок на Jetpack Compose Canvas, используя сенсорные события?

Это вопрос в стиле вопросов и ответов, так как я искал образец рисования с помощью Jetpack Canvas, но вопросы по stackoverflow, тому или иному , я нашел применение pointerInteropFilterдля рисования, как View onTouchEvent MotionEvents, который не рекомендуется в соответствии с документами, поскольку

Специальный PointerInputModifier, предоставляющий доступ к базовым событиям MotionEvents, изначально отправленным в Compose. Отдавайте предпочтение pointerInput и используйте его только для взаимодействия с существующим кодом, использующим MotionEvents.

Хотя основной целью этого модификатора является предоставление произвольному коду доступа к исходному MotionEvent, отправленному в Compose, для полноты предоставлены аналоги, позволяющие произвольному коду взаимодействовать с системой, как если бы это было Android View.

1 ответ

Нам нужны состояния движения, как в случае с первым View.

      val ACTION_IDLE = 0
val ACTION_DOWN = 1
val ACTION_MOVE = 2
val ACTION_UP = 3

Путь, текущая позиция касания и состояния касания

      val path = remember { Path() }
var motionEvent by remember { mutableStateOf(ACTION_IDLE) }
var currentPosition by remember { mutableStateOf(Offset.Unspecified) }

Это необязательно для отладки, нет необходимости, если вы не хотите отлаживать

      // color and text are for debugging and observing state changes and position
var gestureColor by remember { mutableStateOf(Color.LightGray) }
var gestureText by remember { mutableStateOf("Touch to Draw") }

Модификатор для создания сенсорных событий. Modifier.clipToBounds()заключается в предотвращении рисования за пределами Canvas.

      val drawModifier = Modifier
    .fillMaxWidth()
    .height(400.dp)
    .background(gestureColor)
    .clipToBounds()
    .pointerInput(Unit) {
        forEachGesture {
            awaitPointerEventScope {

                // Wait for at least one pointer to press down, and set first contact position
                val down: PointerInputChange = awaitFirstDown().also {
                    motionEvent = ACTION_DOWN
                    currentPosition = it.position
                    gestureColor = Blue400
                }


                do {
                    // This PointerEvent contains details including events, id, position and more
                    val event: PointerEvent = awaitPointerEvent()

                    var eventChanges =
                        "DOWN changedToDown: ${down.changedToDown()} changedUp: ${down.changedToUp()}\n"
                    event.changes
                        .forEachIndexed { index: Int, pointerInputChange: PointerInputChange ->
                            eventChanges += "Index: $index, id: ${pointerInputChange.id}, " +
                                    "changedUp: ${pointerInputChange.changedToUp()}" +
                                    "pos: ${pointerInputChange.position}\n"

                            // This necessary to prevent other gestures or scrolling
                            // when at least one pointer is down on canvas to draw
                            pointerInputChange.consumePositionChange()
                        }

                    gestureText = "EVENT changes size ${event.changes.size}\n" + eventChanges

                    gestureColor = Green400
                    motionEvent = ACTION_MOVE
                    currentPosition = event.changes.first().position
                } while (event.changes.any { it.pressed })

                motionEvent = ACTION_UP
                gestureColor = Color.LightGray

                gestureText += "UP changedToDown: ${down.changedToDown()} " +
                        "changedUp: ${down.changedToUp()}\n"
            }
        }
    }

И примените этот модификатор к холсту и перемещайте или рисуйте в зависимости от текущего состояния и положения.

      Canvas(modifier = drawModifier) {

    when (motionEvent) {
        ACTION_DOWN -> {
            path.moveTo(currentPosition.x, currentPosition.y)
        }
        ACTION_MOVE -> {

            if (currentPosition != Offset.Unspecified) {
                path.lineTo(currentPosition.x, currentPosition.y)
            }
        }

        ACTION_UP -> {
            path.lineTo(currentPosition.x, currentPosition.y)
        }

        else -> Unit
    }

    drawPath(
        color = Color.Red,
        path = path,
        style = Stroke(width = 4.dp.toPx(), cap = StrokeCap.Round, join = StrokeJoin.Round)
    )
}
Другие вопросы по тегам