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().

@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);
}

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.