Android Native - RecyclerView swipe-to-remove and drag-to-reorder

dimitrilc 2 Tallied Votes 217 Views Share

Introduction

In this tutorial, we will learn how to add swipe-to-remove and drag-to-reorder functionalities into RecyclerView.

Goals

At the end of the tutorial, you would have learned:

  1. How to add swipe-to-remove and drag-to-reorder functionality to a RecyclerView.

Tools Required

  1. Android Studio. The version used in this tutorial is Bumblebee 2021.1.1 Patch 1.

Prerequisite Knowledge

  1. Basic Android.
  2. Basic RecyclerView.

Project Setup

To follow along with the tutorial, perform the steps below:

  1. Create a new Android project with the default Empty Activity.

  2. Replace the content of activity_main.xml with the code below.

     <?xml version="1.0" encoding="utf-8"?>
     <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
    
        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recycler_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
            app:layout_constraintTop_toTopOf="parent" />
     </androidx.constraintlayout.widget.ConstraintLayout>
  3. Create a new layout resource called item_view.xml. This is the ViewHolder’s layout. Copy and paste the code below.

     <?xml version="1.0" encoding="utf-8"?>
     <TextView xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/itemView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="16dp"
        android:gravity="center"
        android:textSize="32sp"
        tools:text="TextView" />
  4. Create a new Kotlin file called Item.kt. Copy and paste the code below.

     data class Item(
        val content: String
     )
  5. Create a new Kotlin file called ItemAdapter.kt. Copy and paste the code below.

     import android.view.LayoutInflater
     import android.view.View
     import android.view.ViewGroup
     import android.widget.TextView
     import androidx.recyclerview.widget.RecyclerView
    
     class ItemAdapter(private val dataset: List<Item>): RecyclerView.Adapter<ItemAdapter.ItemViewHolder>() {
    
        class ItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){
            val textView: TextView = itemView.findViewById(R.id.itemView)
        }
    
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
            val view = LayoutInflater.from(parent.context)
                .inflate(R.layout.item_view, parent, false)
    
            return ItemViewHolder(view)
        }
    
        override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
            holder.textView.text = dataset[position].content
        }
    
        override fun getItemCount() = dataset.size
     }
  6. In MainActivity#onCreate(), append the code below.

     //Creates a list of 10 elements
     val dataset = MutableList(10){
        Item("I am Item #$it")
     }
    
     //finds the RecyclerView
     val recyclerView = findViewById<RecyclerView>(R.id.recycler_view)
    
     //Assigns an adapter to RecyclerView
     recyclerView.adapter = ItemAdapter(dataset)

Project Overview

Currently, our project will compile and run just fine. All we have at the moment is a vanilla recycler with 10 items. As you can see from the Gif below with the pointer tracking option on, our RecyclerView currently does not register swiping and hold-to-drag actions.

VanillaRecycler.gif

At the end of the tutorial, we should be able to swipe to remove and hold-to-drag-to-reorder the RecyclerView.

ItemTouchHelper

ItemTouchHelper is a class provided by Android specifically to implement the functionalities that we want. Its most basic properties are:

  1. ItemTouchHelper.Callback: this is an abstract inner class. It is meant to be used only if you require fine control over the swipe and drop behaviors.
  2. ItemTouchHelper.SimpleCallback: this is also an abstract inner class. It already implements ItemTouchHelper.Callback, so we only have to implement two simple methods, onMove() and onSwipe(), to decide what to do when swiping and dragging actions are recorded.
  3. UP, DOWN, LEFT, RIGHT, START, END constants: These constants represent the actions that the user took, which the ItemTouchHelper.Callback class can use.

The picture below provides a rough picture of the relationship among the classes. LEFT and RIGHT are not listed because START and END can perform the same thing for both LTR and RTL layouts.

ItemTouchHelper.jpg

Implementing ItemTouchHelper.SimpleCallback

There are about 3 things that we need to perform to implement swipe-to-remove and drag-to-move.

  1. Create an ItemTouchHelper.SimpleCallback.
  2. Use that callback to create an ItemTouchHelper.
  3. Attaches the ItemTouchHelper to the RecyclerView.

Follow the steps below to implement ItemTouchHelper.SimpleCallback.

  1. Append the code below into MainActivity#onCreate(). Notice that we had to provide the bitwise or result of the direction constants.

     //Implementing the callback.
     val callback = object : ItemTouchHelper.SimpleCallback(
        ItemTouchHelper.UP or ItemTouchHelper.DOWN, //bitwise OR
        ItemTouchHelper.START or ItemTouchHelper.END //bitwise OR
     ) {
    
     }
  2. We have not implemented the required members yet, so let us start with the first required, onMove(). This function determines what to do during drag-and-drop behaviors. All we had to do was to get the adapter positions of the ViewHolders and then call the notifyItemMoved() publisher. When the RecyclerView receives that signal, it will automatically reorder the items for us. It is optional to update the original dataset for our tutorial App.

                override fun onMove(
                    recyclerView: RecyclerView,
                    viewHolder: RecyclerView.ViewHolder,
                    target: RecyclerView.ViewHolder
                ): Boolean {
                    val fromPosition = viewHolder.adapterPosition
                    val toPosition = target.adapterPosition
    
                    //modifying the dataset as well is optional for this tutorial
     //                val movedItem = dataset.removeAt(fromPosition)
     //                dataset.add(toPosition, movedItem)
    
                    //push specific event
                    recyclerView.adapter?.notifyItemMoved(fromPosition, toPosition)
    
                    return true
                }
  3. Next, we will need to implement the other required member, onSwiped(). All we had to do was to remove the item at the requested position, and then call the notifyItemRemoved() publisher.

     override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
        val position = viewHolder.adapterPosition
    
        //Actually removes the item from the dataset
        dataset.removeAt(position)
    
        //push specific event
        recyclerView.adapter?.notifyItemRemoved(position)
     }

Run the App

We only need two more lines of code before we can run the app.

  1. Create the ItemTouchHelper object.

     //Creates touch helper with callback
     val touchHelper = ItemTouchHelper(callback)
  2. Attach it to the RecyclerView.

     //attaches the helper to the recyclerView
     touchHelper.attachToRecyclerView(recyclerView)

Now, run the app. The swiping and dragging functions should work similarly to the Gif below.

SwipeDragRecycler.gif

Solution Code

MainActivity.kt

package com.codelab.daniwebrecyclerviewswipetoremove

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView

class MainActivity : AppCompatActivity() {
   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContentView(R.layout.activity_main)

       //Creates a list of 10 elements
       val dataset = MutableList(10){
           Item("I am Item #$it")
       }

       //finds the RecyclerView
       val recyclerView = findViewById<RecyclerView>(R.id.recycler_view)

       //Assigns an adapter to RecyclerView
       recyclerView.adapter = ItemAdapter(dataset)

       //Implementing the callback.
       val callback = object : ItemTouchHelper.SimpleCallback(
           ItemTouchHelper.UP or ItemTouchHelper.DOWN, //bitwise OR
           ItemTouchHelper.START or ItemTouchHelper.END //bitwise OR
       ) {
           override fun onMove(
               recyclerView: RecyclerView,
               viewHolder: RecyclerView.ViewHolder,
               target: RecyclerView.ViewHolder
           ): Boolean {
               val fromPosition = viewHolder.adapterPosition
               val toPosition = target.adapterPosition

               //modifying the dataset as well is optional for this tutorial
//                val movedItem = dataset.removeAt(fromPosition)
//                dataset.add(toPosition, movedItem)

               //push specific event
               recyclerView.adapter?.notifyItemMoved(fromPosition, toPosition)

               return true
           }

           override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
               val position = viewHolder.adapterPosition

               //Actually removes the item from the dataset
               dataset.removeAt(position)

               //push specific event
               recyclerView.adapter?.notifyItemRemoved(position)
           }

       }

       //Creates touch helper with callback
       val touchHelper = ItemTouchHelper(callback)

       //attaches the helper to the recyclerView
       touchHelper.attachToRecyclerView(recyclerView)
   }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   tools:context=".MainActivity">

   <androidx.recyclerview.widget.RecyclerView
       android:id="@+id/recycler_view"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       app:layout_constraintBottom_toBottomOf="parent"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintStart_toStartOf="parent"
       app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
       app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

item_view.xml

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools"
   android:id="@+id/itemView"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:layout_margin="16dp"
   android:gravity="center"
   android:textSize="32sp"
   tools:text="TextView" />

Item.kt

package com.codelab.daniwebrecyclerviewswipetoremove

data class Item(
   val content: String
)

ItemAdapter.kt

package com.codelab.daniwebrecyclerviewswipetoremove

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView

class ItemAdapter(private val dataset: List<Item>): RecyclerView.Adapter<ItemAdapter.ItemViewHolder>() {

   class ItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){
       val textView: TextView = itemView.findViewById(R.id.itemView)
   }

   override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
       val view = LayoutInflater.from(parent.context)
           .inflate(R.layout.item_view, parent, false)

       return ItemViewHolder(view)
   }

   override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
       holder.textView.text = dataset[position].content
   }

   override fun getItemCount() = dataset.size
}

Summary

We have learned how to add swipe-to-remove and drag-to-reorder in this tutorial. The full project code can be found at https://github.com/dmitrilc/DaniwebRecyclerViewSwipeToRemove.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.