Blog Infos
Author
Published
Topics
Published

Nearly three years have passed since I penned an article on implementing touch hold callbacks in Android, using the old view system. However, the advent of Jetpack Compose has revolutionized how we handle touch events in our apps. This new article will explore the latest approach to achieving the same functionality using Jetpack Compose to create a touch held down Modifier and improve the user experience of your Android app.

We won’t be getting into the touch handling in Compose today. But don’t panic! Check out a droidcon post for a deep dive, and if you’re still lost, the Android Developers docs are here to rescue you!

If you already have my previous implementation of touch held-down in your codebase, you’ll be happy to know that Compose has interop support that lets you use it seamlessly. This means that you can use the pointerInteropFilter() Modifier with your existing code, allowing you to take advantage of Compose’s powerful features without having to start from scratch.

Since Compose doesn’t have a Modifier that provides continuous callbacks when the user holds down their touch, we’ll need to fill this gap by implementing a reusable Modifier.

Please note that this is not a tutorial, but rather a fast-forward answer to get you going forward.

Our main goal is to create UI components that are more interactive and help users take continuous actions, even if those actions need to be accelerated or decelerated over time. To achieve this, developers need to be able to track the elapsed time since a user initially pressed down on a composable, and then take appropriate action based on that information.

To help you imagine the potential end-user experience, here is a basic example that you could build.

Our initial Modifier is the onTouchHeld(), which takes a pollDelay parameter to set the time window between each callback, and a function to invoke with an elapsed duration since the user held down their touch.

fun Modifier.onTouchHeld(
    pollDelay: Duration,
    onTouchHeld: (timeElapsed: Duration) -> Unit
) = composed {
    val scope = rememberCoroutineScope()
    pointerInput(onTouchHeld) {
        awaitEachGesture {
            val initialDown = awaitFirstDown(requireUnconsumed = false)
            val initialDownTime = System.nanoTime()
            val initialTouchHeldJob = scope.launch {
                while (initialDown.pressed) {
                    val timeElapsed = System.nanoTime() - initialDownTime
                    onTouchHeld(timeElapsed.nanoseconds)
                    delay(pollDelay)
                }
            }
            waitForUpOrCancellation()
            initialTouchHeldJob.cancel()
        }
    }
}

Job Offers

Job Offers


    Android Team Lead

    Komoot
    Remote EMEA
    • Full Time
    apply now

    Developer Relations Engineer

    Embrace
    United States
    • Full Time
    apply now

    Information Security Engineer

    MongoDB
    London, UK
    • Full Time
    apply now

OUR VIDEO RECOMMENDATION

,

Exploration of Touch & Input in Jetpack Compose

etpack Compose has a layered approach to user interaction handling, all the way from high-level onClick parameters to dealing directly with low-level touch input. In this talk we’ll cover all these levels, discussing composable parameters,…
Watch Video

Exploration of Touch & Input in Jetpack Compose

Jolanda Verhoef
Android Developer Relations Engineer
Google

Exploration of Touch & Input in Jetpack Compose

Jolanda Verhoef
Android Developer Re ...
Google

Exploration of Touch & Input in Jetpack Compose

Jolanda Verhoef
Android Developer Relatio ...
Google

Jobs

Moving on to our next Modifier is onTouchHeldAnimated(), this time our Modifier takes additional parameters that allow you to increase or decrease the polling delay over a given time and even supports various Easing functions out of the box.

fun Modifier.onTouchHeldAnimated(
    easing: Easing = FastOutSlowInEasing,
    pollDelay: Duration = 500.milliseconds,
    targetPollDelay: Duration = pollDelay,
    animationDuration: Duration = 5.seconds,
    onTouchHeld: () -> Unit
) = composed {
    val scope = rememberCoroutineScope()
    pointerInput(onTouchHeld) {
        val animationSpec: FloatAnimationSpec = FloatTweenSpec(
            animationDuration.inWholeMilliseconds.toInt(),
            0,
            easing
        )
        awaitEachGesture {
            val initialDown = awaitFirstDown(requireUnconsumed = false)
            val initialTouchHeldJob = scope.launch {
                var currentPlayTime = 0.milliseconds
                var delay = pollDelay
                while (initialDown.pressed) {
                    onTouchHeld()
                    delay(delay.inWholeMilliseconds)
                    currentPlayTime += delay
                    delay = animationSpec.getValueFromNanos(
                        currentPlayTime.inWholeNanoseconds,
                        pollDelay.inWholeMilliseconds.toFloat(),
                        targetPollDelay.inWholeMilliseconds.toFloat(),
                        0F
                    ).toInt().milliseconds
                }
            }
            waitForUpOrCancellation()
            initialTouchHeldJob.cancel()
        }
    }
}

To achieve a counter, that accelerates over time, as demonstrated in our example, you can set a lower targetPollDelay. This will progressively reduce the delay between each callback over time, resulting in an accelerated increase in the counter if you are incrementing it on each callback. This approach can help create a dynamic and engaging user experience.

Incorporating new modifiers like onTouchHeld() and onTouchHeldAnimated() can help you create dynamic user experiences in Jetpack Compose. Give them a try and see how they can take your Compose-powered UIs to the next level!

This article was previously published on proandroiddev.com

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
It’s one of the common UX across apps to provide swipe to dismiss so…
READ MORE
blog
In this part of our series on introducing Jetpack Compose into an existing project,…
READ MORE
blog

How to animate BottomSheet content using Jetpack Compose

Early this year I started a new pet project for listening to random radio…
READ MORE
blog
Yes! You heard it right. We’ll try to understand the complete OTP (one time…
READ MORE

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.

Menu