Contents
Android RecyclerView Swipe To Delete & Undo – ItemTouchHelper Class
Using RecyclerView to perform swipe to Delete & Undo is uses the ItemTouchHelper utility class. If you have read the article on using ItemTouchHelper to create a Drag and Drop feature using RecyclerView, this post will be easy to follow. The majority of the heavy load work is done by the ItemTouchHelper Class. ItemTouchHelper.Callback
acts as a bridge between the RecyclerView and the UI motions that you perform.
There are several actions that this utility class captures like, Swipe, Move, ChildDraw, Draw Canvas etc. You can see more about the utility in creating a Swipe to Delete & Undo RecyclerView below.
Creating the Layout
The first objective is to create a simple layout that will be holding the RecyclerView. We have made use of the Coordinator Layout as our Root layout. This is because we cannot use the relative layout to hold the Recyclerview and also at the same time bind a SnackBar to it. This is bad practice since there will not be the support to auto move the elements in the RecyclerView.
<?xml version="1.0" encoding="utf-8"?> <android.support.design.widget.CoordinatorLayout 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:id="@+id/coordinatorLayout" android:layout_height="match_parent"> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.v7.widget.RecyclerView android:id="@+id/recyclerView" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" app:layoutManager="android.support.v7.widget.LinearLayoutManager" /> </RelativeLayout> </android.support.design.widget.CoordinatorLayout>
Create ListItems layout
We are done with the main layout. But the RecyclerView should be holding a list of items. We will use the list_items.xml layout file to create the item list.
Root layout is going to be a CardView, which will help us create a better-looking item list. See the final output to understand the reason for the item attributes.
<?xml version="1.0" encoding="utf-8"?> <android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:card_view="http://schemas.android.com/apk/res-auto" android:id="@+id/cardView" android:layout_width="match_parent" android:layout_height="64dp" android:layout_margin="8dp" card_view:cardCornerRadius="0dp" card_view:cardElevation="2dp"> <RelativeLayout android:id="@+id/relativeLayout" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="8dp" android:paddingLeft="8dp" android:paddingRight="8dp"> <TextView android:id="@+id/txtTitle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:text="Item 1" android:textAppearance="@style/TextAppearance.Compat.Notification.Title" /> </RelativeLayout> </android.support.v7.widget.CardView>
Creating an Adapter for RecyclerView
If you had read the post about using the RecyclerView and classItemTouchHelper.Callback
to create a Drag and Drop feature in Android, you will know that we are in need of an Adapter to dynamically hold our elements. This is done as below. Create RecyclerAdapter.java with the following code
package com.monks.android.newapplication; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.RelativeLayout; import android.widget.TextView; import java.util.ArrayList; import java.util.List; public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerAdapter.MyViewHolder> { private ArrayList<String> data; public class MyViewHolder extends RecyclerView.ViewHolder { private TextView mTitle; RelativeLayout relativeLayout; public MyViewHolder(View itemView) { super(itemView); mTitle = itemView.findViewById(R.id.txtTitle); } } public RecyclerAdapter(ArrayList<String> data) { this.data = data; } @Override public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_items, parent, false); return new MyViewHolder(itemView); } @Override public void onBindViewHolder(MyViewHolder holder, int position) { holder.mTitle.setText(data.get(position)); } @Override public int getItemCount() { return data.size(); } public void removeItem(int position) { data.remove(position); notifyItemRemoved(position); } public void restoreItem(String item, int position) { data.add(position, item); notifyItemInserted(position); } public ArrayList<String> getData() { return data; } }
This is a pretty straightforward template class structure for creating a custom adapter. However, we additionally make use of 2 methods removeItem() and restoreItem().
This method will be called by our next important class, which inherits the ItemTouchHelper.Callback
Creating UIActionClass inheriting ItemTouchHelper.Callback
Time to create our UIAction class that will inherit the ItemTouchHelper.Callback to enable us to perform Swipe action on the RecyclerView items.
We will make use of the list_items.xml layout file to insert a delete icon which will show up on swiping left.
To understand better, see the code below
package com.monks.android.newapplication; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.support.annotation.NonNull; import android.support.v4.content.ContextCompat; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.helper.ItemTouchHelper; import android.view.View; abstract public class UIActionClass extends ItemTouchHelper.Callback { Context mContext; private Paint mClearPaint; private ColorDrawable mBackground; private int backgroundColor; private Drawable deleteDrawable; private int intrinsicWidth; private int intrinsicHeight; UIActionClass(Context context) { mContext = context; mBackground = new ColorDrawable(); backgroundColor = Color.parseColor("#b80f0a"); mClearPaint = new Paint(); mClearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); deleteDrawable = ContextCompat.getDrawable(mContext, android.R.drawable.ic_menu_delete); intrinsicWidth = deleteDrawable.getIntrinsicWidth(); intrinsicHeight = deleteDrawable.getIntrinsicHeight(); } @Override public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) { return makeMovementFlags(0, ItemTouchHelper.LEFT); } @Override public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder viewHolder1) { return false; } @Override public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) { super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); View itemView = viewHolder.itemView; int itemHeight = itemView.getHeight(); boolean isCancelled = dX == 0 && !isCurrentlyActive; if (isCancelled) { clearCanvas(c, itemView.getRight() + dX, (float) itemView.getTop(), (float) itemView.getRight(), (float) itemView.getBottom()); super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); return; } mBackground.setColor(backgroundColor); mBackground.setBounds(itemView.getRight() + (int) dX, itemView.getTop(), itemView.getRight(), itemView.getBottom()); mBackground.draw(c); int deleteIconTop = itemView.getTop() + (itemHeight - intrinsicHeight) / 2; int deleteIconMargin = (itemHeight - intrinsicHeight) / 2; int deleteIconLeft = itemView.getRight() - deleteIconMargin - intrinsicWidth; int deleteIconRight = itemView.getRight() - deleteIconMargin; int deleteIconBottom = deleteIconTop + intrinsicHeight; deleteDrawable.setBounds(deleteIconLeft, deleteIconTop, deleteIconRight, deleteIconBottom); deleteDrawable.draw(c); super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); } private void clearCanvas(Canvas c, Float left, Float top, Float right, Float bottom) { c.drawRect(left, top, right, bottom, mClearPaint); } @Override public float getSwipeThreshold(@NonNull RecyclerView.ViewHolder viewHolder) { return 0.7f; } }
There are a lot of things that are happening in the above class. Stepwise statements are below.
- The 4 main callbacks which we use here are getMovementFlags(), onChildDraw(), onSwipeThreshold() and clearCanvas()
- The getMovementFlags() callback gets fired when the user swipes on the RecyclerView. When we set the makeMovementFlags() and call ItemTouchHelper.LEFT.
- onChildDraw() takes care of performing the swiping action and painting the rest of the screen space red. NOTE: The ItemTouchHelper by default will not remove any items, it merely creates an action of swiping.
- clearCanvas() like the name suggests clears the item from the canvas.
RecyclerActivity to perform Swipe to Delete and Undo
The final activity to stitch all the components above is created in the RecyclerActivity. The main activities performed are,
- Creating an Adapter object and passing it to the RecyclerAdapter
- We create an onSwipeEnabled() method, to detect the swiping action with the UIActionClass object.
package com.monks.android.newapplication; import android.graphics.Color; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.design.widget.CoordinatorLayout; import android.support.design.widget.Snackbar; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.helper.ItemTouchHelper; import android.view.View; import java.util.ArrayList; public class RecyclerActivity extends AppCompatActivity { RecyclerView recyclerView; RecyclerAdapter mAdapter; ArrayList<String> stringArrayList = new ArrayList<>(); CoordinatorLayout coordinatorLayout; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.recycler_view); recyclerView = findViewById(R.id.recyclerView); coordinatorLayout = findViewById(R.id.coordinatorLayout); stringArrayList.add("AndroidMonks"); stringArrayList.add("TextView"); stringArrayList.add("Buttons"); stringArrayList.add("EditText"); stringArrayList.add("ImageView"); stringArrayList.add("Relative Layout"); stringArrayList.add("Linear Layout"); mAdapter = new RecyclerAdapter(stringArrayList); recyclerView.setAdapter(mAdapter); enableSwipeToDeleteAndUndo(); } private void enableSwipeToDeleteAndUndo() { UIActionClass swipeToDeleteCallback = new UIActionClass(this) { @Override public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int i) { final int position = viewHolder.getAdapterPosition(); final String item = mAdapter.getData().get(position); mAdapter.removeItem(position); Snackbar snackbar = Snackbar .make(coordinatorLayout, "Item was removed from the list.", Snackbar.LENGTH_LONG); snackbar.setAction("UNDO", new View.OnClickListener() { @Override public void onClick(View view) { mAdapter.restoreItem(item, position); recyclerView.scrollToPosition(position); } }); snackbar.setActionTextColor(Color.YELLOW); snackbar.show(); } }; ItemTouchHelper itemTouchhelper = new ItemTouchHelper(swipeToDeleteCallback); itemTouchhelper.attachToRecyclerView(recyclerView); } }
Upon finishing these classes, run this in your AVD.
how u created abstract class method object???
UIActionClass swipeToDeleteCallback = new UIActionClass(this) {