How to create recyclerview with Swipe Menu in android ?

In this post, I am explaining how to create a recyclerview with a swipe menu. by swiping the right side of the recyclerview, you can see the menu to edit and delete the particular item in the recyclerview.

We are going to create a menu on top of the recyclerview items. But, I am not going to explain recyclerview in this post. Check the below link to learn recyclerview in detail.

Recyclerview Android Example [Beginners] – Howtodoandroid

In this tutorial, we are going to see how to add a swipe menu over the recyclerview with an example

Let’s create a sample application to list all the tasks and by swiping right to left, we need to show the swiping menu. Also, we are going to perform by clicking the menu items. check the demo video.

1. Adding Recyclerview dependency

So, we are going to add a swipe menu on top of the recyclerview items. So, we need both recyclerview and cardview dependencies. Both are part of the material design library. For that. I am adding material design dependency.

dependencies { .... implementation 'com.google.android.material:material:1.1.0' }
Code language: JavaScript (javascript)

Material design also has more useful components for android development. To know more about material design, check below

Android Chips – Material Component For Android (howtodoandroid.com)

2. Create Data Class For Recyclerview

To list all the tasks, we need to have a class called Task.java to hold the task list.

public class Task { private String name; private String desc; public Task(String name, String desc) { this.name = name; this.desc = desc; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getDesc() { return desc; } public void setDesc(String desc) { this.desc = desc; } }
Code language: JavaScript (javascript)

3. Setup UI For Recyclerview And Swipe Menu

In this step, First, create activity_main.xml layout with recyclerview.

<?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/recyclerview" android:layout_width="match_parent" android:layout_height="0dp" tools:itemCount="5" tools:listitem="@layout/task_item" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
Code language: HTML, XML (xml)

Once created the UI for the recyclerview. The next step is to create a layout for the recyclerview adapter. let’s create task_item.xml.

<?xml version="1.0" encoding="utf-8"?> <RelativeLayout 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="wrap_content" android:orientation="vertical"> <LinearLayout android:id="@+id/rowBG" android:layout_width="wrap_content" android:layout_height="70dp" android:layout_alignParentRight="true" android:gravity="right" android:padding="10dp" android:layout_margin="10dp" android:background="#d65819" android:orientation="horizontal"> <RelativeLayout android:id="@+id/edit_task" android:layout_width="40dp" android:layout_height="match_parent" android:clickable="true" android:focusable="true" android:orientation="vertical"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:layout_centerInParent="true" android:gravity="center" android:orientation="vertical"> <ImageView android:id="@+id/img_edit" android:layout_width="30dp" android:layout_height="30dp" android:layout_gravity="center" android:src="@drawable/ic_edit_black_24dp" android:tint="@android:color/white" /> </LinearLayout> </RelativeLayout> <RelativeLayout android:id="@+id/delete_task" android:layout_width="40dp" android:layout_height="match_parent" android:clickable="true" android:focusable="true" android:orientation="vertical"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:layout_centerInParent="true" android:gravity="center" android:orientation="vertical"> <ImageView android:id="@+id/img_delete" android:layout_width="30dp" android:layout_height="30dp" android:layout_gravity="center" android:src="@drawable/ic_baseline_delete_24" android:tint="@android:color/white" /> </LinearLayout> </RelativeLayout> </LinearLayout> <LinearLayout android:id="@+id/rowFG" android:layout_width="match_parent" android:layout_height="70dp" android:background="@android:color/white" android:clickable="true" android:elevation="4dp" android:focusable="true" android:orientation="horizontal" android:layout_margin="10dp" android:visibility="visible"> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent" android:foreground="?attr/selectableItemBackground"> <TextView android:id="@+id/task_name" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginEnd="8dp" android:layout_marginLeft="8dp" android:layout_marginRight="8dp" android:layout_marginStart="8dp" tools:text="@tools:sample/first_names" style="@style/TextAppearance.AppCompat.Headline" app:layout_constraintBottom_toTopOf="@+id/task_desc" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/task_desc" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginEnd="8dp" android:layout_marginLeft="8dp" android:layout_marginRight="8dp" android:layout_marginStart="8dp" tools:text="@tools:sample/cities" android:lines="1" android:layout_marginTop="5dp" style="@style/TextAppearance.AppCompat.Medium" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="@+id/task_name" app:layout_constraintTop_toBottomOf="@+id/task_name" /> </androidx.constraintlayout.widget.ConstraintLayout> </LinearLayout> </RelativeLayout>
Code language: HTML, XML (xml)

In the above task_item.xml, we have two LinearLayouts.

rowBG — holding the view for the background. That’s swipe menu items. in our example, we are having edit and delete views.

Swipe Menu Design

rowFG — This layout holds the view for the main items. In our case, displaying all the tasks.

adapter item design

Note: In the XML layout both rowBG, rowFG should have the same height. So that the view looks like a single view. otherwise, it will overlap with another view.

In this layout, I have used cardview for the recyclerview items. If you like to know more about cardview check the below link.

Cardview with Recyclerview Android Example [beginners] (howtodoandroid.com)

Now, we have created views for the recyclerview and the swipe menu. let’s implement the swiping menu option programmatically.

4. Implement the Swiping Menu For Recyclerview

First, set up the adapter for the recyclerview RecyclerviewAdapter.java. This one is the same as a normal recyclerview adapter. Adding the swipe menu will see in below.

public class RecyclerviewAdapter extends RecyclerView.Adapter<RecyclerviewAdapter.MyViewHolder> { private Context mContext; private List<Task> taskList; RecyclerviewAdapter(Context context){ mContext = context; taskList = new ArrayList<>(); } @NonNull @Override public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View view = LayoutInflater.from(mContext).inflate(R.layout.task_item,parent,false); return new MyViewHolder(view); } @Override public void onBindViewHolder(@NonNull MyViewHolder holder, int position) { Task task = taskList.get(position); holder.tvTaskName.setText(task.getName()); holder.tvTaskDesc.setText(task.getDesc()); } @Override public int getItemCount() { return taskList.size(); } public void setTaskList(List<Task> taskList) { this.taskList = taskList; notifyDataSetChanged(); } public class MyViewHolder extends RecyclerView.ViewHolder { private TextView tvTaskName; private TextView tvTaskDesc; public MyViewHolder(View itemView) { super(itemView); tvTaskName = itemView.findViewById(R.id.task_name); tvTaskDesc = itemView.findViewById(R.id.task_desc); } } }
Code language: PHP (php)

To implement swiping functionality in recyclerview, we need to create a class called RecyclerTouchListener.java to perform the swiping menu operation. All the explanations about this class operation are provided in the comments of this class. Please refer to that also.

public class RecyclerTouchListener implements RecyclerView.OnItemTouchListener, OnActivityTouchListener { private static final String TAG = "RecyclerTouchListener"; final Handler handler = new Handler(); Activity act; List<Integer> unSwipeableRows; /* * independentViews are views on the foreground layer which when clicked, act "independent" from the foreground * ie, they are treated separately from the "row click" action */ List<Integer> independentViews; List<Integer> unClickableRows; List<Integer> optionViews; Set<Integer> ignoredViewTypes; // Cached ViewConfiguration and system-wide constant values private int touchSlop; private int minFlingVel; private int maxFlingVel; private long ANIMATION_STANDARD = 300; private long ANIMATION_CLOSE = 150; // Fixed properties private RecyclerView rView; // private SwipeListener mSwipeListener; private int bgWidth = 1, bgWidthLeft = 1; // 1 and not 0 to prevent dividing by zero // Transient properties // private List<PendingDismissData> mPendingDismisses = new ArrayList<>(); private int mDismissAnimationRefCount = 0; private float touchedX; private float touchedY; private boolean isFgSwiping; private int mSwipingSlop; private VelocityTracker mVelocityTracker; private int touchedPosition; private View touchedView; private boolean mPaused; private boolean bgVisible, fgPartialViewClicked; private int bgVisiblePosition; private View bgVisibleView; private boolean isRViewScrolling; private int heightOutsideRView, screenHeight; private boolean mLongClickPerformed; // Foreground view (to be swiped), Background view (to show) private View fgView; private View bgView; //view ID private int fgViewID; private int bgViewID, bgViewIDLeft; private ArrayList<Integer> fadeViews; private OnRowClickListener mRowClickListener; private OnRowLongClickListener mRowLongClickListener; private OnSwipeOptionsClickListener mBgClickListener, mBgClickListenerLeft; // user choices private boolean clickable = false; private boolean longClickable = false; private boolean swipeable = false, swipeableLeftOptions = false; private int LONG_CLICK_DELAY = 800; private boolean longClickVibrate; Runnable mLongPressed = new Runnable() { public void run() { if (!longClickable) return; mLongClickPerformed = true; if (!bgVisible && touchedPosition >= 0 && !unClickableRows.contains(touchedPosition) && !isRViewScrolling) { if (longClickVibrate) { // Vibrator vibe = (Vibrator) act.getSystemService(Context.VIBRATOR_SERVICE); // vibe.vibrate(100); // do we really need to add vibrate service } mRowLongClickListener.onRowLongClicked(touchedPosition); } } }; public RecyclerTouchListener(Activity a, RecyclerView recyclerView) { this.act = a; ViewConfiguration vc = ViewConfiguration.get(recyclerView.getContext()); touchSlop = vc.getScaledTouchSlop(); minFlingVel = vc.getScaledMinimumFlingVelocity() * 16; maxFlingVel = vc.getScaledMaximumFlingVelocity(); rView = recyclerView; bgVisible = false; bgVisiblePosition = -1; bgVisibleView = null; fgPartialViewClicked = false; unSwipeableRows = new ArrayList<>(); unClickableRows = new ArrayList<>(); ignoredViewTypes = new HashSet<>(); independentViews = new ArrayList<>(); optionViews = new ArrayList<>(); fadeViews = new ArrayList<>(); isRViewScrolling = false; // mSwipeListener = listener; rView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { /** * This will ensure that this RecyclerTouchListener is paused during recycler view scrolling. * If a scroll listener is already assigned, the caller should still pass scroll changes through * to this listener. */ setEnabled(newState != RecyclerView.SCROLL_STATE_DRAGGING); /** * This is used so that clicking a row cannot be done while scrolling */ isRViewScrolling = newState != RecyclerView.SCROLL_STATE_IDLE; } @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { } }); } /** * Enables or disables (pauses or resumes) watching for swipe-to-dismiss gestures. * * @param enabled Whether or not to watch for gestures. */ public void setEnabled(boolean enabled) { mPaused = !enabled; } @Override public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent motionEvent) { return handleTouchEvent(motionEvent); } @Override public void onTouchEvent(RecyclerView rv, MotionEvent motionEvent) { handleTouchEvent(motionEvent); } /*////////////// Clickable ////////////////////*/ public RecyclerTouchListener setClickable(OnRowClickListener listener) { this.clickable = true; this.mRowClickListener = listener; return this; } public RecyclerTouchListener setClickable(boolean clickable) { this.clickable = clickable; return this; } public RecyclerTouchListener setLongClickable(boolean vibrate, OnRowLongClickListener listener) { this.longClickable = true; this.mRowLongClickListener = listener; this.longClickVibrate = vibrate; return this; } public RecyclerTouchListener setLongClickable(boolean longClickable) { this.longClickable = longClickable; return this; } public RecyclerTouchListener setIndependentViews(Integer... viewIds) { this.independentViews = new ArrayList<>(Arrays.asList(viewIds)); return this; } public RecyclerTouchListener setUnClickableRows(Integer... rows) { this.unClickableRows = new ArrayList<>(Arrays.asList(rows)); return this; } public RecyclerTouchListener setIgnoredViewTypes(Integer... viewTypes) { ignoredViewTypes.clear(); ignoredViewTypes.addAll(Arrays.asList(viewTypes)); return this; } //////////////// Swipeable //////////////////// public RecyclerTouchListener setSwipeable(int foregroundID, int backgroundID, OnSwipeOptionsClickListener listener) { this.swipeable = true; if (fgViewID != 0 && foregroundID != fgViewID) throw new IllegalArgumentException("foregroundID does not match previously set ID"); fgViewID = foregroundID; bgViewID = backgroundID; this.mBgClickListener = listener; if (act instanceof RecyclerTouchListenerHelper) ((RecyclerTouchListenerHelper) act).setOnActivityTouchListener(this); DisplayMetrics displaymetrics = new DisplayMetrics(); act.getWindowManager().getDefaultDisplay().getMetrics(displaymetrics); screenHeight = displaymetrics.heightPixels; return this; } public RecyclerTouchListener setSwipeable(boolean value) { this.swipeable = value; if (!value) invalidateSwipeOptions(); return this; } public RecyclerTouchListener setSwipeOptionViews(Integer... viewIds) { this.optionViews = new ArrayList<>(Arrays.asList(viewIds)); return this; } public RecyclerTouchListener setUnSwipeableRows(Integer... rows) { this.unSwipeableRows = new ArrayList<>(Arrays.asList(rows)); return this; } //////////////// Fade Views //////////////////// // Set views which are faded out as fg is opened public RecyclerTouchListener setViewsToFade(Integer... viewIds) { this.fadeViews = new ArrayList<>(Arrays.asList(viewIds)); return this; } // the entire foreground is faded out as it is opened public RecyclerTouchListener setFgFade() { if (!fadeViews.contains(fgViewID)) this.fadeViews.add(fgViewID); return this; } //-------------- Checkers for preventing ---------------// private boolean isIndependentViewClicked(MotionEvent motionEvent) { for (int i = 0; i < independentViews.size(); i++) { if (touchedView != null) { Rect rect = new Rect(); int x = (int) motionEvent.getRawX(); int y = (int) motionEvent.getRawY(); touchedView.findViewById(independentViews.get(i)).getGlobalVisibleRect(rect); if (rect.contains(x, y)) { return false; } } } return true; } private int getOptionViewID(MotionEvent motionEvent) { for (int i = 0; i < optionViews.size(); i++) { if (touchedView != null) { Rect rect = new Rect(); int x = (int) motionEvent.getRawX(); int y = (int) motionEvent.getRawY(); touchedView.findViewById(optionViews.get(i)).getGlobalVisibleRect(rect); if (rect.contains(x, y)) { return optionViews.get(i); } } } return -1; } private int getIndependentViewID(MotionEvent motionEvent) { for (int i = 0; i < independentViews.size(); i++) { if (touchedView != null) { Rect rect = new Rect(); int x = (int) motionEvent.getRawX(); int y = (int) motionEvent.getRawY(); touchedView.findViewById(independentViews.get(i)).getGlobalVisibleRect(rect); if (rect.contains(x, y)) { return independentViews.get(i); } } } return -1; } @Override public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { } public void invalidateSwipeOptions() { bgWidth = 1; } public void openSwipeOptions(int position) { if (!swipeable || rView.getChildAt(position) == null || unSwipeableRows.contains(position) || shouldIgnoreAction(position)) return; if (bgWidth < 2) { if (act.findViewById(bgViewID) != null) bgWidth = act.findViewById(bgViewID).getWidth(); heightOutsideRView = screenHeight - rView.getHeight(); } touchedPosition = position; touchedView = rView.getChildAt(position); fgView = touchedView.findViewById(fgViewID); bgView = touchedView.findViewById(bgViewID); bgView.setMinimumHeight(fgView.getHeight()); closeVisibleBG(null); animateFG(touchedView, Animation.OPEN, ANIMATION_STANDARD); bgVisible = true; bgVisibleView = fgView; bgVisiblePosition = touchedPosition; } @Deprecated public void closeVisibleBG() { if (bgVisibleView == null) { Log.e(TAG, "No rows found for which background options are visible"); return; } bgVisibleView.animate() .translationX(0) .setDuration(ANIMATION_CLOSE) .setListener(null); animateFadeViews(bgVisibleView, 1f, ANIMATION_CLOSE); bgVisible = false; bgVisibleView = null; bgVisiblePosition = -1; } public void closeVisibleBG(final OnSwipeListener mSwipeCloseListener) { if (bgVisibleView == null) { Log.e(TAG, "No rows found for which background options are visible"); return; } final ObjectAnimator translateAnimator = ObjectAnimator.ofFloat(bgVisibleView, View.TRANSLATION_X, 0f); translateAnimator.setDuration(ANIMATION_CLOSE); translateAnimator.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { if (mSwipeCloseListener != null) mSwipeCloseListener.onSwipeOptionsClosed(); translateAnimator.removeAllListeners(); } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }); translateAnimator.start(); animateFadeViews(bgVisibleView, 1f, ANIMATION_CLOSE); bgVisible = false; bgVisibleView = null; bgVisiblePosition = -1; } private void animateFadeViews(View downView, float alpha, long duration) { if (fadeViews != null) { for (final int viewID : fadeViews) { downView.findViewById(viewID).animate() .alpha(alpha) .setDuration(duration); } } } private void animateFG(View downView, Animation animateType, long duration) { if (animateType == Animation.OPEN) { ObjectAnimator translateAnimator = ObjectAnimator.ofFloat( fgView, View.TRANSLATION_X, -bgWidth); translateAnimator.setDuration(duration); translateAnimator.setInterpolator(new DecelerateInterpolator(1.5f)); translateAnimator.start(); animateFadeViews(downView, 0f, duration); } else if (animateType == Animation.CLOSE) { ObjectAnimator translateAnimator = ObjectAnimator.ofFloat( fgView, View.TRANSLATION_X, 0f); translateAnimator.setDuration(duration); translateAnimator.setInterpolator(new DecelerateInterpolator(1.5f)); translateAnimator.start(); animateFadeViews(downView, 1f, duration); } } private void animateFG(View downView, final Animation animateType, long duration, final OnSwipeListener mSwipeCloseListener) { final ObjectAnimator translateAnimator; if (animateType == Animation.OPEN) { translateAnimator = ObjectAnimator.ofFloat(fgView, View.TRANSLATION_X, -bgWidth); translateAnimator.setDuration(duration); translateAnimator.setInterpolator(new DecelerateInterpolator(1.5f)); translateAnimator.start(); animateFadeViews(downView, 0f, duration); } else /*if (animateType == Animation.CLOSE)*/ { translateAnimator = ObjectAnimator.ofFloat(fgView, View.TRANSLATION_X, 0f); translateAnimator.setDuration(duration); translateAnimator.setInterpolator(new DecelerateInterpolator(1.5f)); translateAnimator.start(); animateFadeViews(downView, 1f, duration); } translateAnimator.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { if (mSwipeCloseListener != null) { if (animateType == Animation.OPEN) mSwipeCloseListener.onSwipeOptionsOpened(); else if (animateType == Animation.CLOSE) mSwipeCloseListener.onSwipeOptionsClosed(); } translateAnimator.removeAllListeners(); } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }); } private boolean handleTouchEvent(MotionEvent motionEvent) { if (swipeable && bgWidth < 2) { // bgWidth = rView.getWidth(); if (act.findViewById(bgViewID) != null) bgWidth = act.findViewById(bgViewID).getWidth(); heightOutsideRView = screenHeight - rView.getHeight(); } switch (motionEvent.getActionMasked()) { // When finger touches screen case MotionEvent.ACTION_DOWN: { if (mPaused) { break; } // Find the child view that was touched (perform a hit test) Rect rect = new Rect(); int childCount = rView.getChildCount(); int[] listViewCoords = new int[2]; rView.getLocationOnScreen(listViewCoords); // x and y values respective to the recycler view int x = (int) motionEvent.getRawX() - listViewCoords[0]; int y = (int) motionEvent.getRawY() - listViewCoords[1]; View child; /* * check for every child (row) in the recycler view whether the touched co-ordinates belong to that * respective child and if it does, register that child as the touched view (touchedView) */ for (int i = 0; i < childCount; i++) { child = rView.getChildAt(i); child.getHitRect(rect); if (rect.contains(x, y)) { touchedView = child; break; } } if (touchedView != null) { touchedX = motionEvent.getRawX(); touchedY = motionEvent.getRawY(); touchedPosition = rView.getChildAdapterPosition(touchedView); if (shouldIgnoreAction(touchedPosition)) { touchedPosition = ListView.INVALID_POSITION; return false; // <-- guard here allows for ignoring events, allowing more than one view type and preventing NPE } if (longClickable) { mLongClickPerformed = false; handler.postDelayed(mLongPressed, LONG_CLICK_DELAY); } if (swipeable) { mVelocityTracker = VelocityTracker.obtain(); mVelocityTracker.addMovement(motionEvent); fgView = touchedView.findViewById(fgViewID); bgView = touchedView.findViewById(bgViewID); // bgView.getLayoutParams().height = fgView.getHeight(); bgView.setMinimumHeight(fgView.getHeight()); /* * bgVisible is true when the options menu is opened * This block is to register fgPartialViewClicked status - Partial view is the view that is still * shown on the screen if the options width is < device width */ if (bgVisible && fgView != null) { handler.removeCallbacks(mLongPressed); x = (int) motionEvent.getRawX(); y = (int) motionEvent.getRawY(); fgView.getGlobalVisibleRect(rect); fgPartialViewClicked = rect.contains(x, y); } else { fgPartialViewClicked = false; } } } /* * If options menu is shown and the touched position is not the same as the row for which the * options is displayed - close the options menu for the row which is displaying it * (bgVisibleView and bgVisiblePosition is used for this purpose which registers which view and * which position has it's options menu opened) */ x = (int) motionEvent.getRawX(); y = (int) motionEvent.getRawY(); rView.getHitRect(rect); if (swipeable && bgVisible && touchedPosition != bgVisiblePosition) { handler.removeCallbacks(mLongPressed); closeVisibleBG(null); } break; } case MotionEvent.ACTION_CANCEL: { handler.removeCallbacks(mLongPressed); if (mLongClickPerformed) break; if (mVelocityTracker == null) { break; } if (swipeable) { if (touchedView != null && isFgSwiping) { // cancel animateFG(touchedView, Animation.CLOSE, ANIMATION_STANDARD); } mVelocityTracker.recycle(); mVelocityTracker = null; isFgSwiping = false; bgView = null; } touchedX = 0; touchedY = 0; touchedView = null; touchedPosition = ListView.INVALID_POSITION; break; } // When finger is lifted off the screen (after clicking, flinging, swiping, etc..) case MotionEvent.ACTION_UP: { handler.removeCallbacks(mLongPressed); if (mLongClickPerformed) break; if (mVelocityTracker == null && swipeable) { break; } if (touchedPosition < 0) break; // swipedLeft and swipedRight are true if the user swipes in the respective direction (no conditions) boolean swipedLeft = false; boolean swipedRight = false; /* * swipedLeftProper and swipedRightProper are true if user swipes in the respective direction * and if certain conditions are satisfied (given some few lines below) */ boolean swipedLeftProper = false; boolean swipedRightProper = false; float mFinalDelta = motionEvent.getRawX() - touchedX; // if swiped in a direction, make that respective variable true if (isFgSwiping) { swipedLeft = mFinalDelta < 0; swipedRight = mFinalDelta > 0; } /* * If the user has swiped more than half of the width of the options menu, or if the * velocity of swiping is between min and max fling values * "proper" variable are set true */ if (Math.abs(mFinalDelta) > bgWidth / 2 && isFgSwiping) { swipedLeftProper = mFinalDelta < 0; swipedRightProper = mFinalDelta > 0; } else if (swipeable) { mVelocityTracker.addMovement(motionEvent); mVelocityTracker.computeCurrentVelocity(1000); float velocityX = mVelocityTracker.getXVelocity(); float absVelocityX = Math.abs(velocityX); float absVelocityY = Math.abs(mVelocityTracker.getYVelocity()); if (minFlingVel <= absVelocityX && absVelocityX <= maxFlingVel && absVelocityY < absVelocityX && isFgSwiping) { // dismiss only if flinging in the same direction as dragging swipedLeftProper = (velocityX < 0) == (mFinalDelta < 0); swipedRightProper = (velocityX > 0) == (mFinalDelta > 0); } } ///////// Manipulation of view based on the 4 variables mentioned above /////////// // if swiped left properly and options menu isn't already visible, animate the foreground to the left if (swipeable && !swipedRight && swipedLeftProper && touchedPosition != RecyclerView.NO_POSITION && !unSwipeableRows.contains(touchedPosition) && !bgVisible) { final View downView = touchedView; // touchedView gets null'd before animation ends final int downPosition = touchedPosition; ++mDismissAnimationRefCount; //TODO - speed animateFG(touchedView, Animation.OPEN, ANIMATION_STANDARD); bgVisible = true; bgVisibleView = fgView; bgVisiblePosition = downPosition; } // else if swiped right properly when options menu is visible, close the menu and bring the foreground // to it's original position else if (swipeable && !swipedLeft && swipedRightProper && touchedPosition != RecyclerView.NO_POSITION && !unSwipeableRows.contains(touchedPosition) && bgVisible) { // dismiss final View downView = touchedView; // touchedView gets null'd before animation ends final int downPosition = touchedPosition; ++mDismissAnimationRefCount; //TODO - speed animateFG(touchedView, Animation.CLOSE, ANIMATION_STANDARD); bgVisible = false; bgVisibleView = null; bgVisiblePosition = -1; } // else if swiped left incorrectly (not satisfying the above conditions), animate the foreground back to // it's original position (spring effect) else if (swipeable && swipedLeft && !bgVisible) { // cancel final View tempBgView = bgView; animateFG(touchedView, Animation.CLOSE, ANIMATION_STANDARD, new OnSwipeListener() { @Override public void onSwipeOptionsClosed() { if (tempBgView != null) tempBgView.setVisibility(View.VISIBLE); } @Override public void onSwipeOptionsOpened() { } }); bgVisible = false; bgVisibleView = null; bgVisiblePosition = -1; } // else if swiped right incorrectly (not satisfying the above conditions), animate the foreground to // it's open position (spring effect) else if (swipeable && swipedRight && bgVisible) { // cancel animateFG(touchedView, Animation.OPEN, ANIMATION_STANDARD); bgVisible = true; bgVisibleView = fgView; bgVisiblePosition = touchedPosition; } // This case deals with an error where the user can swipe left, then right // really fast and the fg is stuck open - so in that case we close the fg else if (swipeable && swipedRight && !bgVisible) { // cancel animateFG(touchedView, Animation.CLOSE, ANIMATION_STANDARD); bgVisible = false; bgVisibleView = null; bgVisiblePosition = -1; } // This case deals with an error where the user can swipe right, then left // really fast and the fg is stuck open - so in that case we open the fg else if (swipeable && swipedLeft && bgVisible) { // cancel animateFG(touchedView, Animation.OPEN, ANIMATION_STANDARD); bgVisible = true; bgVisibleView = fgView; bgVisiblePosition = touchedPosition; } // if clicked else if (!swipedRight && !swipedLeft) { // if partial foreground view is clicked (see ACTION_DOWN) bring foreground back to original position // bgVisible is true automatically since it's already checked in ACTION_DOWN block if (swipeable && fgPartialViewClicked) { animateFG(touchedView, Animation.CLOSE, ANIMATION_STANDARD); bgVisible = false; bgVisibleView = null; bgVisiblePosition = -1; } // On Click listener for rows else if (clickable && !bgVisible && touchedPosition >= 0 && !unClickableRows.contains(touchedPosition) && isIndependentViewClicked(motionEvent) && !isRViewScrolling) { mRowClickListener.onRowClicked(touchedPosition); } // On Click listener for independent views inside the rows else if (clickable && !bgVisible && touchedPosition >= 0 && !unClickableRows.contains(touchedPosition) && !isIndependentViewClicked(motionEvent) && !isRViewScrolling) { final int independentViewID = getIndependentViewID(motionEvent); if (independentViewID >= 0) mRowClickListener.onIndependentViewClicked(independentViewID, touchedPosition); } // On Click listener for background options else if (swipeable && bgVisible && !fgPartialViewClicked) { final int optionID = getOptionViewID(motionEvent); if (optionID >= 0 && touchedPosition >= 0) { final int downPosition = touchedPosition; closeVisibleBG(new OnSwipeListener() { @Override public void onSwipeOptionsClosed() { mBgClickListener.onSwipeOptionClicked(optionID, downPosition); } @Override public void onSwipeOptionsOpened() { } }); } } } } // if clicked and not swiped if (swipeable) { mVelocityTracker.recycle(); mVelocityTracker = null; } touchedX = 0; touchedY = 0; touchedView = null; touchedPosition = ListView.INVALID_POSITION; isFgSwiping = false; bgView = null; break; // when finger is moving across the screen (and not yet lifted) case MotionEvent.ACTION_MOVE: { if (mLongClickPerformed) break; if (mVelocityTracker == null || mPaused || !swipeable) { break; } mVelocityTracker.addMovement(motionEvent); float deltaX = motionEvent.getRawX() - touchedX; float deltaY = motionEvent.getRawY() - touchedY; /* * isFgSwiping variable which is set to true here is used to alter the swipedLeft, swipedRightProper * variables in "ACTION_UP" block by checking if user is actually swiping at present or not */ if (!isFgSwiping && Math.abs(deltaX) > touchSlop && Math.abs(deltaY) < Math.abs(deltaX) / 2) { handler.removeCallbacks(mLongPressed); isFgSwiping = true; mSwipingSlop = (deltaX > 0 ? touchSlop : -touchSlop); } // This block moves the foreground along with the finger when swiping if (swipeable && isFgSwiping && !unSwipeableRows.contains(touchedPosition)) { if (bgView == null) { bgView = touchedView.findViewById(bgViewID); bgView.setVisibility(View.VISIBLE); } // if fg is being swiped left if (deltaX < touchSlop && !bgVisible) { float translateAmount = deltaX - mSwipingSlop; // if ((Math.abs(translateAmount) > bgWidth ? -bgWidth : translateAmount) <= 0) { // swipe fg till width of bg. If swiped further, nothing happens (stalls at width of bg) fgView.setTranslationX(Math.abs(translateAmount) > bgWidth ? -bgWidth : translateAmount); if (fgView.getTranslationX() > 0) fgView.setTranslationX(0); // } // fades all the fadeViews gradually to 0 alpha as dragged if (fadeViews != null) { for (int viewID : fadeViews) { touchedView.findViewById(viewID).setAlpha(1 - (Math.abs(translateAmount) / bgWidth)); } } } // if fg is being swiped right else if (deltaX > 0 && bgVisible) { // for closing rightOptions if (bgVisible) { float translateAmount = (deltaX - mSwipingSlop) - bgWidth; // swipe fg till it reaches original position. If swiped further, nothing happens (stalls at 0) fgView.setTranslationX(translateAmount > 0 ? 0 : translateAmount); // fades all the fadeViews gradually to 0 alpha as dragged if (fadeViews != null) { for (int viewID : fadeViews) { touchedView.findViewById(viewID).setAlpha(1 - (Math.abs(translateAmount) / bgWidth)); } } } // for opening leftOptions else { float translateAmount = (deltaX - mSwipingSlop) - bgWidth; // swipe fg till it reaches original position. If swiped further, nothing happens (stalls at 0) fgView.setTranslationX(translateAmount > 0 ? 0 : translateAmount); // fades all the fadeViews gradually to 0 alpha as dragged if (fadeViews != null) { for (int viewID : fadeViews) { touchedView.findViewById(viewID).setAlpha(1 - (Math.abs(translateAmount) / bgWidth)); } } } } return true; } // moves the fg slightly to give the illusion of an "unswipeable" row else if (swipeable && isFgSwiping && unSwipeableRows.contains(touchedPosition)) { if (deltaX < touchSlop && !bgVisible) { float translateAmount = deltaX - mSwipingSlop; if (bgView == null) bgView = touchedView.findViewById(bgViewID); if (bgView != null) bgView.setVisibility(View.GONE); // swipe fg till width of bg. If swiped further, nothing happens (stalls at width of bg) fgView.setTranslationX(translateAmount / 5); if (fgView.getTranslationX() > 0) fgView.setTranslationX(0); // fades all the fadeViews gradually to 0 alpha as dragged // if (fadeViews != null) { // for (int viewID : fadeViews) { // touchedView.findViewById(viewID).setAlpha(1 - (Math.abs(translateAmount) / bgWidth)); // } // } } return true; } break; } } return false; } /** * Gets coordinates from Activity and closes any * swiped rows if touch happens outside the recycler view */ @Override public void getTouchCoordinates(MotionEvent ev) { int y = (int) ev.getRawY(); if (swipeable && bgVisible && ev.getActionMasked() == MotionEvent.ACTION_DOWN && y < heightOutsideRView) closeVisibleBG(null); } private boolean shouldIgnoreAction(int touchedPosition) { return rView == null || ignoredViewTypes.contains(rView.getAdapter().getItemViewType(touchedPosition)); } private enum Animation { OPEN, CLOSE } /////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////// Interfaces ///////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////// public interface OnRowClickListener { void onRowClicked(int position); void onIndependentViewClicked(int independentViewID, int position); } public interface OnRowLongClickListener { void onRowLongClicked(int position); } public interface OnSwipeOptionsClickListener { void onSwipeOptionClicked(int viewID, int position); } public interface RecyclerTouchListenerHelper { void setOnActivityTouchListener(OnActivityTouchListener listener); } public interface OnSwipeListener { void onSwipeOptionsClosed(); void onSwipeOptionsOpened(); } }
Code language: PHP (php)

Also, we need to create another interface OnActivityTouchListener.java. To gets coordinates from Activity and close any swiped rows if touch happens outside the recyclerview.

public interface OnActivityTouchListener { void getTouchCoordinates(MotionEvent ev); }
Code language: PHP (php)

5. Configure Swipe Menu in Recyclerview

Already, we have created Recyclerview and the swiping menu with swipe listeners. Now, it’s time to configure the both recyclerview and swiping menu.

First, put some data into the recyclerview.

recyclerView = findViewById(R.id.recyclerview); recyclerviewAdapter = new RecyclerviewAdapter(this); final List<Task> taskList = new ArrayList<>(); Task task = new Task("Buy Dress","Buy Dress at Shoppershop for coming functions"); taskList.add(task); task = new Task("Go For Walk","Wake up 6AM go for walking"); taskList.add(task); task = new Task("Office Work","Complete the office works on Time"); taskList.add(task); task = new Task("watch Repair","Give watch to service center"); taskList.add(task); task = new Task("Recharge Mobile","Recharge for 10$ to my **** number"); taskList.add(task); task = new Task("Read book","Read android book completely"); taskList.add(task); recyclerviewAdapter.setTaskList(taskList); recyclerView.setAdapter(recyclerviewAdapter);
Code language: PHP (php)

Then, set the created TouchListener to the recyclerview.

touchListener = new RecyclerTouchListener(this,recyclerView); touchListener .setClickable(new RecyclerTouchListener.OnRowClickListener() { @Override public void onRowClicked(int position) { Toast.makeText(getApplicationContext(),taskList.get(position).getName(), Toast.LENGTH_SHORT).show(); } @Override public void onIndependentViewClicked(int independentViewID, int position) { } }) .setSwipeOptionViews(R.id.delete_task,R.id.edit_task) .setSwipeable(R.id.rowFG, R.id.rowBG, new RecyclerTouchListener.OnSwipeOptionsClickListener() { @Override public void onSwipeOptionClicked(int viewID, int position) { switch (viewID){ case R.id.delete_task: taskList.remove(position); recyclerviewAdapter.setTaskList(taskList); break; case R.id.edit_task: Toast.makeText(getApplicationContext(),"Edit Not Available",Toast.LENGTH_SHORT).show(); break; } } }); recyclerView.addOnItemTouchListener(touchListener);
Code language: JavaScript (javascript)

In the RecyclerTouchListener,

setClickable — used to set the click listener for the recyclerview items. in our case, the main items that are displaying the task.

setSwipeOptionViews — in this method, we need to set the view id of the swipe view. In our case, Edit and delete the view id.

setSwipeable — In this method, we need to tell the listener to listen for the swipeable items. in our example, we have rowFG, rowBG. Also, we need to pass the implementation of the OnSwipeOptionsClickListener to handle the swipe menu item clicks.

That’s all on the Recyclerview swipe menu in android.

Download this example in GITHUB.

Conclusion

Thanks for reading.

Please try this and let me know your feedback in the comments.


Posted

in

by

Tags:

Comments

4 responses to “How to create recyclerview with Swipe Menu in android ?”

  1. James

    Thank you so much for this. I’ve spent so long trying to get swipe to work, and this worked flawlessly 🙂

  2. Tunde

    Awesome tutorial! Works perfectly

  3. Tai

    How to config swipe left option, thanks.

Leave a Reply

Your email address will not be published. Required fields are marked *