In normal view, we can have 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
}
In Jetpack Compose, I can only find we have the tapGestureFilter
in the modifier, which only takes the action from the ACTION_UP
only.
Modifier
.tapGestureFilter { Log.d("Track", "Tap ${it.x} | ${it.y}") }
.doubleTapGestureFilter { Log.d("Track", "DoubleTap ${it.x} | ${it.y}") }
Is there an equivalent onTouchEvent
for Jetpack Compose?
We have a separate package for that, which is pretty useful. There are two main extension functions that would be suitable for you:
pointerInput
- docs
pointerInteropFilter
- docs
If you want to handle and process the event I recommend using pointerInteropFilter
which is the analogue of View.onTouchEvent
. It's used along with modifier
:
Column(modifier = Modifier.pointerInteropFilter {
when (it.action) {
MotionEvent.ACTION_DOWN -> {}
MotionEvent.ACTION_MOVE -> {}
MotionEvent.ACTION_UP -> {}
else -> false
}
true
})
That will be Compose adjusted code to your specified View.onTouchEvent
sample.
P.S. Don't forget about @ExperimentalPointerInput
annotation.
pointerInteropFilter
is not described as preferred way to use if you are not using touch api with interoperation with existing View code.
A special PointerInputModifier that provides access to the underlying MotionEvents originally dispatched to Compose. Prefer pointerInput and use this only for interoperation with existing code that consumes MotionEvents. While the main intent of this Modifier is to allow arbitrary code to access the original MotionEvent dispatched to Compose, for completeness, analogs are provided to allow arbitrary code to interact with the system as if it were an Android View.
You can use pointerInput
, awaitTouchDown
for MotionEvent.ACTION_DOWN
, and awaitPointerEvent
for MotionEvent.ACTION_MOVE
and 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
}
}
}
Some key notes about gestures
detectDragGestures source code for instance
val down = awaitFirstDown(requireUnconsumed = false)
var drag: PointerInputChange?
var overSlop = Offset.Zero
do {
drag = awaitPointerSlopOrCancellation(
down.id,
down.type
) { change, over ->
change.consumePositionChange()
overSlop = over
}
} while (drag != null && !drag.positionChangeConsumed())
So when you need to prevent other events to intercept
call pointerInputChange.consumeDown()
after awaitFirstDown
, call
pointerInputChange.consumePositionChange() after awaitPointerEvent
and awaitFirstDown()
has requireUnconsumed
parameter which is
true by default. If you set it to false even if a pointerInput consumes
down before your gesture you still get it. This is also how events like drag use it to get first down no matter what.
Every available event you see detectDragGestures
,
detectTapGestures
even awaitFirstDown
use awaitPointerEvent
for implementation, so using awaitFirstDown
, awaitPointerEvent
and consuming changes you can configure your own gestures.
For instance, this is a function i customized from original detectTransformGestures
only to be invoked with specific number of pointers down.
suspend fun PointerInputScope.detectMultiplePointerTransformGestures(
panZoomLock: Boolean = false,
numberOfPointersRequired: Int = 2,
onGesture: (centroid: Offset, pan: Offset, zoom: Float, rotation: Float) -> Unit,
) {
forEachGesture {
awaitPointerEventScope {
var rotation = 0f
var zoom = 1f
var pan = Offset.Zero
var pastTouchSlop = false
val touchSlop = viewConfiguration.touchSlop
var lockedToPanZoom = false
awaitFirstDown(requireUnconsumed = false)
do {
val event = awaitPointerEvent()
val downPointerCount = event.changes.size
// If any position change is consumed from another pointer or pointer
// count that is pressed is not equal to pointerCount cancel this gesture
val canceled = event.changes.any { it.positionChangeConsumed() } || (
downPointerCount != numberOfPointersRequired)
if (!canceled) {
val zoomChange = event.calculateZoom()
val rotationChange = event.calculateRotation()
val panChange = event.calculatePan()
if (!pastTouchSlop) {
zoom *= zoomChange
rotation += rotationChange
pan += panChange
val centroidSize = event.calculateCentroidSize(useCurrent = false)
val zoomMotion = abs(1 - zoom) * centroidSize
val rotationMotion =
abs(rotation * PI.toFloat() * centroidSize / 180f)
val panMotion = pan.getDistance()
if (zoomMotion > touchSlop ||
rotationMotion > touchSlop ||
panMotion > touchSlop
) {
pastTouchSlop = true
lockedToPanZoom = panZoomLock && rotationMotion < touchSlop
}
}
if (pastTouchSlop) {
val centroid = event.calculateCentroid(useCurrent = false)
val effectiveRotation = if (lockedToPanZoom) 0f else rotationChange
if (effectiveRotation != 0f ||
zoomChange != 1f ||
panChange != Offset.Zero
) {
onGesture(centroid, panChange, zoomChange, effectiveRotation)
}
event.changes.forEach {
if (it.positionChanged()) {
it.consumeAllChanges()
}
}
}
}
} while (!canceled && event.changes.any { it.pressed })
}
}
}
As of 1.2.0-beta01, partial consumes like
PointerInputChange.consemePositionChange()
,
PointerInputChange.consumeDownChange()
, and one for consuming all changes PointerInputChange.consumeAllChanges()
are deprecated
PointerInputChange.consume()
is the only one to be used preventing other gestures/event.
Also i have a tutorial here that covers gestures in detail
Maybe a bit late, but since compose is constantly updating, this is how I do it as of today:
Modifier
.pointerInput(Unit) {
detectTapGestures {...}
}
.pointerInput(Unit) {
detectDragGestures { change, dragAmount -> ...}
})
We also have detectHorizontalDragGestures
and detectVerticalDragGestures
among others to help us.
ps: 1.0.0-beta03
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With