In the previous tutorial we have seen example of deleting items of recyclerView on swiping ,In this tutorial we will how to do for swiped items of recyclerView .
ItemTouchHelper
ItemTouchHelper is a utility class to add swipe to dismiss and drag & drop support to RecyclerView. It works with a RecyclerView and a Callback class, which configures what type of interactions are enabled and also receives events when user performs these actions.
Add RecyclerView to your layout
Create XML Layout activity_main.xml , this will be set as content view for launcher Activity (MainActivity.kt) and add RecyclerView to your layout file .
file : activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/recyclerView"/>
Data Class
file : Model.kt
package com.tutorialsbuzz.recyclerviewswipetodeleteundo data class Model(val name: String, val version:String) {}
To Load Data Into RecyclerView , We will read JSON File Kept Inside Asset folder and map it to above defined data class .
file : main\assets\android_version.json
[ { "name": "Cupcake", "version": "Android 1.5" }, { "name": "Donut", "version": "Android 1.6" }, { "name": "Eclairs", "version": "Android 2.0-2.1" }, { "name": "Froyo", "version": "Android 2.2-2.3" }, { "name": "Gingerbread", "version": "Android 2.3-2.3.7" }, { "name": "Honeycomb", "version": "Android 3.0-3.2.6" }, { "name": "Icecream", "version": "Android 4.0-4.0.4" }, { "name": "Jellybean", "version": "Android 4.1-4.3.1" }, { "name": "Kitkat", "version": "Android 4.4-4.4.4" }, { "name": "Lolipop", "version": "Android 5.0-5.1.1" }, { "name": "Marshmallow", "version": "Android 6.0-6.0.1" }, { "name": "Nougat", "version": "Android 7.0-7.1.2" }, { "name": "Oreo", "version": "Android 8.0-8.1" }, { "name": "Pie", "version": "Android 9.0" } ]
Image Resource
Name field from json matches to respective png file name kept inside drawable .
Folder : app\src\main\res\drawable
Adapter and ViewHolder For RecyclerView
1. XML Layout For RecyclerView Item
Create XML Layout file in res/layout and name it row_item.xml , This Layout defines the layout for Items of RecyclerView . Here In this example we have two TextView inside LinearLayout .
Each Row Item Of RecyclerView Includes Regular-layout and swipe layout wrapped inside FrameLayout Please find below diagram and xml code .
- Regular Layout is shown when items of recyclerView is loaded and when user clicks undo .
- Swipe layout is shown when user swipes items of recyclerView.
file : row_item.xml
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content"> <!-- Swipe Layout--> <include layout="@layout/swipe_row_item" /> <!-- Regular Layout--> <include layout="@layout/regular_row_item" /> </FrameLayout>
file : swipe_row_item.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/swipeLayout" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:visibility="visible" android:background="@android:color/holo_red_dark" android:padding="30dp" android:weightSum="3"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="1" android:text="archived" android:textColor="@android:color/white" android:textSize="24sp"/> <TextView android:id="@+id/undo" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="2" android:gravity="end" android:paddingBottom="5dp" android:paddingLeft="16dp" android:paddingRight="16dp" android:paddingTop="5dp" android:text="undo" android:textColor="@android:color/white" android:textSize="22sp" android:textStyle="bold"/> </LinearLayout>
file : regular_row_item.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:card_view="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="16dp" android:id="@+id/regularLayout" android:orientation="vertical"> <ImageView android:layout_width="80dp" android:layout_height="80dp" android:id="@+id/img" android:contentDescription="@string/app_name"/> <TextView android:id="@+id/txt" android:textSize="22sp" android:text="Title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textStyle="bold" android:layout_toEndOf="@+id/img" android:layout_marginStart="20dp"/> <TextView android:id="@+id/sub_txt" android:textSize="18sp" android:text="Title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textStyle="italic" android:layout_toEndOf="@+id/img" android:layout_below="@+id/txt" android:layout_marginStart="20dp"/> </RelativeLayout>
2. Adapter
Create a Adapter That RecyclerView Can Use , Create a class CustomAdapter extend it to RecyclerView.Adapter .
3. ViewHolder
Create Inner class ViewHolder extend it to RecyclerView.ViewHolder
file : CustomAdapter.kt
package com.tutorialsbuzz.recyclerviewswipetodeleteundo import android.content.Context import android.os.Handler import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import com.tutorialsbuzz.recyclerviewswipetodeleteundo.R import kotlinx.android.synthetic.main.regular_row_item.view.* import kotlinx.android.synthetic.main.swipe_row_item.view.* class CustomAdapter(val modelList: MutableList<Model>, val context: Context) : RecyclerView.Adapter<RecyclerView.ViewHolder>() { private var itemsPendingRemoval: MutableList<Model>? var PENDING_REMOVAL_TIMEOUT: Long = 3000 var handler: Handler? = Handler() var pendingRunnables: HashMap<Model, Runnable>? = HashMap() init { itemsPendingRemoval = mutableListOf<Model>() } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { (holder as ViewHolder).bind(modelList.get(position)); } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { val layoutInflater = LayoutInflater.from(parent.context) return ViewHolder(layoutInflater.inflate(R.layout.row_item, parent, false)) } override fun getItemCount(): Int { return modelList.size; } inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { fun bind(model: Model): Unit { if (itemsPendingRemoval!!.contains(model)) { //show swipe layout itemView.swipeLayout.visibility = View.VISIBLE itemView.regularLayout.visibility = View.GONE itemView.undo.setOnClickListener({ view -> undoOpt(model) }) } else { //show regular layout itemView.swipeLayout.visibility = View.GONE itemView.regularLayout.visibility = View.VISIBLE itemView.txt.text = model.name itemView.sub_txt.text = model.version val id = context.resources.getIdentifier(model.name.toLowerCase(), "drawable", context.packageName) itemView.img.setBackgroundResource(id) } } private fun undoOpt(model: Model) { val pendingRemovalRunnable: Runnable? = pendingRunnables?.get(model) pendingRunnables?.remove(model) if (pendingRemovalRunnable != null) handler?.removeCallbacks(pendingRemovalRunnable) itemsPendingRemoval?.remove(model) // this will rebind the row in "normal" state notifyItemChanged(modelList.indexOf(model)) } } fun pendingRemoval(position: Int) { val data = modelList.get(position) if (!itemsPendingRemoval!!.contains(data)) { itemsPendingRemoval?.add(data) // this will redraw row in "undo" state notifyItemChanged(position) // let's create, store and post a runnable to remove the data val pendingRemovalRunnable = Runnable { remove(modelList.indexOf(data)) } handler?.postDelayed(pendingRemovalRunnable, PENDING_REMOVAL_TIMEOUT) // pendingRunnables!![data] = pendingRemovalRunnable pendingRunnables?.put(data, pendingRemovalRunnable) } } fun remove(position: Int) { val data = modelList.get(position) if (itemsPendingRemoval!!.contains(data)) { itemsPendingRemoval?.remove(data) } if (modelList.contains(data)) { //dataList.remove(position) modelList.removeAt(position) notifyItemRemoved(position) } } fun isPendingRemoval(position: Int): Boolean { val data = modelList.get(position) return itemsPendingRemoval!!.contains(data) } }
ItemTouchHelper CallBack For RecyclerView
Create an abstract class SwipeToDeleteCallback extend this class to ItemTouchHelper.SimpleCallback
- onSwiped : Called when a ViewHolder is swiped by the user. At this point, you should update your adapter (e.g. remove the item) and call related Adapter notify event.
- getSwipeDirs : This callback Returns the swipe directions for the provided ViewHolder. (Note : To disable swipe at particular viewholder return to ItemTouchHelper.ACTION_STATE_IDLE ) .
package com.tutorialsbuzz.recylerviewswipetodelete import android.content.Context import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.RecyclerView abstract class SwipeToDeleteCallback(var context: Context, dragDir: Int, swipeDir: Int) : ItemTouchHelper.SimpleCallback(dragDir, swipeDir) { override fun onMove( recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder ): Boolean { return false } }
Attaching ItemTouchHelper to RecyclerView .
Creates an instance of ItemTouchHelper that will work with the given Callback.
Call attachToRecyclerView on ItemTouchHelper instance by passing recyclerView .
val swipeToDeleteCallback = object : SwipeToDeleteCallback(this, 0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) { override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { adapter.pendingRemoval(viewHolder.adapterPosition) } override fun getSwipeDirs(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int { if (adapter.isPendingRemoval(viewHolder.adapterPosition)) { return ItemTouchHelper.ACTION_STATE_IDLE } return super.getSwipeDirs(recyclerView, viewHolder) } } val itemTouchHelper = ItemTouchHelper(swipeToDeleteCallback) itemTouchHelper.attachToRecyclerView(recyclerView)
Swipe Undo
Pending Removals
- When user swipes add item to pending removal list and show swipe layout and hide regular layout.
- Using Handler Add Runnable to message queue , Runnable will execute after the specified amount of time elapses ,
- Inside runnable remove the item at the swiped position .
Undo Remove / delete
- when undo is clicked remove the handler Callback at swiped postion and hide swipe layout and show regular layout .
Disable Swipe at Position in RecyclerView
- If user has already swiped once then disable swipe for that particular item of recyclerView , to do that Inside getSwipeDirs we will check if item is already added to pendingremoval list by return ItemTouchHelper.ACTION_STATE_IDLE else return super.getSwipeDirs(recyclerView, viewHolder)
Activity
file : MainActivity.kt
package com.tutorialsbuzz.recyclerviewswipetodeleteundo import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.tutorialsbuzz.recyclerview.CustomAdapter import com.tutorialsbuzz.recyclerview.Model import com.tutorialsbuzz.recylerviewswipetodelete.SwipeToDeleteCallback import kotlinx.android.synthetic.main.activity_main.* import org.json.JSONArray import org.json.JSONObject class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val layoutManager: RecyclerView.LayoutManager? = LinearLayoutManager(this, RecyclerView.VERTICAL, false) recyclerView.layoutManager = layoutManager val modelList = readFromAsset(); val adapter = CustomAdapter(modelList, this) recyclerView.adapter = adapter; recyclerView.addItemDecoration(SimpleDividerItemDecoration(this)) val swipeToDeleteCallback = object : SwipeToDeleteCallback(this, 0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) { override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { adapter.pendingRemoval(viewHolder.adapterPosition) } override fun getSwipeDirs(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int { if (adapter.isPendingRemoval(viewHolder.adapterPosition)) { return ItemTouchHelper.ACTION_STATE_IDLE } return super.getSwipeDirs(recyclerView, viewHolder) } } val itemTouchHelper = ItemTouchHelper(swipeToDeleteCallback) itemTouchHelper.attachToRecyclerView(recyclerView) } private fun readFromAsset(): MutableList<Model> { val modeList = mutableListOf<Model>() val bufferReader = application.assets.open("android_version.json").bufferedReader() val json_string = bufferReader.use { it.readText() } val jsonArray = JSONArray(json_string); for (i in 0..jsonArray.length() - 1) { val jsonObject: JSONObject = jsonArray.getJSONObject(i) val model = Model(jsonObject.getString("name"), jsonObject.getString("version")) modeList.add(model) } return modeList } }
No comments:
Post a Comment