Как я могу получить onTouchEvent в Jetpack Compose?
В нормальном представлении мы можем иметь
onTouchEvent
override fun onTouchEvent(event: MotionEvent?): Boolean {
when (event?.action) {
MotionEvent.ACTION_DOWN -> {}
MotionEvent.ACTION_MOVE -> {}
MotionEvent.ACTION_UP -> {}
else -> return false
}
invalidate()
return true
}
В Jetpack Compose я могу найти только
tapGestureFilter
в модификаторе, который принимает действие только из
ACTION_UP
только.
Modifier
.tapGestureFilter { Log.d("Track", "Tap ${it.x} | ${it.y}") }
.doubleTapGestureFilter { Log.d("Track", "DoubleTap ${it.x} | ${it.y}") }
Есть ли эквивалент
onTouchEvent
для Jetpack Compose?
5 ответов
Для этого у нас есть отдельный пакет, который очень полезен. Есть две основные функции расширения, которые вам подойдут:
Если вы хотите обработать и обработать событие, я рекомендую использовать
pointerInteropFilter
который является аналогом
View.onTouchEvent
. Он используется вместе с
modifier
:
Column(modifier = Modifier.pointerInteropFilter {
when (it.action) {
MotionEvent.ACTION_DOWN -> {}
MotionEvent.ACTION_MOVE -> {}
MotionEvent.ACTION_UP -> {}
else -> false
}
true
})
Это будет составить скорректированный код для вашего указанного
View.onTouchEvent
образец.
PS Не забывайте про
@ExperimentalPointerInput
аннотация.
pointerInteropFilter
не описывается как предпочтительный способ использования, если вы не используете сенсорный API с взаимодействием с существующим кодом просмотра.
Специальный PointerInputModifier, предоставляющий доступ к базовым событиям MotionEvents, изначально отправленным в Compose. Отдавайте предпочтение pointerInput и используйте его только для взаимодействия с существующим кодом, использующим MotionEvents. Хотя основной целью этого модификатора является предоставление произвольному коду доступа к исходному MotionEvent, отправленному в Compose, для полноты предоставлены аналоги, позволяющие произвольному коду взаимодействовать с системой, как если бы это было Android View.
Вы можете использовать
pointInputFilter
,
awaitTouchDown
за
MotionEvent.ACTION_DOWN
, а также
awaitPointerEvent
за
MotionEvent.ACTION_MOVE
а также
MotionEvent.ACTION_UP
val pointerModifier = Modifier
.pointerInput(Unit) {
forEachGesture {
awaitPointerEventScope {
awaitFirstDown()
// ACTION_DOWN here
do {
//This PointerEvent contains details including
// event, id, position and more
val event: PointerEvent = awaitPointerEvent()
// ACTION_MOVE loop
// Consuming event prevents other gestures or scroll to intercept
event.changes.forEach { pointerInputChange: PointerInputChange ->
pointerInputChange.consumePositionChange()
}
} while (event.changes.any { it.pressed })
// ACTION_UP is here
}
}
}
Может быть, немного поздно, но поскольку compose постоянно обновляется, на сегодняшний день я делаю это следующим образом:
Modifier
.pointerInput(Unit) {
detectTapGestures {...}
}
.pointerInput(Unit) {
detectDragGestures { change, dragAmount -> ...}
})
У нас также есть
detectHorizontalDragGestures
а также
detectVerticalDragGestures
среди прочего, чтобы помочь нам.
пс:
1.0.0-beta03
СочинитьPointerEventType.Press
,.Move
и.Release
близко соответствует просмотруMotionEvent.ACTION_DOWN
,_MOVE
и_UP
соответственно. (См. файл реализации платформы Android PointerEvent.android.kt для сопоставления сPointerEvent.type
.)
Вы можете расширить функцию, которая принимает три лямбда-параметра и направляет события в нужный.
В следующем примере предполагается, что в лямбда-выражениях обрабатываются несколько указателей.
suspend fun PointerInputScope.routePointerChangesTo(
onDown: (PointerInputChange) -> Unit = {},
onMove: (PointerInputChange) -> Unit = {},
onUp: (PointerInputChange) -> Unit = {}
) {
awaitEachGesture {
do {
val event = awaitPointerEvent()
event.changes.forEach {
when (event.type) {
PointerEventType.Press -> onDown(it)
PointerEventType.Move -> onMove(it)
PointerEventType.Release -> onUp(it)
}
it.consume()
}
} while (event.changes.any { it.pressed })
}
}
Проверьте это, позвонив с помощьюModifier
вот так наComposable
:
Modifier.pointerInput(Unit) {
routePointerChangesTo(
onDown = { change -> println("Down at ${change.position}")},
onMove = { change -> println("Move to ${change.position}")},
onUp = { change -> println("Up at ${change.position}")}
)
}
Если у тебя все в порядке сonDown()
вызывается только после превышения скорости касания, вы можете просто использоватьdetectDragGestures()
функция включенаPointerInputScope
для достижения аналогичного результата.
Если следует игнорировать указатели, отличные от первого, все становится немного сложнее:
suspend fun PointerInputScope.routePointerChangesTo(
onDown: (PointerInputChange) -> Unit = {},
onMove: (PointerInputChange) -> Unit = {},
onUp: (PointerInputChange) -> Unit = {}
) {
awaitEachGesture {
awaitFirstDown().let { firstDownChange ->
onDown(firstDownChange)
firstDownChange.consume()
var isFirstPointerDown = true
do {
awaitPointerEvent().let { event ->
event.changes.forEach { change ->
if (change.id == firstDownChange.id) {
when (event.type) {
PointerEventType.Move -> {
onMove(change)
change.consume()
}
PointerEventType.Release -> {
if (!change.pressed) {
isFirstPointerDown = false
onUp(change)
change.consume()
}
}
}
}
}
}
} while (isFirstPointerDown)
}
}
}
См. также pointerInput
.
После некоторых исследований, похоже, можно использовать
dragGestureFilter
, смешанный с
tapGestureFilter
Modifier
.dragGestureFilter(object: DragObserver {
override fun onDrag(dragDistance: Offset): Offset {
Log.d("Track", "onActionMove ${dragDistance.x} | ${dragDistance.y}")
return super.onDrag(dragDistance)
}
override fun onStart(downPosition: Offset) {
Log.d("Track", "onActionDown ${downPosition.x} | ${downPosition.y}")
super.onStart(downPosition)
}
override fun onStop(velocity: Offset) {
Log.d("Track", "onStop ${velocity.x} | ${velocity.y}")
super.onStop(velocity)
}
}, { true })
.tapGestureFilter {
Log.d("NGVL", "onActionUp ${it.x} | ${it.y}")
}
Причина все еще использовать
tagGestureFilter
, потому что
onStop
не указывает положение, а просто скорость, поэтому
tapGestureFilter
помогает обеспечить последнюю позицию (при необходимости)