Chapters

Hide chapters

Android Animations by Tutorials

First Edition · Android 12 · Kotlin 1.5 · Android Studio Artic Fox

Section II: Screen Transitions

Section 2: 3 chapters
Show chapters Hide chapters

8. ItemTouchHelper Animations
Written by Filip Babić

Now that you’ve implemented list and layout animations, you’re ready to upgrade the user experience with gestures, allowing users to swipe items off a list and rearrange them with drag and drop gestures.

In this chapter, you’ll:

  • Enable and recognize gestures in list items.
  • Override swipe gestures to add or remove movies from favorites.
  • Add item resetting to notify users when they’ve swiped an item.
  • Showcase item drag and drop to reorder items.

You’ll achieve all this using the ItemTouchHelper API. Next, you’ll see how.

Getting started

To follow along with this chapter, open the starter project located in 08-itemtouchhelper-animations within the aat-materials repository. This project contains your starting point for this chapter. Here, you’ll add the code to build the final project of this chapter.

Once you open the project, let it sync. Then, build and run. You’ll pick up where you left off in the last chapter.

Your first step toward implementing list gesture animations is to build a callback that will react to the user’s gestures.

Creating ItemTouchHelper.Callback

Before you can implement gesture animations, you need to create an ItemTouchHelper.Callback that will enable and recognize swipe and drag-and-drop gestures.

Create a new file called MyItemTouchHelperCallback.kt in the util package. Then, add the following starting code:

class MyItemTouchHelperCallback(
  private val moviesRepository: MoviesRepository,
  private val lifecycleOwner: LifecycleOwner
) : ItemTouchHelper.Callback() {}

Your callback must extend from ItemTouchHelper.Callback so you can override and implement the functions that let you consume gesture events.

Notice how you also added a moviesRepository and a lifecycleOwner to the constructor. You’ll use them to update the database when the user swipes movies to add or remove them from the list of favorites.

Now that you’ve built the base class structure, you need to override the functions that enable and react to different gestures.

First, override getMovementFlags() by placing the following code inside of ItemTouchHelper.Callback:

override fun getMovementFlags(
  recyclerView: RecyclerView,
  viewHolder: RecyclerView.ViewHolder
): Int {
}

This function is the heart of ItemTouchHelper. Here, you define which flags — in other words, which directions of movement for swipe and drag gestures — you want to allow on your list items.

Start by defining both the drag and swipe direction flags by inserting the following code inside getMovementFlags:

  val dragDirectionFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN
  val swipeDirectionFlags = ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
}

By combining the ItemTouchHelper flag constants with the or operator, dragDirectionFlags defines that the user can drag UP or DOWN. Using the same approach, swipeDirectionFlags defines that they can swipe LEFT or RIGHT.

These flags will unlock movement for list items when the user attempts to either swipe or drag and drop them.

Now that you’ve defined these flags, return them at the bottom of getMovementFlags(), directly below the code you added in the previous step:

  return makeMovementFlags(dragDirectionFlags, swipeDirectionFlags) 

Using makeMovementFlags(drag, swipe), you return all the flags you need to enable swipe and drag gestures. Internally, ItemTouchHelper.Callback sets up these values so it can consume them when necessary.

At this point, you’ve set up the basic callback and overridden one of the methods. Before the code will compile, you’ll override a few more methods to complete the callback. Your next step is to add the swipe gesture.

Adding item swipe gestures

When the user swipes an item, you’ll either add or remove it from the list of favorites depending on their swipe direction. But first, you need to know which item the user selected. So your next step is to add a way to fetch the Movie item from the swiped ViewHolder.

Open MoviesViewHolder in MoviesAdapter. Above bind declare movie:

var movie: Movie? = null

This add’s a new property to MoviesViewHolder called movie.

Inside of bind at the top, add:

this.movie = movie

You’ve now assigned the parameter movie being passed to bind to the new member. The result looks like:

inner class MoviesViewHolder(val binding: ItemMovieBinding) :
  RecyclerView.ViewHolder(binding.root) {
    
  var movie: Movie? = null

  fun bind(movie: Movie) {
    this.movie = movie
    
    ...
  }
}

You’ll use movie to know which item to update in the database when the user swipes an item.

Now, go back to MyItemTouchHelperCallback.kt and, in ItemTouchHelper.Callback, override onSwiped() by adding the following code below getMovementFlags:

override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
  val movieViewHolder = viewHolder as? MoviesAdapter.MoviesViewHolder
  val movie = movieViewHolder?.movie
}

ItemTouchHelper.Callback triggers this function whenever the user swipes an item, so long as you’ve enabled the swipe gesture. In its parameters, you receive the viewHolder that the user swiped and the swipe direction. This lets you add different behavior based on those parameters, giving you a lot of control over the swipe behavior.

In the snippet above, you cast the viewHolder to a MoviesAdapter.MoviesViewHolder to fetch the movie. The next step is to check if the movie you tried to fetch exists, then update the database.

You do that by adding the next snippet of code to onSwiped, below the declarations you just added:

if (movie != null) { // 1
  val movieId = movie.id // 2
  lifecycleOwner.lifecycleScope.launch { // 3
    if (direction == ItemTouchHelper.RIGHT) { // 4
      moviesRepository.setFavorite(movieId)
    } else if (direction == ItemTouchHelper.LEFT) { // 5
      moviesRepository.removeFavorite(movieId)
    }
  }
}

A few things happened in this snippet:

  1. You added a simple null check for movie. If it isn’t null, you successfully captured the movie that’s bound to the swiped item.
  2. Next, you fetched the movie ID so you can update the appropriate Movie in the database.
  3. Database operations use coroutines, so you need to launch a new coroutine using the lifecycleOwner.lifecycleScope.
  4. If the user swiped RIGHT, you use moviesRepository.setFavorite(movieId) to add the movie to your favorites.
  5. If the user swiped LEFT, you use moviesRepository.removeFavorite(movieId) to remove the movie from your favorites.

Finally, override onMove() by adding the following below onSwiped:

override fun onMove(
  recyclerView: RecyclerView,
  viewHolder: RecyclerView.ViewHolder,
  target: RecyclerView.ViewHolder
): Boolean {
    return false
}

This almost fulfills ItemTouchHelper.Callback‘s requirements. You’ll fill in the details of this function later in the chapter.

Now that you’ve built the animation, you need to connect the callback to your lists.

Connecting the callback to your list

Open PopularMoviesFragment and add the following dependency above onCreateView():

private val moviesRepository: MoviesRepository by inject()

Your next goal is to use this repository to build the callback you implemented.

First, add the folowing lines of code where you set up popularMoviesList in onViewCreated(), below the existing code in binding.popularMoviesList.apply:

// 1
val itemTouchCallback = MyItemTouchHelperCallback(moviesRepository, viewLifecycleOwner)
  
// 2 
val itemTouchHelper = ItemTouchHelper(itemTouchCallback)
  
// 3
itemTouchHelper.attachToRecyclerView(this)

In the code above, you:

  1. Use the class you implemented previously to create an instance of ItemTouchHelper.Callback. You pass in the repository, as well as viewLifecycleOwner from the Fragment.
  2. Build a new ItemTouchHelper, passing in itemTouchCallback.
  3. Attach itemTouchHelper to popularMoviesList to enable the gestures and animation logic.

Now, you need to do the same in FavoriteMoviesFragment.kt to make the gestures work there, too. Above onCreateView add:

private val moviesRepository: MoviesRepository by inject()

Then, below the existing code in binding.favoriteMoviesList.apply, add:

val itemTouchCallback = MyItemTouchHelperCallback(moviesRepository, viewLifecycleOwner)
val itemTouchHelper = ItemTouchHelper(itemTouchCallback)
itemTouchHelper.attachToRecyclerView(this)

The code above creates itemTouchCallback, which is then passed to ItemTouchHelper when building itemTouchHelper. Finally, it attaches itemTouchHelper to favoriteMoviesList. With this, you’ve enabled the gestures and animation for FavoriteMoviesFragment as well.

Once you finish, build and run. Swipe popular movies left and right and see what happens with the list of favorites. Swiping an item left will remove it from favorites, while swiping it right will add it to the favorites.

Now, you can easily add and remove movies from the favorites list in PopularMoviesFragment. Swiping popular movies adds them to and removes from favorites… but the movie just disappears!

Next, try swiping right on FavoriteMoviesFragment.

The item disappears here too! That’s because the swipe gesture removes the item from the screen visually. However, if you swiped right, the item is still there. The data doesn’t change because you haven’t updated MoviesAdapter yet.

Click Popular then Favorites again, and you’ll notice the screen refreshes and the item is still in the list.

Return to MyItemTouchHelperCallback.kt and update onSwiped() by adding the following code to the end of the function:

viewHolder.bindingAdapter?.notifyItemChanged(viewHolder.bindingAdapterPosition)

Here, you notify the adapter that the item at the swiped position changed. The adapter knows that the user swiped the item away, so it will refresh the data and update the UI accordingly.

Build and run. Resetting will now update the items when the user swipes right on them in FavoriteMoviesFragment.

The items just reset their positions!

Item swiping is amazingly simple, yet it’s powerful enough to let you create different experiences for your users, based on your app’s needs. Archiving, deleting, showing menus, adding or removing favorite items and more — you can support all these different actions just with a few lines of code.

Now that you’ve implemented swipe gestures and animations, it’s time to continue to the next way to move items: drag and drop.

Implementing drag-and-drop gestures

Implementing a simple drag-and-drop gesture isn’t hard. However, MoviesAdapter doesn’t let you make any changes to the position because it’s powered by the database.

Currently, your app doesn’t have a way to differentiate items based on their position in the list — which is really useful when building an app where your items have an order of priority. For the purposes of this chapter, however, you’ll just let your users position the movies in the order of their choice.

Setting up the adapter

First, you’ll switch to using MoviesRecyclerAdapter to implement the gesture. Open PopularMoviesFragment.kt and replace popularAdapter with the following:

private val popularAdapter = MoviesRecyclerAdapter()

This is necessary because MoviesAdapter uses data from the database, meaning you have more control over the items in the standard MoviesRecyclerAdapter.

Now that you’ve set up the adapter, you need to change MoviesViewHolder in MoviesRecyclerAdapter.kt to support accessing Movie, as you did previously in MoviesAdapter:

inner class MoviesViewHolder(val binding: ItemMovieBinding) :
  RecyclerView.ViewHolder(binding.root) { 
  var movie: Movie? = null

  fun bind(movie: Movie) {
    this.movie = movie
      
    ...
  }
}

You’ve seen this before: You just added the option to access the Movie that’s bound to the ViewHolder.

Now, add the following function to the adapter below setItems so you can update items while moving them around:

fun onItemMoved(oldPosition: Int, newPosition: Int) {
  val itemToReplace = items[oldPosition] // 1
  items.remove(itemToReplace) // 2

  val positionToMove = if (oldPosition > newPosition) newPosition else newPosition - 1 // 3
  items.add(positionToMove, itemToReplace) // 4

  notifyItemMoved(oldPosition, positionToMove) // 5
}

A few things are going on here:

  1. You fetch the item at its original position, which you’ll replace when you finish the drag-and-drop animation.
  2. You remove that item from the list; later, you’ll add it back in another position.
  3. You determine the item’s new position. If the user moved the item up, you just swap the positions. If they move the item down, on the other hand, the position is equal to newPosition - 1 to accommodate 0-based indices.
  4. Once you know the position, you add the dragged item to the new position.
  5. Finally, you update the adapter using notifyItemMoved(), passing in oldPosition and positionToMove.

To put it simply, you remove the item and add it either above or below its old position, based on where the user moved it. Finally, update onBindViewHolder() in the adapter to disable the long-tap behavior:

override fun onBindViewHolder(holder: MoviesViewHolder, position: Int) {
  holder.bind(items[position]) { movie ->
    // val newIndex = position + 1
    // this.items.add(newIndex, movie)

    // notifyItemInserted(newIndex)
  }
}

This makes it easier to follow the drag animation when the user isn’t using long taps to add new items to the list.

Moving the items

Now that you’ve set up the adapter and the Fragment, head back to ItemTouchHelper.Callback in MyItemTouchHelperCallback.kt. Check out onMove():

  override fun onMove(
    recyclerView: RecyclerView,
    viewHolder: RecyclerView.ViewHolder,
    target: RecyclerView.ViewHolder
  ): Boolean {
    return false
  }

The function above notifies you when the user is dragging and moving an item in the list. This gives you access to three things: the recyclerView where the gesture is happening, the viewHolder the user is dragging and the target. The target is also a ViewHolder, but it represents the element you’re dragging the original item to.

The function expects a Boolean that represents whether the move happened or not. In other words, it lets you know whether the items changed position.

Now, fetch the adapter from the RecyclerView so you can notify it of the change by replacing return false with the following:

    val adapter = recyclerView.adapter as? MoviesRecyclerAdapter

Here, you attempt to fetch adapter and cast it to a MoviesRecyclerAdapter. If the cast succeeds, you can continue with the rest of the logic. Otherwise, adapter will be null.

Now that you have the adapter, you need to notify it about the move. Add the following statement right below the code you just added:

adapter?.onItemMoved(viewHolder.bindingAdapterPosition, target.bindingAdapterPosition)

Here, you use the safe call operator and onItemMoved() to notify the adapter that a change took place. You get the positions from bindingAdapterPosition, which represents the items’ positions on the list.

Finally, you need to make this function return a Boolean, which tells the helper whether you’ve successfully moved the items. In this case, onMove returns true whenever the adapter exists.

Now, add the next line of code below the last line you added:

return adapter != null

By returning adapter != null if the adapter exists, you move the items and the function returns true. This is a straightforward and easy way to set up onMove().

Build and run. Congratulations, you can now move the items!

As you see, you can now drag items up and down by long-tapping on them. When you initiate the drag, you can move the item freely anywhere in the list. Once you place the item where you need it, the adapter will rearrange the data set.

It does so by using onItemMoved() to update the positions of the items in the list. This behavior is useful for all apps that use item ordering and priorities, such as TODO lists, sticky notes, apps with ranking systems and more.

In your example, you didn’t do much with the items. Because they don’t have priorities, you just changed their ordering locally. But with this knowledge, you can do so much more in your personal projects!

Challenges

Challenge 1: Add a Snackbar notification for swipes

Your first challenge is to improve the experience when swiping items by showing a Snackbar that gives the user more information. Your goal is to implement a notification that tells the user if they added or removed the item from the favorites list.

Here’s a hint: First, add a new parameter to MyItemTouchHelperCallback that acts as a callback to notify your Fragment of the change. Then, just use the Snackbar API to show some information on the screen.

Challenge 2: Enable right and left directions for drag and drop

Your second challenge is to improve the drag-and-drop gesture experience by adding flags that enable the user to move items in all directions. So instead of supporting just UP and DOWN dragging, you’ll add RIGHT and LEFT drag too.

As always, you’ll find the solution to both of these challenges in the challenge folder of this chapter’s materials.

Have fun! :]

Key points

  • Swipe animations are great for adding or removing items from lists, showing extra options and showing dialogs.
  • Drag-and-drop animations are useful for reordering items and changing their priorities.
  • ItemTouchHelper is a simple and clean API that lets you enable and react to list item gestures.
  • ItemTouchHelper.Callback gives you more control, while ItemTouchHelper.SimpleCallback offers easier implementation.
  • Using getMovementFlags(), you define which flags the ItemTouchHelper API needs to consume and react to.
  • To build the correct flags, use makeMovementFlags().
  • onSwiped() gives you control over what happens when you swipe items using the helper API. It exposes the swipe direction as well as the ViewHolder that you swiped.
  • To handle drag-and-drop gestures, use onMove().
  • onMove() exposes the parent RecyclerView and the two ViewHolders in question. The ViewHolders represent the item you’ve moved and the position you’re moving the item to.
  • To integrate the ItemTouchHelper.Callback with your RecyclerView, create an ItemTouchHelper with the callback and call itemTouchHelper.attachToRecyclerView(list).
  • When you attach the helper to your list, it automatically propagates the gesture events to your callback.

Where to go from here?

ItemTouchHelper is easy to integrate into lists. It allows you to customize two popular types of motion in lists: swipe and drag and drop. These animations are useful when you change the state of items and data in your app. Depending on the type of app you’re building, you can add many different useful features.

But ItemTouchHelper only allows a specific set of gestures. If you want to explore more options, you can detect gestures by using the touch events API. This allows you to build complex gestures and animations that support custom behavior. It lets you detect all touch events, not just swipes and long touches for dragging.

Using complex touch handling, you can build even more support for apps. For example, think about how chat apps allow you to swipe messages to select items or to show special menus.

Try building these gestures using the touch events API!

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2026 Kodeco Inc.