Creating Rich Interactions with Custom Gestures in Compose

Meet
5 min readOct 26, 2023

In the world of mobile app development, creating rich and intuitive user interactions is crucial for delivering a top-notch user experience. In this blog, we’ll explore how to use custom gestures in Jetpack Compose to build beautiful and engaging interactions for your Android app. By the end of this article, you’ll be able to reason about and easily implement custom gestures in your own projects.

Terminology in Jetpack Compose

Before we dive into custom gestures, let’s clarify some terminology in Jetpack Compose. Android app development has evolved, and it’s not limited to just phones with touch interaction anymore. To be inclusive of various form factors and input types, Jetpack Compose uses a more general term called “pointer” to refer to any type of input used for pointing at elements on your screen. A pointer allows users to perform gestures.

For instance, in traditional touch interaction, a gesture might involve a user placing their finger on the screen, moving it around, and then lifting it. In Jetpack Compose, such a gesture is represented by a stream of pointer events. This stream includes a press event, multiple move events, and a release event, which collectively form a complete gesture.

Additionally, users can use more than one finger to perform a gesture, like pinching or rotating. Jetpack Compose includes helper methods to recognize these complex gestures, making it easier for developers to create rich interactions.

The Composable Toolbox

Jetpack Compose provides developers with a variety of tools to implement gesture handling in their apps. These tools are organized into different layers:

  1. Lowest Level: You can listen for and directly handle raw pointer events using the pointerInput modifier. This approach offers the most flexibility for customizing gesture recognition.
  2. Common Gestures: For common gestures like dragging, tapping, and zooming, Jetpack Compose includes a set of gesture recognizers within the pointerInput modifier. These recognizers translate raw pointer events into full-fledged gestures.
  3. Gesture Modifiers: Composables like clickable and draggable allow you to add gesture handling to arbitrary UI elements. These modifiers come with additional functionality for enhancing user interactions.
  4. Components: Some built-in components, such as buttons and sliders, already include gesture recognition. You can leverage these components for common interactions without extra configuration.

In this blog, we’ll focus on the middle two layers: gesture recognizers and gesture modifiers. These layers provide a balance of control and simplicity for implementing custom gestures.

Gesture Recognizers

To create a custom gesture recognizer, you first apply the pointerInput modifier to your composable. Inside this modifier, you're given access to the various gesture recognizers. For example, you can use the detectTapGestures method to recognize various tap gestures like double-tap, long-press, single-press, and tap. You provide lambdas for the specific tap gestures you want to respond to, and these lambdas are executed when the corresponding gesture is recognized.

Jetpack Compose also allows you to detect drag gestures, and during a drag, the onDrag lambda is continuously executed. You can configure your drag detector to respond only to vertical or horizontal dragging or to detect drags after a long press. Multi-touch transformation gestures, such as panning, pinching, and rotating, can be detected using the detectTransformGestures method.

These are just a few examples of the gesture recognizers available in the pointerInput scope. You can choose the recognizer that suits your specific requirements.

Gesture Modifiers

Gesture modifiers provide a more concise way of adding gesture handling to your composables, without the need to use the pointerInput modifier directly. Besides gesture recognition, these modifiers offer extra functionality to enhance user interactions.

  • Clickable Modifier: This modifier adds click behavior to a composable. It responds to single taps, and you specify a lambda to be executed when the composable is clicked. If you want to respond to not only taps but also long presses or double taps, you can use the combinedClickable modifier, and once again, you provide the relevant callbacks.
  • Draggable Modifier: This modifier allows you to listen to horizontal or vertical drag gestures. Instead of passing a single onDrag lambda, you pass a state, which is a common pattern that allows you to hoist the state and mutate it outside of the modifier.
  • Scrollable Modifier: Similar to the draggable modifier, this one includes logic for scrolling and flinging.
  • Transformable Modifier: This modifier makes it possible to listen to multi-touch transform events on a composable, such as zoom, offset, and rotation changes.

Now that we have explored the tools available for implementing custom gestures in Jetpack Compose, let’s discuss how to choose the right one for your specific needs.

Choosing Between Gesture Recognizers and Modifiers

In Jetpack Compose, you have both gesture recognizers and gesture modifiers at your disposal, and it’s essential to understand when to use each. Let’s compare the two and determine when it’s appropriate to choose one over the other.

  • Gesture Recognizers: These are low-level tools for recognizing gestures. You typically use gesture recognizers when you need fine-grained control over the gesture recognition process. For complex or unique gestures that can’t be easily achieved with high-level modifiers, gesture recognizers are your go-to choice.
  • Gesture Modifiers: These are higher-level tools that provide a more abstract and user-friendly way to handle common gestures. Gesture modifiers are great for simplifying gesture handling, and they come with additional functionality beyond just gesture recognition. You should opt for gesture modifiers when they provide the functionality you need for your specific use case.

In general, start with gesture modifiers, and only resort to gesture recognizers when you have a compelling reason to do so. High-level modifiers are often sufficient for most common interactions and offer a more straightforward development experience.

🚀 Enjoyed the insights in my latest Medium article? If you found it helpful, please consider giving it a round of applause (👏) and sharing it with your network. Your support means the world to me! 💙

And if you want more of such content, don’t forget to hit that ‘Follow’ button. 📚 Let’s stay connected and explore more together! 🚀📌

--

--

Meet

Full-time Android Developer, Tech Enthusiast, Knows Flutter, React Native, Web Development Etc…