Jetpack Compose: Detect the Number of Fingers Touching the Screen
Jetpack Compose: Detect the current number of touches on the screen
On my journey learning Jetpack Compose and I had the need to come up with a solution to detecting the number of fingers touching the screen. Here are two approaches I took to solve this.
There are many ways to detect gestures of multiple touches in the Jetpack Compose gestures documentation, but none give you a way to get the exact number of fingers that touched the screen.
https://developer.android.com/jetpack/compose/gestures
The classic approach prior to Jetpack Compose is to get the pointer count from a MotionEvent by overriding OnTouchEvent:
https://developer.android.com/training/gestures/detector
So I decided to research a way to detect gestures in Jetpack compose that would give me a MotionEvent.
I stumbled upon PointerInteropFilter after a quick google search. It’s a Jetpack Compose Modifier.
Here is my first solution:
@ExperimentalComposeUiApi
@Composable
fun Greeting(name: String) {
*Card*(modifier = Modifier
.*fillMaxSize*()
.*pointerInteropFilter ***{ **motionEvent **->
**when (motionEvent.*action *and MotionEvent.*ACTION_MASK *) {
MotionEvent.*ACTION_POINTER_DOWN *-> {
val pointerCount = motionEvent.*pointerCount
*Log.d("Greeting", "number of fingers detected: $pointerCount")
}
}
true
**}
**, content = **{ ***Text*(text = name) **}**)
}
Greeting is a Composable that is automatically generated by Android Studio when creating a blank Jetpack Compose app. I changed it to use a Card a composable. The fillMaxSize() Modifier makes the card fill the entire view of the screen. I did this for convenience so I can swipe anywhere.
motionEvent.*action *and MotionEvent.*ACTION_MASK*
The above line is a common practice when detecting multi-touch in Android. Check here for more info on this: Android multitouch and getActionMasked()
This is a working solution! But it does come with some downsides depending on your use case. It continues to listen to touches. I wanted a way to stop listening after I have detected my desired number of touches on the screen. Luckily Android is open source, so I decided to jump in a see what I could find.
I decided to first look at the documentation and implementation of PointerInteropFilter first:
It only exposes so much so I started to explore other options before continuing this route. I thought of the Jetpack Compose gesture documentation I mentioned earlier and how they gave you ways to detect multi-touch. So I started to look at the documentation again and noticed they all used the PointerInput Modifier.
https://developer.android.com/jetpack/compose/gestures
So I started to look into the gestures implementations:
I noticed they all use the ForEachGesture:
After looking at that code and examples in the kotlin-lang workspace. I was able to come up with this second solution.
@ExperimentalComposeUiApi
@Composable
fun Greeting(name: String) {
*Card*(modifier = Modifier
.*fillMaxSize*()
.*pointerInput*(Unit) **{
**val fingerCount = 2;
forEachGesture **{
**val context = currentCoroutineContext()
awaitPointerEventScope **{
**do {
val event = awaitPointerEvent()
if (event.changes.size == fingerCount) {
context.*cancel*()
}
} while (event.changes.*any ***{ it**.pressed **} **&& context.isActive)
**}
}
}**, content = **{ ***Text*(text = name) **}**)
}
You will need to keep getting the size from the list of changes of the event. I also needed the context from forEachGesture so that I can stop processing input. You will need the coroutines-android library. Because of some issues, I ran into, I had to use this one at the time of writing this(https://stackoverflow.com/a/70231016):
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:x.x.x"
I thought of doing this after seeing this comment in the ForEachGesture source code:
/**
* Repeatedly calls [block] to handle gestures. If there is a [CancellationException],
* it will wait until all pointers are raised before another gesture is detected, or it
* exits if [isActive] is `false`.
*/
Note you can also look at the source code inside of Android Studio using Command+b ( Mac ) or Control+b ( Windows ) after putting your cursor on the code you want to go to. You can also do this by holding down Command/Control + clicking on the code.
This is as far I got with this. You can create a more robust solution by altering the source code as well.
There is also this great article that also helped tremendously: Using Jetpack Compose to complete custom gesture processing
Thank you for taking the time to read this!