Gestures in Jetpack Compose: Getting Started

Learn how to implement different gestures in Jetpack Compose and provide your app an intuitive user experience. By Max Buster.

Leave a rating/review
Download materials
Save for later

Gestures are essential to a mobile app because they foster an intuitive user experience and create a cleaner interface. Get rid of clunky UI and use common gesture interaction to make your app more delightful. Small changes can make your app better.

Migrating from XML-based layouts to Compose has changed much of how we create gestures. In this tutorial, you’ll learn how to install the following gestures in the new Jetpack Compose paradigm:

  • How to respond to single taps on buttons and other view types.
  • Handling double taps on list items.
  • Scrolling lists of uniform items and nonuniform items.
  • Swiping to dismiss content in lists.
Note: This tutorial assumes you know the basics of Android development and Jetpack Compose. If you’re new to Android development or haven’t used Compose before, check the following tutorials: Beginning Android Development and Jetpack Compose Tutorial for Android.

Getting Started

You’ll be working through a to-do list app to better understand common gestures and how to integrate them in Compose.

Use the Download Materials button at the top or bottom of this tutorial to download the project. Open the starter project in Android Studio. You’ll see the initial screen for the to-do list app:

Image showing the initial todo-list screen

If you start exploring the files, Inside ui folder, you’ll see two main composables: TodoListComposable.kt and TodoEditorComposable.kt. Those are the two primary screens that provide a list of to-do items, and an editor to add items and modify previous ones.

Yet, you can’t interact with anything in the app at this point. You’ll update that with the power of gestures.

Introduction to Jetpack Compose Gestures

If you’ve been developing UI in XML-based layouts, you might wonder how to add listeners to Jetpack Compose components. For the most part, you don’t need to. Rather than adding listeners to inflated views, while working with Jetpack Compose, you can add gesture modifiers and gesture callbacks directly to the composables when you declare them.

Detecting Taps

Taps are the simplest and most important form of interaction in mobile apps. They signify a single finger press and release to indicate selection. In your app, they’re necessary to interact with all of the buttons and to-do list items.

Single Tapping

First, open TodoListComposable.kt and replace the TODO: Add click event comment inside the onClick callback.

onClick = { navController.navigate(Destinations.EDITOR_ROUTE) },

This will now navigate to the editor screen for a new to-do item creation.

Next, add this callback in TodoEditorComposable.kt to replace the TODO: Add click event comment in the save Button:

onClick = {
  todo?.let {
    // Update item if it already exists
    todoEditorViewModel.updateTodo(todo, title = title.value, content = content.value)
  } ?: run {
    // Add new item if one doesn't already exist
    todoEditorViewModel.saveTodo(title = title.value, content = content.value)

  // Navigate back to the to-do list screen after saving changes

This action saves a new event — if the screen was navigated to without a to-do item but just updates the item if one was passed. It then returns to the to-do list by popping the back stack.

Now, add a clickable modifier to the to-do list item in TodoListComposable.kt where it asks TODO: Add clickable modifier.

.clickable {

This uses Compose navigation to navigate to the editor screen and pass the to-do item ID as a navigation argument. Note that we added the clickable modifier to the entire row. It will open the editor for the item on click.

Build and run the app. You should be able to interact with all of the buttons and the to-do list now.

Gif demonstrating tap interactions

You could add the clickable modifier to an element within the row to make a certain section clickable. Only that element would trigger the action.

Now it’s time to learn the double tap!

Double Tapping to Star

The next feature you’ll work on is making to-do list elements “star-able” in order to draw attention to them. In the current app, a single click isn’t possible because it opens the editor. You can add an empty star button that the user could tap once to star the item, but that will begin to bloat the UI. Instead we can use another common gesture — double tapping.

Double taps are added within a slightly different modifier than the more generic button onClick. Add the following modifier to the line in TodoListComposable.kt labeled TODO: Add pointer input modifier.

.pointerInput(Unit) {
    onDoubleTap = { todoListViewModel.toggleStarred(item) }

The detectTapGestures function allows more flexibility to detect tap inputs, which include:

  • onPress — the initial press down of a tap is first detected.
  • onDoubleTap — two taps in rapid succession.
  • onLongPress — a single press held down.
  • onTap — after a single press and release.

Using these additional gestures allows you to expand the range of interactions with less additional code.

Because the detectTapGestures modifier can also accept single taps, you can get rid of the clickable modifier and add that action to the detectTapGestures function, if you want to clean up the code a bit.

.pointerInput(Unit) {
    onTap = { 
    onDoubleTap = { todoListViewModel.toggleStarred(item) }

Build and run the app. It should star and unstar a row on double tap.

Gif demonstrating double tap to star

Handling Scrolling Gestures

You can only display a few items at once, and then you have to scroll to show what is off-screen. Scrolling plays a role of an essential gesture here.

Default Scrolling Behavior

Making content scrollable happens in two primary ways: By putting it in a Column/Row or in a LazyColumn/LazyRow. A regular Column/Row isn’t scrollable by default, but we have a modifier for that!

LazyColumn/LazyRow are scrollable by default but typically are only used for homogenous lists of elements or long lists that couldn’t render all at once.

Currently, both the list screen and the editor screen are implemented with Columns, which doesn’t support scrolling. That can cause major dysfunctions with the app. You have a series of repeating elements on the list screen, which is a good spot for a LazyColumn.

In TodoListComposable.kt, find the // TODO: Change to LazyColumn comment and replace the existing Column implementation with the following LazyColumn:

LazyColumn(modifier = Modifier.padding(16.dp), content = { 
  items(items) {
    TodoListItem(it, todoListViewModel, navController)

This code is almost identical to the previous code, except it uses LazyColumn instead of Column to take advantage of the automatic scrolling. It uses the built-in items function to generate a list of homogenous elements from a list of data.

And just like that, the to-do list scrolls! You can test it by adding a bunch of new to-dos using the plus button on the list screen:

Gif demonstrating how to add a new item

And once you have enough, you can drag the list up and down:

Gif demonstrating list scrolling

The editor screen doesn’t have repeating elements, but it will still be helpful to have it scrollable in case the input content ever spreads beyond the screen. You can add a regular scrollable modifier to the Column containing editor inputs in order to allow scrolling off screen.

Open TodoEditorComposable.kt and replace the // TODO: Add vertical scroll code with the following modifier.


This allows the Column to scroll when content goes off the screen and provides a state holder to store the scroll position and handle recomposition.

Build and run the app. Now you can write an entire manuscript in the to-do item and be able to see all of it.

Gif demonstrating editor scrolling