vc908.stickerfactory.ui.advancedrecyclerview.swipeable.RecyclerViewSwipeManager.java Source code

Java tutorial

Introduction

Here is the source code for vc908.stickerfactory.ui.advancedrecyclerview.swipeable.RecyclerViewSwipeManager.java

Source

/*
 *    Copyright (C) 2015
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package vc908.stickerfactory.ui.advancedrecyclerview.swipeable;

import android.graphics.Rect;
import android.os.Build;
import android.support.v4.view.MotionEventCompat;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.ViewPropertyAnimatorListener;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;

import vc908.stickerfactory.ui.advancedrecyclerview.animator.SwipeDismissItemAnimator;
import vc908.stickerfactory.ui.advancedrecyclerview.utils.CustomRecyclerViewUtils;
import vc908.stickerfactory.ui.advancedrecyclerview.utils.WrapperAdapterUtils;

/**
 * Provides item swipe operation for {@link RecyclerView}
 */
@SuppressWarnings("PointlessBitwiseExpression")
public class RecyclerViewSwipeManager {
    private static final String TAG = "ARVSwipeManager";

    /**
     * Result code of swipe operation. Used for the second argument of the
     * {@link SwipeableItemAdapter#onPerformAfterSwipeReaction(RecyclerView.ViewHolder, int, int, int)} method.
     *
     * None. (internal default value, this value is not used for the argument)
     */
    public static final int RESULT_NONE = 0;

    /**
     * Result code of swipe operation. Used for the second argument of the
     * {@link SwipeableItemAdapter#onPerformAfterSwipeReaction(RecyclerView.ViewHolder, int, int, int)} method.
     *
     * Canceled.
     */
    public static final int RESULT_CANCELED = 1;

    /**
     * Result code of swipe operation. Used for the second argument of the
     * {@link SwipeableItemAdapter#onPerformAfterSwipeReaction(RecyclerView.ViewHolder, int, int, int)} method.
     *
     * Swipe left performed.
     */
    public static final int RESULT_SWIPED_LEFT = 2;

    /**
     * Result code of swipe operation. Used for the second argument of the
     * {@link SwipeableItemAdapter#onPerformAfterSwipeReaction(RecyclerView.ViewHolder, int, int, int)} method.
     *
     * Swipe right performed.
     */
    public static final int RESULT_SWIPED_RIGHT = 3;

    // ---
    /**
     * Reaction type to swipe operation. Used for the return value of the
     * {@link SwipeableItemAdapter#onGetSwipeReactionType(RecyclerView.ViewHolder, int, int, int)} method.
     *
     * Indicates "can not swipe left" (completely no reactions)
     */
    public static final int REACTION_CAN_NOT_SWIPE_LEFT = (0 << 0);

    /**
     * Reaction type to swipe operation. Used for the return value of the
     * {@link SwipeableItemAdapter#onGetSwipeReactionType(RecyclerView.ViewHolder, int, int, int)} method.
     *
     * Indicates "can not swipe left"  (not swipeable, but rubber-band effect applied)
     */
    public static final int REACTION_CAN_NOT_SWIPE_LEFT_WITH_RUBBER_BAND_EFFECT = (1 << 0);

    /**
     * Reaction type to swipe operation. Used for the return value of the
     * {@link SwipeableItemAdapter#onGetSwipeReactionType(RecyclerView.ViewHolder, int, int, int)} method.
     *
     * Indicates "can swipe left"
     */
    public static final int REACTION_CAN_SWIPE_LEFT = (2 << 0);

    /**
     * Reaction type to swipe operation. Used for the return value of the
     * {@link SwipeableItemAdapter#onGetSwipeReactionType(RecyclerView.ViewHolder, int, int, int)} method.
     *
     * Indicates "can not swipe right" (completely no reactions)
     */
    public static final int REACTION_CAN_NOT_SWIPE_RIGHT = (0 << 16);

    /**
     * Reaction type to swipe operation. Used for the return value of the
     * {@link SwipeableItemAdapter#onGetSwipeReactionType(RecyclerView.ViewHolder, int, int, int)} method.
     *
     * Indicates "can not swipe right"  (not swipeable, but rubber-band effect applied)
     */
    public static final int REACTION_CAN_NOT_SWIPE_RIGHT_WITH_RUBBER_BAND_EFFECT = (1 << 16);

    /**
     * Reaction type to swipe operation. Used for the return value of the
     * {@link SwipeableItemAdapter#onGetSwipeReactionType(RecyclerView.ViewHolder, int, int, int)} method.
     *
     * Indicates "can swipe right"
     */
    public static final int REACTION_CAN_SWIPE_RIGHT = (2 << 16);

    /**
     * Convenient constant value: Equals to {@link #REACTION_CAN_NOT_SWIPE_LEFT} | {@link #REACTION_CAN_NOT_SWIPE_RIGHT}
     */
    public static final int REACTION_CAN_NOT_SWIPE_BOTH = REACTION_CAN_NOT_SWIPE_LEFT
            | REACTION_CAN_NOT_SWIPE_RIGHT;

    /**
     * Convenient constant value: Equals to {@link #REACTION_CAN_NOT_SWIPE_LEFT_WITH_RUBBER_BAND_EFFECT} | {@link #REACTION_CAN_NOT_SWIPE_RIGHT_WITH_RUBBER_BAND_EFFECT}
     */
    public static final int REACTION_CAN_NOT_SWIPE_BOTH_WITH_RUBBER_BAND_EFFECT = REACTION_CAN_NOT_SWIPE_LEFT_WITH_RUBBER_BAND_EFFECT
            | REACTION_CAN_NOT_SWIPE_RIGHT_WITH_RUBBER_BAND_EFFECT;

    /**
     * Convenient constant value: Equals to {@link #REACTION_CAN_SWIPE_LEFT} | {@link #REACTION_CAN_SWIPE_RIGHT}
     */
    public static final int REACTION_CAN_SWIPE_BOTH = REACTION_CAN_SWIPE_LEFT | REACTION_CAN_SWIPE_RIGHT;

    // ---
    /**
     * Background drawable type used for the second argument of the
     * {@link SwipeableItemAdapter#onSetSwipeBackground(RecyclerView.ViewHolder, int, int)} method.
     *
     * Background image for the neutral (= not swiping) item.
     */
    public static final int DRAWABLE_SWIPE_NEUTRAL_BACKGROUND = 0;

    /**
     * Background drawable type used for the second argument of the
     * {@link SwipeableItemAdapter#onSetSwipeBackground(RecyclerView.ViewHolder, int, int)} method.
     *
     * Background image for the swiping-left item.
     */
    public static final int DRAWABLE_SWIPE_LEFT_BACKGROUND = 1;

    /**
     * Background drawable type used for the second argument of the
     * {@link SwipeableItemAdapter#onSetSwipeBackground(RecyclerView.ViewHolder, int, int)} method.
     *
     * Background image for the swiping-right item.
     */
    public static final int DRAWABLE_SWIPE_RIGHT_BACKGROUND = 2;

    // ---
    /**
     * After-reaction type used for the {@link SwipeableItemViewHolder#setAfterSwipeReaction(int)} and {@link SwipeableItemViewHolder#getAfterSwipeReaction()} methods.
     * Represents perform nothing.
     */
    public static final int AFTER_SWIPE_REACTION_DEFAULT = 0;

    /**
     * After-reaction type used for the {@link SwipeableItemViewHolder#setAfterSwipeReaction(int)} and {@link SwipeableItemViewHolder#getAfterSwipeReaction()} methods.
     * Represents remove the swiped item.
     */
    public static final int AFTER_SWIPE_REACTION_REMOVE_ITEM = 1;

    /**
     * After-reaction type used for the {@link SwipeableItemViewHolder#setAfterSwipeReaction(int)} and {@link SwipeableItemViewHolder#getAfterSwipeReaction()} methods.
     * Represents that the item moved to swiped direction.
     */
    public static final int AFTER_SWIPE_REACTION_MOVE_TO_SWIPED_DIRECTION = 2;

    // ---
    /**
     * Special value for the {@link SwipeableItemViewHolder#setSwipeItemSlideAmount(float)} and {@link SwipeableItemViewHolder#getSwipeItemSlideAmount()} methods.
     * Indicates that this item is pinned to LEFT of the window.
     */
    public static final float OUTSIDE_OF_THE_WINDOW_LEFT = -Float.MAX_VALUE;

    /**
     * Special value for the {@link SwipeableItemViewHolder#setSwipeItemSlideAmount(float)} and {@link SwipeableItemViewHolder#getSwipeItemSlideAmount()} methods.
     * Indicates that this item is pinned to RIGHT the window.
     */
    public static final float OUTSIDE_OF_THE_WINDOW_RIGHT = Float.MAX_VALUE;

    // ---
    /**
     * State flag for the {@link SwipeableItemViewHolder#setSwipeStateFlags(int)} and {@link SwipeableItemViewHolder#getSwipeStateFlags()} methods.
     * Indicates that currently performing swiping.
     */
    public static final int STATE_FLAG_SWIPING = (1 << 0);

    /**
     * State flag for the {@link SwipeableItemViewHolder#setSwipeStateFlags(int)} and {@link SwipeableItemViewHolder#getSwipeStateFlags()} methods.
     * Indicates that this item is being swiped.
     */
    public static final int STATE_FLAG_IS_ACTIVE = (1 << 1);

    /**
     * State flag for the {@link SwipeableItemViewHolder#setSwipeStateFlags(int)} and {@link SwipeableItemViewHolder#getSwipeStateFlags()} methods.
     * If this flag is set, some other flags are changed and require to apply.
     */
    public static final int STATE_FLAG_IS_UPDATED = (1 << 31);

    // ---

    private static final int MIN_DISTANCE_TOUCH_SLOP_MUL = 10;
    private static final int SLIDE_ITEM_IMMEDIATELY_SET_TRANSLATION_THRESHOLD_DP = 8;

    private static final boolean LOCAL_LOGV = false;
    private static final boolean LOCAL_LOGD = false;

    private RecyclerView.OnItemTouchListener mInternalUseOnItemTouchListener;
    private RecyclerView mRecyclerView;

    private long mReturnToDefaultPositionAnimationDuration = 300;
    private long mMoveToOutsideWindowAnimationDuration = 200;

    private int mTouchSlop;
    private int mMinFlingVelocity; // [pixels per second]
    private int mMaxFlingVelocity; // [pixels per second]
    private int mInitialTouchX;
    private int mInitialTouchY;
    private long mCheckingTouchSlop = RecyclerView.NO_ID;

    private ItemSlidingAnimator mItemSlideAnimator;
    private SwipeableItemWrapperAdapter<RecyclerView.ViewHolder> mAdapter;
    private RecyclerView.ViewHolder mSwipingItem;
    private int mSwipingItemPosition = RecyclerView.NO_POSITION;
    private Rect mSwipingItemMargins = new Rect();
    private int mTouchedItemOffsetX;
    private int mLastTouchX;
    private int mSwipingItemReactionType;
    private VelocityTracker mVelocityTracker;
    private SwipingItemOperator mSwipingItemOperator;

    /**
     * Constructor.
     */
    public RecyclerViewSwipeManager() {
        mInternalUseOnItemTouchListener = new RecyclerView.OnItemTouchListener() {
            @Override
            public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
                return RecyclerViewSwipeManager.this.onInterceptTouchEvent(rv, e);
            }

            @Override
            public void onTouchEvent(RecyclerView rv, MotionEvent e) {
                RecyclerViewSwipeManager.this.onTouchEvent(rv, e);
            }

            @Override
            public void onRequestDisallowInterceptTouchEvent(boolean arg0) {
                // TODO Auto-generated method stub

            }
        };
        mItemSlideAnimator = new ItemSlidingAnimator();
        mVelocityTracker = VelocityTracker.obtain();
    }

    /**
     * Create wrapped adapter.
     *
     * @param adapter The target adapter.
     *
     * @return Wrapped adapter which is associated to this {@link RecyclerViewSwipeManager} instance.
     */
    @SuppressWarnings("unchecked")
    public RecyclerView.Adapter createWrappedAdapter(RecyclerView.Adapter adapter) {
        if (mAdapter != null) {
            throw new IllegalStateException("already have a wrapped adapter");
        }

        mAdapter = new SwipeableItemWrapperAdapter(this, adapter);

        return mAdapter;
    }

    /**
     * Indicates this manager instance has released or not.
     *
     * @return True if this manager instance has released
     */
    public boolean isReleased() {
        return (mInternalUseOnItemTouchListener == null);
    }

    /**
     * Attaches {@link RecyclerView} instance.
     *
     * Before calling this method, the target {@link RecyclerView} must set
     * the wrapped adapter instance which is returned by the
     * {@link #createWrappedAdapter(RecyclerView.Adapter)} method.
     *
     * @param rv The {@link RecyclerView} instance
     */
    public void attachRecyclerView(RecyclerView rv) {
        if (rv == null) {
            throw new IllegalArgumentException("RecyclerView cannot be null");
        }

        if (isReleased()) {
            throw new IllegalStateException("Accessing released object");
        }

        if (mRecyclerView != null) {
            throw new IllegalStateException("RecyclerView instance has already been set");
        }

        if (mAdapter == null || getSwipeableItemWrapperAdapter(rv) != mAdapter) {
            throw new IllegalStateException("adapter is not set properly");
        }

        mRecyclerView = rv;
        mRecyclerView.addOnItemTouchListener(mInternalUseOnItemTouchListener);

        final ViewConfiguration vc = ViewConfiguration.get(rv.getContext());

        mTouchSlop = vc.getScaledTouchSlop();
        mMinFlingVelocity = vc.getScaledMinimumFlingVelocity();
        mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();

        mItemSlideAnimator
                .setImmediatelySetTranslationThreshold((int) (rv.getResources().getDisplayMetrics().density
                        * SLIDE_ITEM_IMMEDIATELY_SET_TRANSLATION_THRESHOLD_DP + 0.5f));
    }

    /**
     * Detach the {@link RecyclerView} instance and release internal field references.
     *
     * This method should be called in order to avoid memory leaks.
     */
    public void release() {
        if (mRecyclerView != null && mInternalUseOnItemTouchListener != null) {
            mRecyclerView.removeOnItemTouchListener(mInternalUseOnItemTouchListener);
        }
        mInternalUseOnItemTouchListener = null;

        if (mVelocityTracker != null) {
            mVelocityTracker.recycle();
            mVelocityTracker = null;
        }

        if (mItemSlideAnimator != null) {
            mItemSlideAnimator.endAnimations();
            mItemSlideAnimator = null;
        }

        mAdapter = null;
        mRecyclerView = null;
    }

    /**
     * Indicates whether currently performing item swiping.
     *
     * @return True if currently performing item swiping
     */
    public boolean isSwiping() {
        return (mSwipingItem != null);
    }

    /*package*/ boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
        final int action = MotionEventCompat.getActionMasked(e);

        if (LOCAL_LOGV) {
            Log.v(TAG, "onInterceptTouchEvent() action = " + action);
        }

        switch (action) {
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:
            if (isSwiping()) {
                handleActionUpOrCancelWhileSwiping(e);
                return true;
            } else {
                handleActionUpOrCancelWhileNotSwiping();
            }
            break;

        case MotionEvent.ACTION_DOWN:
            if (!isSwiping()) {
                handleActionDown(rv, e);
            }
            break;

        case MotionEvent.ACTION_MOVE:
            if (isSwiping()) {
                // NOTE: The first ACTION_MOVE event will come here. (maybe a bug of RecyclerView?)
                handleActionMoveWhileSwiping(e);
                return true;
            } else {
                if (handleActionMoveWhileNotSwiping(rv, e)) {
                    return true;
                }
            }
            break;
        }

        return false;
    }

    /*package*/ void onTouchEvent(RecyclerView rv, MotionEvent e) {
        final int action = MotionEventCompat.getActionMasked(e);

        if (LOCAL_LOGV) {
            Log.v(TAG, "onTouchEvent() action = " + action);
        }

        if (!isSwiping()) {
            return;
        }

        switch (action) {
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:
            handleActionUpOrCancelWhileSwiping(e);
            break;

        case MotionEvent.ACTION_MOVE:
            handleActionMoveWhileSwiping(e);
            break;
        }
    }

    private boolean handleActionDown(RecyclerView rv, MotionEvent e) {
        final RecyclerView.Adapter adapter = rv.getAdapter();
        final RecyclerView.ViewHolder holder = CustomRecyclerViewUtils.findChildViewHolderUnderWithTranslation(rv,
                e.getX(), e.getY());

        if (!(holder instanceof SwipeableItemViewHolder)) {
            return false;
        }

        final int itemPosition = CustomRecyclerViewUtils.getSynchronizedPosition(holder);

        // verify the touched item is valid state
        if (!(itemPosition >= 0 && itemPosition < adapter.getItemCount())) {
            return false;
        }
        if (holder.getItemId() != adapter.getItemId(itemPosition)) {
            return false;
        }

        final int touchX = (int) (e.getX() + 0.5f);
        final int touchY = (int) (e.getY() + 0.5f);

        final View view = holder.itemView;
        final int translateX = (int) (ViewCompat.getTranslationX(view) + 0.5f);
        final int translateY = (int) (ViewCompat.getTranslationY(view) + 0.5f);
        final int viewX = touchX - (view.getLeft() + translateX);
        final int viewY = touchY - (view.getTop() + translateY);

        final int reactionType = mAdapter.getSwipeReactionType(holder, itemPosition, viewX, viewY);

        if (reactionType == 0) {
            return false;
        }

        mInitialTouchX = touchX;
        mInitialTouchY = touchY;
        mCheckingTouchSlop = holder.getItemId();
        mSwipingItemReactionType = reactionType;

        return true;
    }

    private static SwipeableItemWrapperAdapter getSwipeableItemWrapperAdapter(RecyclerView rv) {
        return WrapperAdapterUtils.findWrappedAdapter(rv.getAdapter(), SwipeableItemWrapperAdapter.class);
    }

    private void handleActionUpOrCancelWhileNotSwiping() {
        mCheckingTouchSlop = RecyclerView.NO_ID;
        mSwipingItemReactionType = 0;
    }

    private boolean handleActionUpOrCancelWhileSwiping(MotionEvent e) {
        mLastTouchX = (int) (e.getX() + 0.5f);

        int result = RESULT_CANCELED;

        if (MotionEventCompat.getActionMasked(e) == MotionEvent.ACTION_UP) {
            final int viewWidth = mSwipingItem.itemView.getWidth();
            final float distance = mLastTouchX - mInitialTouchX;
            final float absDistance = Math.abs(distance);

            mVelocityTracker.computeCurrentVelocity(1000, mMaxFlingVelocity); // 1000: pixels per second

            final float xVelocity = mVelocityTracker.getXVelocity();
            final float absXVelocity = Math.abs(xVelocity);

            if ((absDistance > (mTouchSlop * MIN_DISTANCE_TOUCH_SLOP_MUL)) && ((distance * xVelocity) > 0.0f)
                    && (absXVelocity <= mMaxFlingVelocity)
                    && ((absDistance > (viewWidth / 2)) || (absXVelocity >= mMinFlingVelocity))) {

                if ((distance < 0) && SwipeReactionUtils.canSwipeLeft(mSwipingItemReactionType)) {
                    result = RESULT_SWIPED_LEFT;
                } else if ((distance > 0) && SwipeReactionUtils.canSwipeRight(mSwipingItemReactionType)) {
                    result = RESULT_SWIPED_RIGHT;
                }
            }
        }

        if (LOCAL_LOGD) {
            Log.d(TAG, "swping finished  --- result = " + result);
        }

        finishSwiping(result);

        return true;
    }

    private boolean handleActionMoveWhileNotSwiping(RecyclerView rv, MotionEvent e) {
        if (mCheckingTouchSlop == RecyclerView.NO_ID) {
            return false;
        }

        if (Math.abs(e.getY() - mInitialTouchY) > mTouchSlop) {
            // scrolling occurred
            mCheckingTouchSlop = RecyclerView.NO_ID;
            return false;
        }

        if (Math.abs(e.getX() - mInitialTouchX) <= mTouchSlop) {
            return false;
        }

        final RecyclerView.ViewHolder holder = CustomRecyclerViewUtils.findChildViewHolderUnderWithTranslation(rv,
                e.getX(), e.getY());

        if (holder == null || holder.getItemId() != mCheckingTouchSlop) {
            mCheckingTouchSlop = RecyclerView.NO_ID;
            return false;
        }

        final int itemPosition = CustomRecyclerViewUtils.getSynchronizedPosition(holder);

        if (itemPosition == RecyclerView.NO_POSITION) {
            return false;
        }

        if (LOCAL_LOGD) {
            Log.d(TAG, "swiping started");
        }

        startSwiping(rv, e, holder, itemPosition);

        return true;
    }

    private void handleActionMoveWhileSwiping(MotionEvent e) {
        mLastTouchX = (int) (e.getX() + 0.5f);
        mVelocityTracker.addMovement(e);

        final int swipeDistance = mLastTouchX - mTouchedItemOffsetX;

        mSwipingItemOperator.update(swipeDistance);
    }

    private void startSwiping(RecyclerView rv, MotionEvent e, RecyclerView.ViewHolder holder, int itemPosition) {
        mSwipingItem = holder;
        mSwipingItemPosition = itemPosition;
        mLastTouchX = (int) (e.getX() + 0.5f);
        mTouchedItemOffsetX = mLastTouchX;
        CustomRecyclerViewUtils.getLayoutMargins(holder.itemView, mSwipingItemMargins);

        mSwipingItemOperator = new SwipingItemOperator(this, mSwipingItem, mSwipingItemPosition,
                mSwipingItemReactionType);
        mSwipingItemOperator.start();

        mVelocityTracker.clear();
        mVelocityTracker.addMovement(e);

        mRecyclerView.getParent().requestDisallowInterceptTouchEvent(true);

        // raise onSwipeItemStarted() event
        mAdapter.onSwipeItemStarted(this, holder, itemPosition);
    }

    private void finishSwiping(int result) {
        final RecyclerView.ViewHolder swipingItem = mSwipingItem;

        if (swipingItem == null) {
            return;
        }

        if (mRecyclerView != null && mRecyclerView.getParent() != null) {
            mRecyclerView.getParent().requestDisallowInterceptTouchEvent(false);
        }

        final int itemPosition = mSwipingItemPosition;

        mVelocityTracker.clear();

        mSwipingItem = null;
        mSwipingItemPosition = RecyclerView.NO_POSITION;
        mLastTouchX = 0;
        mInitialTouchX = 0;
        mTouchedItemOffsetX = 0;
        mCheckingTouchSlop = RecyclerView.NO_ID;
        mSwipingItemReactionType = 0;

        if (mSwipingItemOperator != null) {
            mSwipingItemOperator.finish();
            mSwipingItemOperator = null;
        }

        final boolean toLeft = (result == RESULT_SWIPED_LEFT);
        int afterReaction = AFTER_SWIPE_REACTION_DEFAULT;

        if (mAdapter != null) {
            afterReaction = mAdapter.onSwipeItemFinished(swipingItem, itemPosition, result);
        }

        afterReaction = correctAfterReaction(result, afterReaction);

        switch (afterReaction) {
        case AFTER_SWIPE_REACTION_DEFAULT:
            mItemSlideAnimator.slideToDefaultPosition(swipingItem, true, mReturnToDefaultPositionAnimationDuration);
            break;
        case AFTER_SWIPE_REACTION_MOVE_TO_SWIPED_DIRECTION:
            mItemSlideAnimator.slideToOutsideOfWindow(swipingItem, toLeft, true,
                    mMoveToOutsideWindowAnimationDuration);
            break;
        case AFTER_SWIPE_REACTION_REMOVE_ITEM: {
            final RecyclerView.ItemAnimator itemAnimator = mRecyclerView.getItemAnimator();

            final long removeAnimationDuration = (itemAnimator != null) ? itemAnimator.getRemoveDuration() : 0;

            if (supportsViewPropertyAnimator()) {
                final long moveAnimationDuration = (itemAnimator != null) ? itemAnimator.getMoveDuration() : 0;

                final RemovingItemDecorator decorator = new RemovingItemDecorator(mRecyclerView, swipingItem,
                        removeAnimationDuration, moveAnimationDuration);

                decorator.setMoveAnimationInterpolator(SwipeDismissItemAnimator.MOVE_INTERPOLATOR);
                decorator.start();
            }

            mItemSlideAnimator.slideToOutsideOfWindow(swipingItem, toLeft, true, removeAnimationDuration);
        }
            break;
        default:
            throw new IllegalStateException("Unknwon after reaction type: " + afterReaction);
        }

        if (mAdapter != null) {
            mAdapter.onSwipeItemFinished2(swipingItem, itemPosition, result, afterReaction);
        }
    }

    private static int correctAfterReaction(int result, int afterReaction) {
        if ((afterReaction == AFTER_SWIPE_REACTION_MOVE_TO_SWIPED_DIRECTION)
                || (afterReaction == AFTER_SWIPE_REACTION_REMOVE_ITEM)) {
            if (!((result == RESULT_SWIPED_LEFT) || (result == RESULT_SWIPED_RIGHT))) {
                afterReaction = AFTER_SWIPE_REACTION_DEFAULT;
            }
        }

        return afterReaction;
    }

    public void cancelSwipe() {
        finishSwiping(RESULT_CANCELED);
    }

    /*package*/ boolean isAnimationRunning(RecyclerView.ViewHolder item) {
        return (mItemSlideAnimator != null) && (mItemSlideAnimator.isRunning(item));
    }

    private void slideItem(RecyclerView.ViewHolder holder, float amount, boolean shouldAnimate) {
        if (amount == OUTSIDE_OF_THE_WINDOW_LEFT) {
            mItemSlideAnimator.slideToOutsideOfWindow(holder, true, shouldAnimate,
                    mMoveToOutsideWindowAnimationDuration);
        } else if (amount == OUTSIDE_OF_THE_WINDOW_RIGHT) {
            mItemSlideAnimator.slideToOutsideOfWindow(holder, false, shouldAnimate,
                    mMoveToOutsideWindowAnimationDuration);
        } else if (amount == 0.0f) {
            mItemSlideAnimator.slideToDefaultPosition(holder, shouldAnimate,
                    mReturnToDefaultPositionAnimationDuration);
        } else {
            mItemSlideAnimator.slideToSpecifiedPosition(holder, amount, shouldAnimate);
        }
    }

    /**
     * Gets the duration of the "return to default position" animation
     *
     * @return Duration of the "return to default position" animation in milliseconds
     */
    public long getReturnToDefaultPositionAnimationDuration() {
        return mReturnToDefaultPositionAnimationDuration;
    }

    /**
     * Sets the duration of the "return to default position" animation
     *
     * @param duration Duration of the "return to default position" animation in milliseconds
     */
    public void setReturnToDefaultPositionAnimationDuration(long duration) {
        mReturnToDefaultPositionAnimationDuration = duration;
    }

    /**
     * Gets the duration of the "move to outside of the window" animation
     *
     * @return Duration of the "move to outside of the window" animation in milliseconds
     */
    public long getMoveToOutsideWindowAnimationDuration() {
        return mMoveToOutsideWindowAnimationDuration;
    }

    /**
     * Sets the duration of the "move to outside of the window" animation
     *
     * @param duration Duration of the "move to outside of the window" animation in milliseconds
     */
    public void setMoveToOutsideWindowAnimationDuration(long duration) {
        mMoveToOutsideWindowAnimationDuration = duration;
    }

    /*package*/ void applySlideItem(RecyclerView.ViewHolder holder, int itemPosition, float prevAmount,
            float amount, boolean shouldAnimate) {
        final SwipeableItemViewHolder holder2 = (SwipeableItemViewHolder) holder;
        final View containerView = holder2.getSwipeableContainerView();

        if (containerView == null) {
            return;
        }

        final int reqBackgroundType;

        if (amount == 0.0f) {
            if (prevAmount == 0.0f) {
                reqBackgroundType = DRAWABLE_SWIPE_NEUTRAL_BACKGROUND;
            } else {
                reqBackgroundType = (prevAmount < 0) ? DRAWABLE_SWIPE_LEFT_BACKGROUND
                        : DRAWABLE_SWIPE_RIGHT_BACKGROUND;
            }
        } else {
            reqBackgroundType = (amount < 0) ? DRAWABLE_SWIPE_LEFT_BACKGROUND : DRAWABLE_SWIPE_RIGHT_BACKGROUND;
        }

        if (amount == 0.0f) {
            slideItem(holder, amount, shouldAnimate);
            mAdapter.setSwipeBackgroundDrawable(holder, itemPosition, reqBackgroundType);
        } else {
            float adjustedAmount = amount;

            adjustedAmount = Math.max(adjustedAmount, holder2.getMaxLeftSwipeAmount());
            adjustedAmount = Math.min(adjustedAmount, holder2.getMaxRightSwipeAmount());

            mAdapter.setSwipeBackgroundDrawable(holder, itemPosition, reqBackgroundType);
            slideItem(holder, adjustedAmount, shouldAnimate);
        }
    }

    /*package*/ void cancelPendingAnimations(RecyclerView.ViewHolder holder) {
        if (mItemSlideAnimator != null) {
            mItemSlideAnimator.endAnimation(holder);
        }
    }

    /*package*/ int getSwipeContainerViewTranslationX(RecyclerView.ViewHolder holder) {
        return mItemSlideAnimator.getSwipeContainerViewTranslationX(holder);
    }

    private static boolean supportsViewPropertyAnimator() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
    }

    public void setAdditionItemAnimatorListener(ViewPropertyAnimatorListener listener) {
        if (mItemSlideAnimator != null) {
            mItemSlideAnimator.setAdditionAnimatorListener(listener);
        }
    }
}