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:
- How to add swipe-to-remove and drag-to-reorder functionality to a RecyclerView.
Tools Required
- Android Studio. The version used in this tutorial is Bumblebee 2021.1.1 Patch 1.
Prerequisite Knowledge
- Basic Android.
- Basic RecyclerView.
Project Setup
To follow along with the tutorial, perform the steps below:
-
Create a new Android project with the default Empty Activity.
-
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>
-
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" />
-
Create a new Kotlin file called Item.kt. Copy and paste the code below.
data class Item( val content: String )
-
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 }
-
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.
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:
- 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.
- ItemTouchHelper.SimpleCallback: this is also an abstract inner class. It already implements ItemTouchHelper.Callback, so we only have to implement two simple methods,
onMove()
andonSwipe()
, to decide what to do when swiping and dragging actions are recorded. - 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.
Implementing ItemTouchHelper.SimpleCallback
There are about 3 things that we need to perform to implement swipe-to-remove and drag-to-move.
- Create an ItemTouchHelper.SimpleCallback.
- Use that callback to create an ItemTouchHelper.
- Attaches the ItemTouchHelper to the RecyclerView.
Follow the steps below to implement ItemTouchHelper.SimpleCallback.
-
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 ) { }
-
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 thenotifyItemMoved()
publisher. When the RecyclerView receives that signal, it will automatically reorder the items for us. It is optional to update the originaldataset
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 }
-
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 thenotifyItemRemoved()
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.
-
Create the ItemTouchHelper object.
//Creates touch helper with callback val touchHelper = ItemTouchHelper(callback)
-
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.
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.