Android RecyclerView – Drag and Drop and Swipe to Dismiss

In the last post I explained how you can implement an expandable recyclerview. In this post we will see how to implement swipe-to-remove and drag-and-drop gestures.

RecyclerView provides a built-in mechanism to enable drag and drop and swipe to dismiss gestures. This is a great advantage for Recyclerview compared to ListView where we had to write all the boilerplate for animating items for dragging and swiping. So if you are still using ListView this is a great feature for you to switch to RecyclerView.

This can be accomplished using the ItemTouchHelper class provided with RecyclerView. This class does all the heavy lifting needed for handling swiping and dragging and animating the view accordingly.

You can specify in which directions and in which ViewHolders the gestures should work. Also you need to be notified when a swipe or drag and drop gesture is completed. This can be addressed using the ItemTouchHelper.Callback class.

To quickly setup these gestures, we can subclass the Callback class and override three methods: getMovementFlags(), onMove() and onSwiped().

  • In getMovementFlags() you have to return a int value. This value denotes a composite flag that defines the movement directions for each movement states namely IDLE, DRAG and SWIPE. This method takes two parameters: a RecyclerView instance and the ViewHolder of the view. You can return the flag using the method makeFlag() or the convenience method makeMovementFlags(). In the below code, drag supports both up and down directions, and swipe supports left and right directions.
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
    int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
    int swipeFlags = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
    return makeMovementFlags(dragFlags, swipeFlags);
}
  • onMove() gets called when a view is dragged from its position to other positions. You have to return true if the item has been moved from its old position to a new position. Here you can notify the adapter about the position change. By default an item can be moved only after long pressing it.
  • Similarly, onSwipe() gets called when a view is completely swiped out. Here you can notify the adapter about the removal.

To correctly handle drag-and-drop and swipe, we can create a interface.

public interface ActionCompletionContract {
    void onViewMoved(int oldPosition, int newPosition);
    void onViewSwiped(int position);
}

And let’s make our Adapter implement this. In the onViewMoved() callback, we will remove the data at the oldPosition and add it to the newPosition, and notify the adapter:

@Override
public void onViewMoved(int oldPosition, int newPosition) {
    User targetUser = usersList.get(oldPosition);
    User user = new User(targetUser);
    usersList.remove(oldPosition);
    usersList.add(newPosition, user);
    notifyItemMoved(oldPosition, newPosition);
}

For swipe to dismiss action, we call onViewSwiped() interface callback and remove the item:

@Override
public void onViewSwiped(int position) {
    usersList.remove(position);
    notifyItemRemoved(position);
}

Now to call these appropriate callbacks from the ItemTouchHelper.Callback class, we will pass the adapter to the class:

SwipeAndDragHelper swipeAndDragHelper = new SwipeAndDragHelper(adapter);
ItemTouchHelper touchHelper = new ItemTouchHelper(swipeAndDragHelper);

Here I have created a subclass of the ItemTouchHelper.Callback called SwipeAndDragHelper.

And finally to integrate this ItemTouchHelper with our RecyclerView, we call attachToRecyclerView() method:

touchHelper.attachToRecyclerView(userRecyclerView);

That’s it. We have implemented the drag-and-drop and swipe-to-dismiss gestures. This is how it looks:

recyclerview-itemtouchhelper

 

Now what if we want to move the items only by touching a handle something like below:

components-listcontrols-reorder
Source: Material Design Guidelines

For that, the ItemTouchHelper provides startDrag() and startSwipe() methods to manually start drag and swipe actions respectively. Let’s implement this.

First to manually drag, we must disable the default dragging. Since default dragging is started when a view is long pressed, we must disable it. This can be done by returning false in isLongPressEnabled() of the Callback class.

Then pass the instance of ItemTouchHelper to the adapter. Then implement onTouchListener for the reorder handle ImageView. Inside onTouch call the startDrag method passing the ViewHolder as parameter like below:

In onBindViewHolder:

((UserViewHolder) holder).reorderView.setOnTouchListener(new View.OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
            touchHelper.startDrag(holder);
        }
        return false;
    }
});

Result:

recyclerview-item-touch-helper-reorder

 

Next we will add some fade effect to the swipe action. Right now when the view gets swiped there is no effect except the view gets transitioned in x direction.

The Callback class provides onChildDraw() method to draw anything over the area of the child view being swiped or dragged. It provides a canvas, viewholder, x and y displacement caused by the gesture, and action state as parameters among others. So we will check the action state and if it is equal to ACTION_STATE_SWIPE we will reduce the alpha of the view as it moves away from its original position.

@Override
public void onChildDraw(Canvas c,
                        RecyclerView recyclerView,
                        RecyclerView.ViewHolder viewHolder,
                        float dX,
                        float dY,
                        int actionState,
                        boolean isCurrentlyActive) {
    if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
        float alpha = 1 - (Math.abs(dX) / recyclerView.getWidth());
        viewHolder.itemView.setAlpha(alpha);
    }
    super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}

Now you will get a nice fade as below when swiping:

recyclerview-swipe-dismiss-item-fade-out

Checkout the full source code here.

If you have feedback, comment below.

Advertisements

5 thoughts on “Android RecyclerView – Drag and Drop and Swipe to Dismiss

  1. Thanks for your post! Wonderful code. If only it had methods to disallow dropping into/under certain sections of the list…

    I see the list’s re-arrangement can be prevented by checking if newPosition in onViewMoved() is within non-droppable region – but how to trigger something like fly-back animation instead of drop?

    Like

    1. You can make onViewMoved() return boolean. So if you don’t want to drop in a section, return false. Then in onMove() of the Callback class if onViewMoved() returns false, return false for onMove(). Also the view goes back to the previous position with a default fly animation.

      Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s