Как создать рисунок на Jetpack Compose Canvas, используя сенсорные события?
Это вопрос в стиле вопросов и ответов, так как я искал образец рисования с помощью Jetpack Canvas, но вопросы по stackoverflow, тому или иному , я нашел применение
pointerInteropFilter
для рисования, как View
onTouchEvent
MotionEvent
s, который не рекомендуется в соответствии с документами, поскольку
Специальный 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)
)
}