Let us make Kotlin RecyclerView Swipe To Delete Android Like Gmail App.
You will learn to create recycler view swipe to show delete and undo button example.
It is easy for users to just swipe left or right to delete any row from the recycler view.
You can use this feature for flexibility as well as simplicity.
First of all, see the following video.
Step 1. New Project and Gradle
In your android studio, create a new project with empty activity.
Also, do not forget to set Kotlin as the primary language for your project.
Now open your build.gradle(Module: app) file. Add the following lines in this file
implementation 'com.android.support:recyclerview-v7:28.0.0' implementation 'com.android.support:cardview-v7:28.0.0' implementation 'com.android.support:design:28.0.0'
First line will give us access to the classes of the recycler view.
Similarly, second line will allow us to use card view along with the recycler view.
Finally, last line is for material design. We are implementing these lines because android do not have any UI widgets for them like text view or Image View.
Step 2. Add Delete Image
First, download the Delete Icon by clicking the below link.
Click to Download Delete Icon.
After downloading the above image, copy it into the app->res->drawable directory.
Step 3. XML File and Model Class
Inside app->res->layout directory, make a new XML file and give it a name like rv_item.xml
rv_item.xml file should have the following source lines
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:app="schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingBottom="5dp" android:paddingLeft="5dp" android:paddingRight="5dp" tools:context=".MainActivity" tools:ignore="NamespaceTypo"> <android.support.v7.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto" android:id="@+id/card_view" android:layout_width="match_parent" android:layout_height="90dp" android:layout_gravity="center" app:cardBackgroundColor="#ffffff" android:layout_marginTop="0dp" card_view:cardCornerRadius="1dp"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <TextView android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/tv" android:height="90dp" android:gravity="center" android:paddingLeft="10dp" android:text="Image" android:textColor="#000" android:textStyle="bold" android:textSize="18sp" /> </LinearLayout> </android.support.v7.widget.CardView> </RelativeLayout>
This file has one text view inside the card view. Thus, every row of recycler view will have the card view along with one text view.
Now make a new class and give it a name like SwipeModel.kt
SwipeModel.kt have the following source snippet
class Model { var name: String? = null fun getNames(): String { return name.toString() } fun setNames(name: String) { this.name = name } }
One string variable is there in the above code. It will print the name of the vehicle in the recycler view.
For this string variable, appropriate getter and setter methods are also there which will help us in the data maintenance.
Step 4. Adapter For Swipe
Make a new class and give it a name as SwipeAdapter.kt
Below is the coding snippet for SwipeAdapter.kt file.
import android.content.Context import android.support.v7.widget.RecyclerView import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.TextView import java.util.ArrayList /** * Created by Parsania Hardik on 26-Jun-17. */ class SwipeAdapter(ctx: Context, private val imageModelArrayList: ArrayList<Model>) : RecyclerView.Adapter<SwipeAdapter.MyViewHolder>() { private val inflater: LayoutInflater init { inflater = LayoutInflater.from(ctx) } fun removeItem(position: Int) { imageModelArrayList.removeAt(position) notifyItemRemoved(position) notifyItemRangeChanged(position, imageModelArrayList.size) } fun restoreItem(model: Model, position: Int) { imageModelArrayList.add(position, model) // notify item added by position notifyItemInserted(position) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { val view = inflater.inflate(R.layout.rv_item, parent, false) return MyViewHolder(view) } override fun onBindViewHolder(holder: MyViewHolder, position: Int) { holder.time.setText(imageModelArrayList[position].getNames()) } override fun getItemCount(): Int { return imageModelArrayList.size } inner class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { var time: TextView init { time = itemView.findViewById(R.id.tv) as TextView } } }
A method onCreateViewHolder() will inflate the rv_item.xml file to create the view of every row.
Then onBindViewHolder() method will set the vehicle name into the text view. For this, it will use imageModelArrayList as a data source.
imageModelArrayList is the arraylist with the objects of the SwipeModel class. Adapter is getting it from the parameters.
Now see the following function
fun removeItem(position: Int) { imageModelArrayList.removeAt(position) notifyItemRemoved(position) notifyItemRangeChanged(position, imageModelArrayList.size) }
This function will be called when the user swipes left or right and do not press undo button.
Compiler will remove the object of the position which user have swiped. (If first row then position will be 0)
Then after notifyItemRemoved() method will be called and finally, notifyItemRangeChanged() method will refresh the recycler view.
Now read the following function
fun restoreItem(model: Model, position: Int) { imageModelArrayList.add(position, model) // notify item added by position notifyItemInserted(position) }
When the user clicks the Undo button after deleting the row, compiler will execute the below method.
It will first add the deleted object and then it will run notifyItemInserted() method to refresh the recycler view.
Step 5. Changing Main Lines
There are two files for main activity. You need to add the following lines in activity_main.xml file.
<?xml version="1.0" encoding="utf-8"?> <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/linear" android:background="#e4e0e0" tools:context=".MainActivity"> <android.support.v7.widget.RecyclerView android:id="@+id/recycler" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginTop="10dp"/> </android.support.design.widget.CoordinatorLayout>
This main file have just one recycler view.
Now write down the below source code lines in MainActivity.kt file.
import android.graphics.Bitmap import android.graphics.BitmapFactory import android.graphics.Color import android.graphics.Paint import android.graphics.RectF import android.support.design.widget.Snackbar import android.support.v7.app.AppCompatActivity import android.os.Bundle import android.support.v7.widget.LinearLayoutManager import android.support.v7.widget.RecyclerView import android.graphics.Canvas import java.util.ArrayList import android.support.v7.widget.helper.ItemTouchHelper class MainActivity : AppCompatActivity() { private var recyclerView: RecyclerView? = null private var imageModelArrayList: ArrayList<Model>? = null private var adapter: SwipeAdapter? = null private val p = Paint() private val myImageNameList = arrayOf("Benz", "Bike", "Car", "Carrera", "Ferrari", "Harly", "Lamborghini", "Silver") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) recyclerView = findViewById(R.id.recycler) as RecyclerView imageModelArrayList = populateList() adapter = SwipeAdapter(this, imageModelArrayList!!) recyclerView!!.adapter = adapter recyclerView!!.layoutManager = LinearLayoutManager(applicationContext, LinearLayoutManager.VERTICAL, false) enableSwipe() } private fun enableSwipe() { val simpleItemTouchCallback = object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) { override fun onMove( recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder ): Boolean { return false } override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { val position = viewHolder.adapterPosition if (direction == ItemTouchHelper.LEFT) { val deletedModel = imageModelArrayList!![position] adapter!!.removeItem(position) // showing snack bar with Undo option val snackbar = Snackbar.make( window.decorView.rootView, " removed from Recyclerview!", Snackbar.LENGTH_LONG ) snackbar.setAction("UNDO") { // undo is selected, restore the deleted item adapter!!.restoreItem(deletedModel, position) } snackbar.setActionTextColor(Color.YELLOW) snackbar.show() } else { val deletedModel = imageModelArrayList!![position] adapter!!.removeItem(position) // showing snack bar with Undo option val snackbar = Snackbar.make( window.decorView.rootView, " removed from Recyclerview!", Snackbar.LENGTH_LONG ) snackbar.setAction("UNDO") { // undo is selected, restore the deleted item adapter!!.restoreItem(deletedModel, position) } snackbar.setActionTextColor(Color.YELLOW) snackbar.show() } } override fun onChildDraw( c: Canvas, recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, dX: Float, dY: Float, actionState: Int, isCurrentlyActive: Boolean ) { val icon: Bitmap if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) { val itemView = viewHolder.itemView val height = itemView.bottom.toFloat() - itemView.top.toFloat() val width = height / 3 if (dX > 0) { p.color = Color.parseColor("#388E3C") val background = RectF(itemView.left.toFloat(), itemView.top.toFloat(), dX, itemView.bottom.toFloat()) c.drawRect(background, p) icon = BitmapFactory.decodeResource(resources, R.drawable.delete) val icon_dest = RectF( itemView.left.toFloat() + width, itemView.top.toFloat() + width, itemView.left.toFloat() + 2 * width, itemView.bottom.toFloat() - width ) c.drawBitmap(icon, null, icon_dest, p) } else { p.color = Color.parseColor("#D32F2F") val background = RectF( itemView.right.toFloat() + dX, itemView.top.toFloat(), itemView.right.toFloat(), itemView.bottom.toFloat() ) c.drawRect(background, p) icon = BitmapFactory.decodeResource(resources, R.drawable.delete) val icon_dest = RectF( itemView.right.toFloat() - 2 * width, itemView.top.toFloat() + width, itemView.right.toFloat() - width, itemView.bottom.toFloat() - width ) c.drawBitmap(icon, null, icon_dest, p) } } super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive) } } val itemTouchHelper = ItemTouchHelper(simpleItemTouchCallback) itemTouchHelper.attachToRecyclerView(recyclerView) } private fun populateList(): ArrayList<Model> { val list = ArrayList<Model>() for (i in 0..7) { val imageModel = Model() imageModel.setNames(myImageNameList[i]) list.add(imageModel) } return list } }
Diving In Main Kotlin
First of all, see the below
private var recyclerView: RecyclerView? = null private var imageModelArrayList: ArrayList<Model>? = null private var adapter: SwipeAdapter? = null private val p = Paint() private val myImageNameList = arrayOf("Benz", "Bike", "Car", "Carrera", "Ferrari", "Harly", "Lamborghini", "Silver")
First, compiler will create the object of recycler view, Swipe adapter, Paint classes.
An array with the objects of an Model class is imageModelArrayList
Another string array contains the names of the vehicles.
Now read the below lines
imageModelArrayList = populateList() adapter = SwipeAdapter(this, imageModelArrayList!!) recyclerView!!.adapter = adapter recyclerView!!.layoutManager = LinearLayoutManager(applicationContext, LinearLayoutManager.VERTICAL, false) enableSwipe()
Compiler will use the populateList() method to fill the data into imageModelArrayList
Then it will bind the adapter to the recycler view. After this, compiler will call enableSwipe() method.
Look at the below snippet from enableSwipe() method.
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { val position = viewHolder.adapterPosition if (direction == ItemTouchHelper.LEFT) { val deletedModel = imageModelArrayList!![position] adapter!!.removeItem(position) // showing snack bar with Undo option val snackbar = Snackbar.make( window.decorView.rootView, " removed from Recyclerview!", Snackbar.LENGTH_LONG ) snackbar.setAction("UNDO") { // undo is selected, restore the deleted item adapter!!.restoreItem(deletedModel, position) } snackbar.setActionTextColor(Color.YELLOW) snackbar.show() } else { val deletedModel = imageModelArrayList!![position] adapter!!.removeItem(position) // showing snack bar with Undo option val snackbar = Snackbar.make( window.decorView.rootView, " removed from Recyclerview!", Snackbar.LENGTH_LONG ) snackbar.setAction("UNDO") { // undo is selected, restore the deleted item adapter!!.restoreItem(deletedModel, position) } snackbar.setActionTextColor(Color.YELLOW) snackbar.show() } }
Compiler will call the above onSwiped() method when the user swipes any row to the left or right side.
Then it will check one if condition for detecting the left or right swipe. Compiler will first get the object which it wants to remove.
Then it will execute removeItem() method from the adapter class. After this, it will show one snack bar.
When the user clicks the UNDO button on snackbar, compiler will call restoreItem() method from adapter.
Download Kotlin RecyclerView Swipe to Delete
https://github.com/demonuts/Kotlin-RecyclerView-Swipe-To-Delete-Android-Like-Gmail