RecyclerView ItemTouchHelper swipe remove animation

RokG picture RokG · Jul 15, 2015 · Viewed 14.7k times · Source

I've got a remove on swipe, that draws a background (much like the Inbox app), implemented by an ItemTouchHelper - by overriding the onChilDraw method and drawing a rectangle on the provided canvas:

    ItemTouchHelper mIth = new ItemTouchHelper(
        new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.RIGHT) {

            public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
                remove(viewHolder.getAdapterPosition());
            }

            public boolean onMove(RecyclerView recyclerview, RecyclerView.ViewHolder v, RecyclerView.ViewHolder target) {
                return false;
            }

            @Override
            public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {

                View itemView = viewHolder.itemView;

                Drawable d = ContextCompat.getDrawable(context, R.drawable.bg_swipe_item_right);
                d.setBounds(itemView.getLeft(), itemView.getTop(), (int) dX, itemView.getBottom());
                d.draw(c);

                super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
            }
        });

The remove method called above is in the Adapter:

    public void remove(int position) {
       items.remove(position);
       notifyItemRemoved(position);
    }

The background draws out nicely, but when notifyItemRemoved is called (according to Mr. Debugger), the RecyclerView first deletes my pretty green background, and then pushes the two adjacent items together.

enter image description here enter image description here

I would like it to keep the background there while it does that (just like the Inbox app). Is there any way to do that?

Answer

Nemanja Kovacevic picture Nemanja Kovacevic · Jan 9, 2016

I had the same issue and I didn't wanna introduce a new lib just to fix it. The RecyclerView is not deleting your pretty green background, it's just redrawing itself, and your ItemTouchHelper is not drawing anymore. Actually it's drawing but the dX is 0 and is drawing from the itemView.getLeft() (which is 0) to dX (which is 0) so you see nothing. And it's drawing too much, but I'll come back to it later.

Anyway back to the background while rows animate: I couldn't do it within ItemTouchHelper and onChildDraw. In the end I had to add another item decorator to do it. It goes along these lines:

public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
    if (parent.getItemAnimator().isRunning()) {
        // find first child with translationY > 0
        // draw from it's top to translationY whatever you want

        int top = 0;
        int bottom = 0;

        int childCount = parent.getLayoutManager().getChildCount();
        for (int i = 0; i < childCount; i++) {
            View child = parent.getLayoutManager().getChildAt(i);
            if (child.getTranslationY() != 0) {
                top = child.getTop();
                bottom = top + (int) child.getTranslationY();                    
                break;
            }
        }

        // draw whatever you want

        super.onDraw(c, parent, state);
    }
}

This code takes into account only rows animating up, but you should also consider rows coming down. That happens if you swipe delete the last row, rows above are gonna animate down to that space.

When I said your ItemTouchHelper is drawing too much what I meant was: Looks like ItemTouchHelper keeps ViewHolders of removed rows in case they need to be restored. It's also calling onChildDraw for those VHs in addition to the VH being swiped. Not sure about memory management implications of this behavior but I needed an additional check in the start of onChildDraw to avoid drawing for "fantom" rows.

if (viewHolder.getAdapterPosition() == -1) {
    return;
}

In your case it's drawing from left=0 to right=0 so you don't see anything but the overhead is there. If you start seeing previously swiped away rows drawing their backgrounds that is the reason.

EDIT: I had a go at this, see this blog post and this github repo.