Android Java Swipe Collapse Animation


The introduction

tl;dr https://github.com/david-szabo97/Android-Swipe-Collapse-Animation

After spending hours and hours and hours and hours… and hours… You don’t want to know how many hours. But I spent all of my free time in 3 consecutive days trying to find and/or replicate on my own the Android Google Inbox’s “swipe to dismiss” animation. If you don’t know what I am talking about let me show you below:

You must be thinking: “What’s so difficult in this? I could do it in a few minutes.” I had that in mind too. Android is kind of a black box for me so far. I have just started developing apps for learning purposes but there are always some “Hold my beer, I will code that in 5 minutes with one finger…” situations. Don’t get me wrong, Google is superb at teaching stuff (Hello Udacity). But if you can’t find a little piece you want to add to your app on the internet, you are screwed. It takes a lot of time to dive into the source code and get to know all the little moving pieces just to implement something simple. Thank God we have Material Design at least.

There are a couple of implementations on the internet but none of them worked the way I wanted it to work. Let’s see the first one I found when I was looking for the best solution.

Candidate #1

This one is from Marcin Kitowicz’s blog post

It’s gorgeous. We can swipe it to the left and we get a nice red background with the awesome trash icon over it. The problem is, when the item is swiped, the whole background-icon thing disappears! We need that smooth collapsing animation that we have in Inbox.

Candidate #2

Our second candidate is from Nemanja Kovacevic’s blog post Let’s see how that one looks and I will point out the problems with it.

This one works as expected. We get that smooth collapse animation. But we still have a couple of issues there. First of all, the icon disappears immediately after swiping the item and this solution only supports one kind of draw. So that means if we swipe left and we have a green background for that, then we are going to get a red collapsing background animation. The reason for that is Nemanja is using an ItemDecorator and that doesn’t have a clue about what happened. It just fills the empty space with something that you coded in it.

I don’t say that any of these solutions are bad, they just didn’t fit my project. Nemanja’s solution is a performant one though. So if you only have one swipe to dismiss collapsing animation then that’s the way to go. Otherwise, keep reading my post.

My solution

Now let’s mix the two mentioned candidates and add a little bit of magic. First of all, let’s see what our goal is again:

You see? It’s simple. The goal is simple: after we swiped the item, we want it to collapse. Easy-peasy, isn’t it? Now let’s see how we overcome the issues mentioned above in the candidates.

In the first candidate the issue was the red background and icon just disappears and a blank space is left there, later the items below the item moves up. That looks unfinished. In the second candidate the background collapsed beautifully. But the icon disappeared and we couldn’t draw customized stuff for different swiped items. It was just one hardcoded draw.

So the goal is: When we swipe left, we want a red background to appear over the item and a trash icon on the left. When the item is swiped, all of this should collapse and everything should move smoothly. When we swipe right, we want a green background to appear over the item and a checkmark icon on the right. When the item is swiped, all of this should collapse and everything should move smoothly. All of this should be implemented in a reusable manner so we can reuse/modify it later. I will not explain every little bit of piece, I assume you have some experience with Android already.

Version #1

Direct link to this version on Github

Let’s start a new project and setup a RecyclerView with our custom Adapter.

This will be our model, a POJO.

Item.java

package com.messedcode.swipecollapseanimationexample;

public class Item {

    public String name;
    public Status status;

    public Item(String name) {
        this.name = name;
        this.status = Status.ACTIVE;
    }

    public enum Status {
        ACTIVE,
        CHECKED,
        DELETED
    }

}

This is the layout file for our item.

simple_item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:background="#FFFFFF">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center_vertical"
        android:padding="16dp"
        android:textAppearance="@style/TextAppearance.AppCompat.Large"
        tools:text="Test Text" />

    <View
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="#DDDDDD" />

</LinearLayout>

And here is a simple implementation of a RecyclerView Adapter.

CustomAdapter.java

package com.messedcode.swipecollapseanimationexample;

import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;

import java.util.ArrayList;

public class CustomAdapter extends RecyclerView.Adapter<CustomAdapter.ViewHolder> {

    private ArrayList<Item> dataset = new ArrayList<>();

    public CustomAdapter(String[] strings) {
        for (String string : strings) {
            dataset.add(new Item(string));
        }
    }

    @Override
    public CustomAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        LinearLayout v = (LinearLayout) LayoutInflater.from(parent.getContext()).inflate(R.layout.simple_item, parent, false);
        ViewHolder vh = new ViewHolder(v);
        return vh;
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        holder.textView.setText(dataset.get(position).name);
    }

    @Override
    public int getItemCount() {
        return dataset.size();
    }

    public void add(Item item) {
        dataset.add(item);
        notifyItemInserted(dataset.size() - 1);
    }

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

    public Item getItemAt(int position) {
        return dataset.get(position);
    }

    public static class ViewHolder extends RecyclerView.ViewHolder {
        public TextView textView;

        public ViewHolder(LinearLayout v) {
            super(v);
            textView = (TextView) v.getChildAt(0);
        }
    }
}

The layout we are going to use for our example app is very simple.

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.messedcode.swipecollapseanimationexample.MainActivity">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</FrameLayout>

Our activity is simple too, we just initialize a RecyclerView with our CustomAdapter and making it a stack the items vertically.

MainActivity.java

package com.messedcode.swipecollapseanimationexample;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        RecyclerView recyclerView = findViewById(R.id.recyclerView);

        // Adapter
        final CustomAdapter adapter = new CustomAdapter(new String[]{"Bread", "Egg", "Salmon", "Bacon", "Apple"});
        recyclerView.setAdapter(adapter);

        // Layout manager
        RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
        recyclerView.setLayoutManager(layoutManager);
    }

}

Version #2

Direct link to this version on Github

Now let’s make those items swipeable.

CustomItemTouchHelperCallback.java

package com.messedcode.swipecollapseanimationexample;

import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;

public class CustomItemTouchHelperCallback extends ItemTouchHelper.Callback {

    @Override
    public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        return makeMovementFlags(0, ItemTouchHelper.START | ItemTouchHelper.END);
    }

    @Override
    public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
        return false;
    }

    @Override
    public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {

    }

}

MainActivity.java

package com.messedcode.swipecollapseanimationexample;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        RecyclerView recyclerView = findViewById(R.id.recyclerView);

        // Adapter
        final CustomAdapter adapter = new CustomAdapter(new String[]{"Bread", "Egg", "Salmon", "Bacon", "Apple"});
        recyclerView.setAdapter(adapter);

        // Layout manager
        RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
        recyclerView.setLayoutManager(layoutManager);

        // Add swipe
        CustomItemTouchHelperCallback helperCb = new CustomItemTouchHelperCallback();
        ItemTouchHelper helper = new ItemTouchHelper(helperCb);
        helper.attachToRecyclerView(recyclerView);
    }

}

Version #3

Direct link to this version on Github

In the third version we add those sexy red and green background behind the items with awesome material icons.

CustomItemTouchHelperCallback.java

package com.messedcode.swipecollapseanimationexample;

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;
import android.view.View;

public class CustomItemTouchHelperCallback extends ItemTouchHelper.Callback {

    private final OnSwipeListener onSwipeListener;

    private final int leftBackgroundColor;
    private final Bitmap leftIcon;
    private final int rightBackgroundColor;
    private final Bitmap rightIcon;

    private final float iconSize;

    private final Paint paint = new Paint();

    private CustomItemTouchHelperCallback(Builder b) {
        this.onSwipeListener = b.onSwipeListener;

        this.leftBackgroundColor = b.leftBackgroundColor;
        this.leftIcon = b.leftIcon;
        this.rightBackgroundColor = b.rightBackgroundColor;
        this.rightIcon = b.rightIcon;

        this.iconSize = b.iconSize;
    }

    @Override
    public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        return makeMovementFlags(0, ItemTouchHelper.START | ItemTouchHelper.END);
    }

    @Override
    public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
        return false;
    }

    @Override
    public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
        if (onSwipeListener != null) {
            if (direction == ItemTouchHelper.START) {
                onSwipeListener.onSwipeLeft(viewHolder);
            } else if (direction == ItemTouchHelper.END) {
                onSwipeListener.onSwipeRight(viewHolder);
            }
        }
    }

    @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) {
            View itemView = viewHolder.itemView;

            float width = itemView.getWidth();
            float height = itemView.getHeight();

            if (height > 0) {
                float left = itemView.getLeft();
                float top = itemView.getTop();
                float right = itemView.getRight();
                float bottom = itemView.getBottom();

                float centerY = (top + bottom) / 2;

                float margin = width * 0.025f;

                if (dX > 0) {
                    paint.setColor(leftBackgroundColor);
                    RectF background = new RectF(left, top, dX, bottom);
                    c.drawRect(background, paint);

                    float iconLeft = left + margin;
                    RectF iconRect = new RectF(iconLeft, centerY - iconSize / 2, iconLeft + iconSize, centerY + iconSize / 2);
                    c.drawBitmap(leftIcon, null, iconRect, paint);
                } else {
                    paint.setColor(rightBackgroundColor);
                    RectF background = new RectF(right + dX, top, right, bottom);
                    c.drawRect(background, paint);

                    float iconRight = right - margin;
                    RectF iconRect = new RectF(iconRight - iconSize, centerY - iconSize / 2, iconRight, centerY + iconSize / 2);
                    c.drawBitmap(rightIcon, null, iconRect, paint);
                }
            }
        }

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

    public interface OnSwipeListener {

        void onSwipeRight(RecyclerView.ViewHolder vh);

        void onSwipeLeft(RecyclerView.ViewHolder vh);

    }

    public static class Builder {

        private OnSwipeListener onSwipeListener;

        private int leftBackgroundColor;
        private Bitmap leftIcon;
        private int rightBackgroundColor;
        private Bitmap rightIcon;

        private float iconSize;

        public Builder leftBackgroundColor(int leftBackgroundColor) {
            this.leftBackgroundColor = leftBackgroundColor;
            return this;
        }

        public Builder leftIcon(Bitmap leftIcon) {
            this.leftIcon = leftIcon;
            return this;
        }

        public Builder rightBackgroundColor(int rightBackgroundColor) {
            this.rightBackgroundColor = rightBackgroundColor;
            return this;
        }

        public Builder rightIcon(Bitmap rightIcon) {
            this.rightIcon = rightIcon;
            return this;
        }

        public Builder iconSize(float iconSize) {
            this.iconSize = iconSize;
            return this;
        }

        public Builder onSwipeListener(OnSwipeListener onSwipeLister) {
            this.onSwipeListener = onSwipeLister;
            return this;
        }

        public CustomItemTouchHelperCallback build() {
            return new CustomItemTouchHelperCallback(this);
        }

    }

}

MainActivity.java

package com.messedcode.swipecollapseanimationexample;

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;
import android.util.TypedValue;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        RecyclerView recyclerView = findViewById(R.id.recyclerView);

        // Adapter
        final CustomAdapter adapter = new CustomAdapter(new String[]{"Bread", "Egg", "Salmon", "Bacon", "Apple"});
        recyclerView.setAdapter(adapter);

        // Layout manager
        RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
        recyclerView.setLayoutManager(layoutManager);

        // Add swipe
        int red = Color.parseColor("#F44336");
        int green = Color.parseColor("#4CAF50");

        Drawable deleteIconWhite = getResources().getDrawable(R.drawable.ic_delete_black_24dp).mutate();
        deleteIconWhite.setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_IN);

        Drawable checkIconWhite = getResources().getDrawable(R.drawable.ic_check_black_24dp).mutate();
        checkIconWhite.setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_IN);

        float iconSizeInDp = 24;
        CustomItemTouchHelperCallback helperCb = new CustomItemTouchHelperCallback.Builder()
                .iconSize(dpToPx(iconSizeInDp))
                .leftBackgroundColor(red)
                .leftIcon(convertDrawableToBitmap(deleteIconWhite))
                .rightBackgroundColor(green)
                .rightIcon(convertDrawableToBitmap(checkIconWhite))
                .build();
        ItemTouchHelper helper = new ItemTouchHelper(helperCb);
        helper.attachToRecyclerView(recyclerView);
    }

    private float dpToPx(float dp) {
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
    }

    private Bitmap convertDrawableToBitmap(Drawable drawable) {
        Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);

        drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
        drawable.draw(canvas);

        return bitmap;
    }

}

Version #4

Direct link to this version on Github

The last piece of the puzzle: make those swiped items animated. COLLAPSE THEM!

LayoutParamHeightAnimator.java

package com.messedcode.swipecollapseanimationexample;

import android.animation.ValueAnimator;
import android.view.View;

public class LayoutParamHeightAnimator extends ValueAnimator {

    public LayoutParamHeightAnimator(final View target, int... values) {
        setIntValues(values);

        addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                int value = (int) valueAnimator.getAnimatedValue();
                target.getLayoutParams().height = value;
                target.requestLayout();
            }
        });
    }

    public static LayoutParamHeightAnimator collapse(View target) {
        return new LayoutParamHeightAnimator(target, target.getHeight(), 0);
    }

}

CustomItemAnimator.java

package com.messedcode.swipecollapseanimationexample;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.support.annotation.NonNull;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.Interpolator;

public class CustomItemAnimator extends DefaultItemAnimator {

    public static final Interpolator COLLAPSE_INTERPOLATOR = new AccelerateInterpolator(3f);
    public static final int COLLAPSE_ANIM_DURATION = 600;

    @Override
    public boolean canReuseUpdatedViewHolder(@NonNull RecyclerView.ViewHolder viewHolder) {
        return true;
    }

    @Override
    public boolean animateChange(@NonNull RecyclerView.ViewHolder oldHolder, @NonNull final RecyclerView.ViewHolder newHolder, @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) {
        final View itemView = newHolder.itemView;
        AnimatorSet set = new AnimatorSet();

        LayoutParamHeightAnimator animHeight = LayoutParamHeightAnimator.collapse(itemView);
        animHeight.setDuration(COLLAPSE_ANIM_DURATION).setInterpolator(COLLAPSE_INTERPOLATOR);

        set.play(animHeight);

        set.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationStart(Animator animation) {
                dispatchChangeStarting(newHolder, false);
            }

            @Override
            public void onAnimationEnd(Animator animator) {
                dispatchChangeFinished(newHolder, false);
            }
        });

        set.start();

        return false;
    }

}

MainActivity.java

package com.messedcode.swipecollapseanimationexample;

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;
import android.util.TypedValue;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        RecyclerView recyclerView = findViewById(R.id.recyclerView);

        // Adapter
        final CustomAdapter adapter = new CustomAdapter(new String[]{"Bread", "Egg", "Salmon", "Bacon", "Apple"});
        recyclerView.setAdapter(adapter);

        // Layout manager
        RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
        recyclerView.setLayoutManager(layoutManager);

        // Add swipe
        int red = Color.parseColor("#F44336");
        int green = Color.parseColor("#4CAF50");

        Drawable deleteIconWhite = getResources().getDrawable(R.drawable.ic_delete_black_24dp).mutate();
        deleteIconWhite.setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_IN);

        Drawable checkIconWhite = getResources().getDrawable(R.drawable.ic_check_black_24dp).mutate();
        checkIconWhite.setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_IN);

        float iconSizeInDp = 24;
        CustomItemTouchHelperCallback helperCb = new CustomItemTouchHelperCallback.Builder()
                .iconSize(dpToPx(iconSizeInDp))
                .leftBackgroundColor(red)
                .leftIcon(convertDrawableToBitmap(deleteIconWhite))
                .rightBackgroundColor(green)
                .rightIcon(convertDrawableToBitmap(checkIconWhite))
                .onSwipeListener(new CustomItemTouchHelperCallback.OnSwipeListener() {
                    @Override
                    public void onSwipeRight(RecyclerView.ViewHolder vh) {
                        int position = vh.getAdapterPosition();
                        Item item = adapter.getItemAt(position);
                        item.status = Item.Status.CHECKED;
                        adapter.notifyItemChanged(position);
                    }

                    @Override
                    public void onSwipeLeft(RecyclerView.ViewHolder vh) {
                        int position = vh.getAdapterPosition();
                        Item item = adapter.getItemAt(position);
                        item.status = Item.Status.DELETED;
                        adapter.notifyItemChanged(position);
                    }
                })
                .build();
        ItemTouchHelper helper = new ItemTouchHelper(helperCb);
        helper.attachToRecyclerView(recyclerView);

        // Add animation
        CustomItemAnimator animator = new CustomItemAnimator();
        recyclerView.setItemAnimator(animator);
    }

    private float dpToPx(float dp) {
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
    }

    private Bitmap convertDrawableToBitmap(Drawable drawable) {
        Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);

        drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
        drawable.draw(canvas);

        return bitmap;
    }

}

Version #5 (Extra)

Direct link to this version on Github

Here is an extra version for our loyal readers: We are going to add a listener to our CustomItemAnimator so we can remove the item from the adapter. This is just an example how you can do something when the animation finished.

CustomItemAnimator.java

package com.messedcode.swipecollapseanimationexample;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.support.annotation.NonNull;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.Interpolator;

public class CustomItemAnimator extends DefaultItemAnimator {

    public static final Interpolator COLLAPSE_INTERPOLATOR = new AccelerateInterpolator(3f);
    public static final int COLLAPSE_ANIM_DURATION = 600;

    private onAnimationEndListener animationEndListener;

    public CustomItemAnimator() {

    }

    public CustomItemAnimator(onAnimationEndListener listener) {
        this.animationEndListener = listener;
    }

    @Override
    public boolean canReuseUpdatedViewHolder(@NonNull RecyclerView.ViewHolder viewHolder) {
        return true;
    }

    @Override
    public boolean animateChange(@NonNull RecyclerView.ViewHolder oldHolder, @NonNull final RecyclerView.ViewHolder newHolder, @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) {
        final View itemView = newHolder.itemView;
        AnimatorSet set = new AnimatorSet();

        LayoutParamHeightAnimator animHeight = LayoutParamHeightAnimator.collapse(itemView);
        animHeight.setDuration(COLLAPSE_ANIM_DURATION).setInterpolator(COLLAPSE_INTERPOLATOR);

        set.play(animHeight);

        set.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationStart(Animator animation) {
                dispatchChangeStarting(newHolder, false);
            }

            @Override
            public void onAnimationEnd(Animator animator) {
                dispatchChangeFinished(newHolder, false);
                if (animationEndListener != null) {
                    animationEndListener.onChangeEnd(newHolder);
                }
            }
        });

        set.start();

        return false;
    }

    interface onAnimationEndListener {
        void onChangeEnd(RecyclerView.ViewHolder newHolder);
    }

}

MainActivity.java

package com.messedcode.swipecollapseanimationexample;

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;
import android.util.TypedValue;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        RecyclerView recyclerView = findViewById(R.id.recyclerView);

        // Adapter
        final CustomAdapter adapter = new CustomAdapter(new String[]{"Bread", "Egg", "Salmon", "Bacon", "Apple"});
        recyclerView.setAdapter(adapter);

        // Layout manager
        RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
        recyclerView.setLayoutManager(layoutManager);

        // Add swipe
        int red = Color.parseColor("#F44336");
        int green = Color.parseColor("#4CAF50");

        Drawable deleteIconWhite = getResources().getDrawable(R.drawable.ic_delete_black_24dp).mutate();
        deleteIconWhite.setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_IN);

        Drawable checkIconWhite = getResources().getDrawable(R.drawable.ic_check_black_24dp).mutate();
        checkIconWhite.setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_IN);

        float iconSizeInDp = 24;
        CustomItemTouchHelperCallback helperCb = new CustomItemTouchHelperCallback.Builder()
                .iconSize(dpToPx(iconSizeInDp))
                .leftBackgroundColor(red)
                .leftIcon(convertDrawableToBitmap(deleteIconWhite))
                .rightBackgroundColor(green)
                .rightIcon(convertDrawableToBitmap(checkIconWhite))
                .onSwipeListener(new CustomItemTouchHelperCallback.OnSwipeListener() {
                    @Override
                    public void onSwipeRight(RecyclerView.ViewHolder vh) {
                        int position = vh.getAdapterPosition();
                        Item item = adapter.getItemAt(position);
                        item.status = Item.Status.CHECKED;
                        adapter.notifyItemChanged(position);
                    }

                    @Override
                    public void onSwipeLeft(RecyclerView.ViewHolder vh) {
                        int position = vh.getAdapterPosition();
                        Item item = adapter.getItemAt(position);
                        item.status = Item.Status.DELETED;
                        adapter.notifyItemChanged(position);
                    }
                })
                .build();
        ItemTouchHelper helper = new ItemTouchHelper(helperCb);
        helper.attachToRecyclerView(recyclerView);

        // Add animation
        CustomItemAnimator animator = new CustomItemAnimator(new CustomItemAnimator.onAnimationEndListener() {
            @Override
            public void onChangeEnd(RecyclerView.ViewHolder newHolder) {
                adapter.remove(newHolder.getAdapterPosition());
            }
        });
        recyclerView.setItemAnimator(animator);
    }

    private float dpToPx(float dp) {
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
    }

    private Bitmap convertDrawableToBitmap(Drawable drawable) {
        Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);

        drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
        drawable.draw(canvas);

        return bitmap;
    }

}