ItemTouchHelper.java revision a56b4b40d7b5c30010a8c4467e13b41213a192e8
1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.support.v7.widget.helper;
18
19import android.content.res.Resources;
20import android.graphics.Canvas;
21import android.graphics.Rect;
22import android.os.Build;
23import android.support.v4.animation.AnimatorCompatHelper;
24import android.support.v4.animation.AnimatorListenerCompat;
25import android.support.v4.animation.AnimatorUpdateListenerCompat;
26import android.support.v4.animation.ValueAnimatorCompat;
27import android.support.v4.view.GestureDetectorCompat;
28import android.support.v4.view.MotionEventCompat;
29import android.support.v4.view.VelocityTrackerCompat;
30import android.support.v4.view.ViewCompat;
31import android.support.v7.recyclerview.R;
32import android.support.v7.widget.LinearLayoutManager;
33import android.support.v7.widget.RecyclerView;
34import android.support.v7.widget.RecyclerView.OnItemTouchListener;
35import android.support.v7.widget.RecyclerView.ViewHolder;
36import android.util.Log;
37import android.view.GestureDetector;
38import android.view.HapticFeedbackConstants;
39import android.view.MotionEvent;
40import android.view.VelocityTracker;
41import android.view.View;
42import android.view.ViewConfiguration;
43import android.view.ViewParent;
44import android.view.animation.Interpolator;
45
46import java.util.ArrayList;
47import java.util.List;
48
49/**
50 * This is a utility class to add swipe to dismiss and drag & drop support to RecyclerView.
51 * <p>
52 * It works with a RecyclerView and a Callback class, which configures what type of interactions
53 * are enabled and also receives events when user performs these actions.
54 * <p>
55 * Depending on which functionality you support, you should override
56 * {@link Callback#onMove(RecyclerView, ViewHolder, ViewHolder)} and / or
57 * {@link Callback#onSwiped(ViewHolder, int)}.
58 * <p>
59 * This class is designed to work with any LayoutManager but for certain situations, it can be
60 * optimized for your custom LayoutManager by extending methods in the
61 * {@link ItemTouchHelper.Callback} class or implementing {@link ItemTouchHelper.ViewDropHandler}
62 * interface in your LayoutManager.
63 * <p>
64 * By default, ItemTouchHelper moves the items' translateX/Y properties to reposition them. On
65 * platforms older than Honeycomb, ItemTouchHelper uses canvas translations and View's visibility
66 * property to move items in response to touch events. You can customize these behaviors by
67 * overriding {@link Callback#onChildDraw(Canvas, RecyclerView, ViewHolder, float, float, int,
68 * boolean)}
69 * or {@link Callback#onChildDrawOver(Canvas, RecyclerView, ViewHolder, float, float, int,
70 * boolean)}.
71 * <p/>
72 * Most of the time, you only need to override <code>onChildDraw</code> but due to limitations of
73 * platform prior to Honeycomb, you may need to implement <code>onChildDrawOver</code> as well.
74 */
75public class ItemTouchHelper extends RecyclerView.ItemDecoration
76        implements RecyclerView.OnChildAttachStateChangeListener {
77
78    /**
79     * Up direction, used for swipe & drag control.
80     */
81    public static final int UP = 1;
82
83    /**
84     * Down direction, used for swipe & drag control.
85     */
86    public static final int DOWN = 1 << 1;
87
88    /**
89     * Left direction, used for swipe & drag control.
90     */
91    public static final int LEFT = 1 << 2;
92
93    /**
94     * Right direction, used for swipe & drag control.
95     */
96    public static final int RIGHT = 1 << 3;
97
98    // If you change these relative direction values, update Callback#convertToAbsoluteDirection,
99    // Callback#convertToRelativeDirection.
100    /**
101     * Horizontal start direction. Resolved to LEFT or RIGHT depending on RecyclerView's layout
102     * direction. Used for swipe & drag control.
103     */
104    public static final int START = LEFT << 2;
105
106    /**
107     * Horizontal end direction. Resolved to LEFT or RIGHT depending on RecyclerView's layout
108     * direction. Used for swipe & drag control.
109     */
110    public static final int END = RIGHT << 2;
111
112    /**
113     * ItemTouchHelper is in idle state. At this state, either there is no related motion event by
114     * the user or latest motion events have not yet triggered a swipe or drag.
115     */
116    public static final int ACTION_STATE_IDLE = 0;
117
118    /**
119     * A View is currently being swiped.
120     */
121    public static final int ACTION_STATE_SWIPE = 1;
122
123    /**
124     * A View is currently being dragged.
125     */
126    public static final int ACTION_STATE_DRAG = 2;
127
128    /**
129     * Animation type for views which are swiped successfully.
130     */
131    public static final int ANIMATION_TYPE_SWIPE_SUCCESS = 1 << 1;
132
133    /**
134     * Animation type for views which are not completely swiped thus will animate back to their
135     * original position.
136     */
137    public static final int ANIMATION_TYPE_SWIPE_CANCEL = 1 << 2;
138
139    /**
140     * Animation type for views that were dragged and now will animate to their final position.
141     */
142    public static final int ANIMATION_TYPE_DRAG = 1 << 3;
143
144    private static final String TAG = "ItemTouchHelper";
145
146    private static final boolean DEBUG = false;
147
148    private static final int ACTIVE_POINTER_ID_NONE = -1;
149
150    private static final int DIRECTION_FLAG_COUNT = 8;
151
152    private static final int ACTION_MODE_IDLE_MASK = (1 << DIRECTION_FLAG_COUNT) - 1;
153
154    private static final int ACTION_MODE_SWIPE_MASK = ACTION_MODE_IDLE_MASK << DIRECTION_FLAG_COUNT;
155
156    private static final int ACTION_MODE_DRAG_MASK = ACTION_MODE_SWIPE_MASK << DIRECTION_FLAG_COUNT;
157
158    /**
159     * The unit we are using to track velocity
160     */
161    private static final int PIXELS_PER_SECOND = 1000;
162
163    /**
164     * Views, whose state should be cleared after they are detached from RecyclerView.
165     * This is necessary after swipe dismissing an item. We wait until animator finishes its job
166     * to clean these views.
167     */
168    final List<View> mPendingCleanup = new ArrayList<View>();
169
170    /**
171     * Re-use array to calculate dx dy for a ViewHolder
172     */
173    private final float[] mTmpPosition = new float[2];
174
175    /**
176     * Currently selected view holder
177     */
178    ViewHolder mSelected = null;
179
180    /**
181     * The reference coordinates for the action start. For drag & drop, this is the time long
182     * press is completed vs for swipe, this is the initial touch point.
183     */
184    float mInitialTouchX;
185
186    float mInitialTouchY;
187
188    /**
189     * Set when ItemTouchHelper is assigned to a RecyclerView.
190     */
191    float mSwipeEscapeVelocity;
192
193    /**
194     * Set when ItemTouchHelper is assigned to a RecyclerView.
195     */
196    float mMaxSwipeVelocity;
197
198    /**
199     * The diff between the last event and initial touch.
200     */
201    float mDx;
202
203    float mDy;
204
205    /**
206     * The coordinates of the selected view at the time it is selected. We record these values
207     * when action starts so that we can consistently position it even if LayoutManager moves the
208     * View.
209     */
210    float mSelectedStartX;
211
212    float mSelectedStartY;
213
214    /**
215     * The pointer we are tracking.
216     */
217    int mActivePointerId = ACTIVE_POINTER_ID_NONE;
218
219    /**
220     * Developer callback which controls the behavior of ItemTouchHelper.
221     */
222    Callback mCallback;
223
224    /**
225     * Current mode.
226     */
227    int mActionState = ACTION_STATE_IDLE;
228
229    /**
230     * The direction flags obtained from unmasking
231     * {@link Callback#getAbsoluteMovementFlags(RecyclerView, ViewHolder)} for the current
232     * action state.
233     */
234    int mSelectedFlags;
235
236    /**
237     * When a View is dragged or swiped and needs to go back to where it was, we create a Recover
238     * Animation and animate it to its location using this custom Animator, instead of using
239     * framework Animators.
240     * Using framework animators has the side effect of clashing with ItemAnimator, creating
241     * jumpy UIs.
242     */
243    List<RecoverAnimation> mRecoverAnimations = new ArrayList<RecoverAnimation>();
244
245    private int mSlop;
246
247    private RecyclerView mRecyclerView;
248
249    /**
250     * When user drags a view to the edge, we start scrolling the LayoutManager as long as View
251     * is partially out of bounds.
252     */
253    private final Runnable mScrollRunnable = new Runnable() {
254        @Override
255        public void run() {
256            if (mSelected != null && scrollIfNecessary()) {
257                if (mSelected != null) { //it might be lost during scrolling
258                    moveIfNecessary(mSelected);
259                }
260                mRecyclerView.removeCallbacks(mScrollRunnable);
261                ViewCompat.postOnAnimation(mRecyclerView, this);
262            }
263        }
264    };
265
266    /**
267     * Used for detecting fling swipe
268     */
269    private VelocityTracker mVelocityTracker;
270
271    //re-used list for selecting a swap target
272    private List<ViewHolder> mSwapTargets;
273
274    //re used for for sorting swap targets
275    private List<Integer> mDistances;
276
277    /**
278     * If drag & drop is supported, we use child drawing order to bring them to front.
279     */
280    private RecyclerView.ChildDrawingOrderCallback mChildDrawingOrderCallback = null;
281
282    /**
283     * This keeps a reference to the child dragged by the user. Even after user stops dragging,
284     * until view reaches its final position (end of recover animation), we keep a reference so
285     * that it can be drawn above other children.
286     */
287    private View mOverdrawChild = null;
288
289    /**
290     * We cache the position of the overdraw child to avoid recalculating it each time child
291     * position callback is called. This value is invalidated whenever a child is attached or
292     * detached.
293     */
294    private int mOverdrawChildPosition = -1;
295
296    /**
297     * Used to detect long press.
298     */
299    private GestureDetectorCompat mGestureDetector;
300
301    private final OnItemTouchListener mOnItemTouchListener
302            = new OnItemTouchListener() {
303        @Override
304        public boolean onInterceptTouchEvent(RecyclerView recyclerView, MotionEvent event) {
305            mGestureDetector.onTouchEvent(event);
306            if (DEBUG) {
307                Log.d(TAG, "intercept: x:" + event.getX() + ",y:" + event.getY() + ", " + event);
308            }
309            final int action = MotionEventCompat.getActionMasked(event);
310            if (action == MotionEvent.ACTION_DOWN) {
311                mActivePointerId = MotionEventCompat.getPointerId(event, 0);
312                mInitialTouchX = event.getX();
313                mInitialTouchY = event.getY();
314                obtainVelocityTracker();
315                if (mSelected == null) {
316                    final RecoverAnimation animation = findAnimation(event);
317                    if (animation != null) {
318                        mInitialTouchX -= animation.mX;
319                        mInitialTouchY -= animation.mY;
320                        endRecoverAnimation(animation.mViewHolder, true);
321                        if (mPendingCleanup.remove(animation.mViewHolder.itemView)) {
322                            mCallback.clearView(mRecyclerView, animation.mViewHolder);
323                        }
324                        select(animation.mViewHolder, animation.mActionState);
325                        updateDxDy(event, mSelectedFlags, 0);
326                    }
327                }
328            } else if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
329                mActivePointerId = ACTIVE_POINTER_ID_NONE;
330                select(null, ACTION_STATE_IDLE);
331            } else if (mActivePointerId != ACTIVE_POINTER_ID_NONE) {
332                // in a non scroll orientation, if distance change is above threshold, we
333                // can select the item
334                final int index = MotionEventCompat.findPointerIndex(event, mActivePointerId);
335                if (DEBUG) {
336                    Log.d(TAG, "pointer index " + index);
337                }
338                if (index >= 0) {
339                    checkSelectForSwipe(action, event, index);
340                }
341            }
342            if (mVelocityTracker != null) {
343                mVelocityTracker.addMovement(event);
344            }
345            return mSelected != null;
346        }
347
348        @Override
349        public void onTouchEvent(RecyclerView recyclerView, MotionEvent event) {
350            mGestureDetector.onTouchEvent(event);
351            if (DEBUG) {
352                Log.d(TAG,
353                        "on touch: x:" + mInitialTouchX + ",y:" + mInitialTouchY + ", :" + event);
354            }
355            if (mVelocityTracker != null) {
356                mVelocityTracker.addMovement(event);
357            }
358            if (mActivePointerId == ACTIVE_POINTER_ID_NONE) {
359                return;
360            }
361            final int action = MotionEventCompat.getActionMasked(event);
362            final int activePointerIndex = MotionEventCompat
363                    .findPointerIndex(event, mActivePointerId);
364            if (activePointerIndex >= 0) {
365                checkSelectForSwipe(action, event, activePointerIndex);
366            }
367            ViewHolder viewHolder = mSelected;
368            if (viewHolder == null) {
369                return;
370            }
371            switch (action) {
372                case MotionEvent.ACTION_MOVE: {
373                    // Find the index of the active pointer and fetch its position
374                    if (activePointerIndex >= 0) {
375                        updateDxDy(event, mSelectedFlags, activePointerIndex);
376                        moveIfNecessary(viewHolder);
377                        mRecyclerView.removeCallbacks(mScrollRunnable);
378                        mScrollRunnable.run();
379                        mRecyclerView.invalidate();
380                    }
381                    break;
382                }
383                case MotionEvent.ACTION_CANCEL:
384                    if (mVelocityTracker != null) {
385                        mVelocityTracker.clear();
386                    }
387                    // fall through
388                case MotionEvent.ACTION_UP:
389                    select(null, ACTION_STATE_IDLE);
390                    mActivePointerId = ACTIVE_POINTER_ID_NONE;
391                    break;
392                case MotionEvent.ACTION_POINTER_UP: {
393                    final int pointerIndex = MotionEventCompat.getActionIndex(event);
394                    final int pointerId = MotionEventCompat.getPointerId(event, pointerIndex);
395                    if (pointerId == mActivePointerId) {
396                        // This was our active pointer going up. Choose a new
397                        // active pointer and adjust accordingly.
398                        final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
399                        mActivePointerId = MotionEventCompat.getPointerId(event, newPointerIndex);
400                        updateDxDy(event, mSelectedFlags, pointerIndex);
401                    }
402                    break;
403                }
404            }
405        }
406
407        @Override
408        public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
409            if (!disallowIntercept) {
410                return;
411            }
412            select(null, ACTION_STATE_IDLE);
413        }
414    };
415
416    /**
417     * Temporary rect instance that is used when we need to lookup Item decorations.
418     */
419    private Rect mTmpRect;
420
421    /**
422     * When user started to drag scroll. Reset when we don't scroll
423     */
424    private long mDragScrollStartTimeInMs;
425
426    /**
427     * Creates an ItemTouchHelper that will work with the given Callback.
428     * <p>
429     * You can attach ItemTouchHelper to a RecyclerView via
430     * {@link #attachToRecyclerView(RecyclerView)}. Upon attaching, it will add an item decoration,
431     * an onItemTouchListener and a Child attach / detach listener to the RecyclerView.
432     *
433     * @param callback The Callback which controls the behavior of this touch helper.
434     */
435    public ItemTouchHelper(Callback callback) {
436        mCallback = callback;
437    }
438
439    private static boolean hitTest(View child, float x, float y, float left, float top) {
440        return x >= left &&
441                x <= left + child.getWidth() &&
442                y >= top &&
443                y <= top + child.getHeight();
444    }
445
446    /**
447     * Attaches the ItemTouchHelper to the provided RecyclerView. If TouchHelper is already
448     * attached
449     * to a RecyclerView, it will first detach from the previous one.
450     *
451     * @param recyclerView The RecyclerView instance to which you want to add this helper.
452     */
453    public void attachToRecyclerView(RecyclerView recyclerView) {
454        if (mRecyclerView == recyclerView) {
455            return; // nothing to do
456        }
457        if (mRecyclerView != null) {
458            destroyCallbacks();
459        }
460        mRecyclerView = recyclerView;
461        if (mRecyclerView != null) {
462            final Resources resources = recyclerView.getResources();
463            mSwipeEscapeVelocity = resources
464                    .getDimension(R.dimen.item_touch_helper_swipe_escape_velocity);
465            mMaxSwipeVelocity = resources
466                    .getDimension(R.dimen.item_touch_helper_swipe_escape_max_velocity);
467            setupCallbacks();
468        }
469    }
470
471    private void setupCallbacks() {
472        ViewConfiguration vc = ViewConfiguration.get(mRecyclerView.getContext());
473        mSlop = vc.getScaledTouchSlop();
474        mRecyclerView.addItemDecoration(this);
475        mRecyclerView.addOnItemTouchListener(mOnItemTouchListener);
476        mRecyclerView.addOnChildAttachStateChangeListener(this);
477        initGestureDetector();
478    }
479
480    private void destroyCallbacks() {
481        mRecyclerView.removeItemDecoration(this);
482        mRecyclerView.removeOnItemTouchListener(mOnItemTouchListener);
483        mRecyclerView.removeOnChildAttachStateChangeListener(this);
484        // clean all attached
485        final int recoverAnimSize = mRecoverAnimations.size();
486        for (int i = recoverAnimSize - 1; i >= 0; i--) {
487            final RecoverAnimation recoverAnimation = mRecoverAnimations.get(0);
488            mCallback.clearView(mRecyclerView, recoverAnimation.mViewHolder);
489        }
490        mRecoverAnimations.clear();
491        mOverdrawChild = null;
492        mOverdrawChildPosition = -1;
493        releaseVelocityTracker();
494    }
495
496    private void initGestureDetector() {
497        if (mGestureDetector != null) {
498            return;
499        }
500        mGestureDetector = new GestureDetectorCompat(mRecyclerView.getContext(),
501                new ItemTouchHelperGestureListener());
502    }
503
504    private void getSelectedDxDy(float[] outPosition) {
505        if ((mSelectedFlags & (LEFT | RIGHT)) != 0) {
506            outPosition[0] = mSelectedStartX + mDx - mSelected.itemView.getLeft();
507        } else {
508            outPosition[0] = ViewCompat.getTranslationX(mSelected.itemView);
509        }
510        if ((mSelectedFlags & (UP | DOWN)) != 0) {
511            outPosition[1] = mSelectedStartY + mDy - mSelected.itemView.getTop();
512        } else {
513            outPosition[1] = ViewCompat.getTranslationY(mSelected.itemView);
514        }
515    }
516
517    @Override
518    public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
519        float dx = 0, dy = 0;
520        if (mSelected != null) {
521            getSelectedDxDy(mTmpPosition);
522            dx = mTmpPosition[0];
523            dy = mTmpPosition[1];
524        }
525        mCallback.onDrawOver(c, parent, mSelected,
526                mRecoverAnimations, mActionState, dx, dy);
527    }
528
529    @Override
530    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
531        // we don't know if RV changed something so we should invalidate this index.
532        mOverdrawChildPosition = -1;
533        float dx = 0, dy = 0;
534        if (mSelected != null) {
535            getSelectedDxDy(mTmpPosition);
536            dx = mTmpPosition[0];
537            dy = mTmpPosition[1];
538        }
539        mCallback.onDraw(c, parent, mSelected,
540                mRecoverAnimations, mActionState, dx, dy);
541    }
542
543    /**
544     * Starts dragging or swiping the given View. Call with null if you want to clear it.
545     *
546     * @param selected    The ViewHolder to drag or swipe. Can be null if you want to cancel the
547     *                    current action
548     * @param actionState The type of action
549     */
550    private void select(ViewHolder selected, int actionState) {
551        if (selected == mSelected && actionState == mActionState) {
552            return;
553        }
554        mDragScrollStartTimeInMs = Long.MIN_VALUE;
555        final int prevActionState = mActionState;
556        // prevent duplicate animations
557        endRecoverAnimation(selected, true);
558        mActionState = actionState;
559        if (actionState == ACTION_STATE_DRAG) {
560            // we remove after animation is complete. this means we only elevate the last drag
561            // child but that should perform good enough as it is very hard to start dragging a
562            // new child before the previous one settles.
563            mOverdrawChild = selected.itemView;
564            addChildDrawingOrderCallback();
565        }
566        int actionStateMask = (1 << (DIRECTION_FLAG_COUNT + DIRECTION_FLAG_COUNT * actionState))
567                - 1;
568        boolean preventLayout = false;
569
570        if (mSelected != null) {
571            final ViewHolder prevSelected = mSelected;
572            if (prevSelected.itemView.getParent() != null) {
573                final int swipeDir = prevActionState == ACTION_STATE_DRAG ? 0
574                        : swipeIfNecessary(prevSelected);
575                releaseVelocityTracker();
576                // find where we should animate to
577                final float targetTranslateX, targetTranslateY;
578                int animationType;
579                switch (swipeDir) {
580                    case LEFT:
581                    case RIGHT:
582                    case START:
583                    case END:
584                        targetTranslateY = 0;
585                        targetTranslateX = Math.signum(mDx) * mRecyclerView.getWidth();
586                        break;
587                    case UP:
588                    case DOWN:
589                        targetTranslateX = 0;
590                        targetTranslateY = Math.signum(mDy) * mRecyclerView.getHeight();
591                        break;
592                    default:
593                        targetTranslateX = 0;
594                        targetTranslateY = 0;
595                }
596                if (prevActionState == ACTION_STATE_DRAG) {
597                    animationType = ANIMATION_TYPE_DRAG;
598                } else if (swipeDir > 0) {
599                    animationType = ANIMATION_TYPE_SWIPE_SUCCESS;
600                } else {
601                    animationType = ANIMATION_TYPE_SWIPE_CANCEL;
602                }
603                getSelectedDxDy(mTmpPosition);
604                final float currentTranslateX = mTmpPosition[0];
605                final float currentTranslateY = mTmpPosition[1];
606                final RecoverAnimation rv = new RecoverAnimation(prevSelected, animationType,
607                        prevActionState, currentTranslateX, currentTranslateY,
608                        targetTranslateX, targetTranslateY) {
609                    @Override
610                    public void onAnimationEnd(ValueAnimatorCompat animation) {
611                        super.onAnimationEnd(animation);
612                        if (this.mOverridden) {
613                            return;
614                        }
615                        if (swipeDir <= 0) {
616                            // this is a drag or failed swipe. recover immediately
617                            mCallback.clearView(mRecyclerView, prevSelected);
618                            // full cleanup will happen on onDrawOver
619                        } else {
620                            // wait until remove animation is complete.
621                            mPendingCleanup.add(prevSelected.itemView);
622                            mIsPendingCleanup = true;
623                            if (swipeDir > 0) {
624                                // Animation might be ended by other animators during a layout.
625                                // We defer callback to avoid editing adapter during a layout.
626                                postDispatchSwipe(this, swipeDir);
627                            }
628                        }
629                        // removed from the list after it is drawn for the last time
630                        if (mOverdrawChild == prevSelected.itemView) {
631                            removeChildDrawingOrderCallbackIfNecessary(prevSelected.itemView);
632                        }
633                    }
634                };
635                final long duration = mCallback.getAnimationDuration(mRecyclerView, animationType,
636                        targetTranslateX - currentTranslateX, targetTranslateY - currentTranslateY);
637                rv.setDuration(duration);
638                mRecoverAnimations.add(rv);
639                rv.start();
640                preventLayout = true;
641            } else {
642                removeChildDrawingOrderCallbackIfNecessary(prevSelected.itemView);
643                mCallback.clearView(mRecyclerView, prevSelected);
644            }
645            mSelected = null;
646        }
647        if (selected != null) {
648            mSelectedFlags =
649                    (mCallback.getAbsoluteMovementFlags(mRecyclerView, selected) & actionStateMask)
650                            >> (mActionState * DIRECTION_FLAG_COUNT);
651            mSelectedStartX = selected.itemView.getLeft();
652            mSelectedStartY = selected.itemView.getTop();
653            mSelected = selected;
654
655            if (actionState == ACTION_STATE_DRAG) {
656                mSelected.itemView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
657            }
658        }
659        final ViewParent rvParent = mRecyclerView.getParent();
660        if (rvParent != null) {
661            rvParent.requestDisallowInterceptTouchEvent(mSelected != null);
662        }
663        if (!preventLayout) {
664            mRecyclerView.getLayoutManager().requestSimpleAnimationsInNextLayout();
665        }
666        mCallback.onSelectedChanged(mSelected, mActionState);
667        mRecyclerView.invalidate();
668    }
669
670    private void postDispatchSwipe(final RecoverAnimation anim, final int swipeDir) {
671        // wait until animations are complete.
672        mRecyclerView.post(new Runnable() {
673            @Override
674            public void run() {
675                if (mRecyclerView != null && mRecyclerView.isAttachedToWindow() &&
676                        !anim.mOverridden &&
677                        anim.mViewHolder.getAdapterPosition() != RecyclerView.NO_POSITION) {
678                    final RecyclerView.ItemAnimator animator = mRecyclerView.getItemAnimator();
679                    // if animator is running or we have other active recover animations, we try
680                    // not to call onSwiped because DefaultItemAnimator is not good at merging
681                    // animations. Instead, we wait and batch.
682                    if ((animator == null || !animator.isRunning(null))
683                            && !hasRunningRecoverAnim()) {
684                        mCallback.onSwiped(anim.mViewHolder, swipeDir);
685                    } else {
686                        mRecyclerView.post(this);
687                    }
688                }
689            }
690        });
691    }
692
693    private boolean hasRunningRecoverAnim() {
694        final int size = mRecoverAnimations.size();
695        for (int i = 0; i < size; i++) {
696            if (!mRecoverAnimations.get(i).mEnded) {
697                return true;
698            }
699        }
700        return false;
701    }
702
703    /**
704     * If user drags the view to the edge, trigger a scroll if necessary.
705     */
706    private boolean scrollIfNecessary() {
707        if (mSelected == null) {
708            mDragScrollStartTimeInMs = Long.MIN_VALUE;
709            return false;
710        }
711        final long now = System.currentTimeMillis();
712        final long scrollDuration = mDragScrollStartTimeInMs
713                == Long.MIN_VALUE ? 0 : now - mDragScrollStartTimeInMs;
714        RecyclerView.LayoutManager lm = mRecyclerView.getLayoutManager();
715        if (mTmpRect == null) {
716            mTmpRect = new Rect();
717        }
718        int scrollX = 0;
719        int scrollY = 0;
720        lm.calculateItemDecorationsForChild(mSelected.itemView, mTmpRect);
721        if (lm.canScrollHorizontally()) {
722            int curX = (int) (mSelectedStartX + mDx);
723            final int leftDiff = curX - mTmpRect.left - mRecyclerView.getPaddingLeft();
724            if (mDx < 0 && leftDiff < 0) {
725                scrollX = leftDiff;
726            } else if (mDx > 0) {
727                final int rightDiff =
728                        curX + mSelected.itemView.getWidth() + mTmpRect.right
729                                - (mRecyclerView.getWidth() - mRecyclerView.getPaddingRight());
730                if (rightDiff > 0) {
731                    scrollX = rightDiff;
732                }
733            }
734        }
735        if (lm.canScrollVertically()) {
736            int curY = (int) (mSelectedStartY + mDy);
737            final int topDiff = curY - mTmpRect.top - mRecyclerView.getPaddingTop();
738            if (mDy < 0 && topDiff < 0) {
739                scrollY = topDiff;
740            } else if (mDy > 0) {
741                final int bottomDiff = curY + mSelected.itemView.getHeight() + mTmpRect.bottom -
742                        (mRecyclerView.getHeight() - mRecyclerView.getPaddingBottom());
743                if (bottomDiff > 0) {
744                    scrollY = bottomDiff;
745                }
746            }
747        }
748        if (scrollX != 0) {
749            scrollX = mCallback.interpolateOutOfBoundsScroll(mRecyclerView,
750                    mSelected.itemView.getWidth(), scrollX,
751                    mRecyclerView.getWidth(), scrollDuration);
752        }
753        if (scrollY != 0) {
754            scrollY = mCallback.interpolateOutOfBoundsScroll(mRecyclerView,
755                    mSelected.itemView.getHeight(), scrollY,
756                    mRecyclerView.getHeight(), scrollDuration);
757        }
758        if (scrollX != 0 || scrollY != 0) {
759            if (mDragScrollStartTimeInMs == Long.MIN_VALUE) {
760                mDragScrollStartTimeInMs = now;
761            }
762            mRecyclerView.scrollBy(scrollX, scrollY);
763            return true;
764        }
765        mDragScrollStartTimeInMs = Long.MIN_VALUE;
766        return false;
767    }
768
769    private List<ViewHolder> findSwapTargets(ViewHolder viewHolder) {
770        if (mSwapTargets == null) {
771            mSwapTargets = new ArrayList<ViewHolder>();
772            mDistances = new ArrayList<Integer>();
773        } else {
774            mSwapTargets.clear();
775            mDistances.clear();
776        }
777        final int margin = mCallback.getBoundingBoxMargin();
778        final int left = Math.round(mSelectedStartX + mDx) - margin;
779        final int top = Math.round(mSelectedStartY + mDy) - margin;
780        final int right = left + viewHolder.itemView.getWidth() + 2 * margin;
781        final int bottom = top + viewHolder.itemView.getHeight() + 2 * margin;
782        final int centerX = (left + right) / 2;
783        final int centerY = (top + bottom) / 2;
784        final RecyclerView.LayoutManager lm = mRecyclerView.getLayoutManager();
785        final int childCount = lm.getChildCount();
786        for (int i = 0; i < childCount; i++) {
787            View other = lm.getChildAt(i);
788            if (other == viewHolder.itemView) {
789                continue;//myself!
790            }
791            if (other.getBottom() < top || other.getTop() > bottom
792                    || other.getRight() < left || other.getLeft() > right) {
793                continue;
794            }
795            final ViewHolder otherVh = mRecyclerView.getChildViewHolder(other);
796            if (mCallback.canDropOver(mRecyclerView, mSelected, otherVh)) {
797                // find the index to add
798                final int dx = Math.abs(centerX - (other.getLeft() + other.getRight()) / 2);
799                final int dy = Math.abs(centerY - (other.getTop() + other.getBottom()) / 2);
800                final int dist = dx * dx + dy * dy;
801
802                int pos = 0;
803                final int cnt = mSwapTargets.size();
804                for (int j = 0; j < cnt; j++) {
805                    if (dist > mDistances.get(j)) {
806                        pos++;
807                    } else {
808                        break;
809                    }
810                }
811                mSwapTargets.add(pos, otherVh);
812                mDistances.add(pos, dist);
813            }
814        }
815        return mSwapTargets;
816    }
817
818    /**
819     * Checks if we should swap w/ another view holder.
820     */
821    private void moveIfNecessary(ViewHolder viewHolder) {
822        if (mRecyclerView.isLayoutRequested()) {
823            return;
824        }
825        if (mActionState != ACTION_STATE_DRAG) {
826            return;
827        }
828
829        final float threshold = mCallback.getMoveThreshold(viewHolder);
830        final int x = (int) (mSelectedStartX + mDx);
831        final int y = (int) (mSelectedStartY + mDy);
832        if (Math.abs(y - viewHolder.itemView.getTop()) < viewHolder.itemView.getHeight() * threshold
833                && Math.abs(x - viewHolder.itemView.getLeft())
834                < viewHolder.itemView.getWidth() * threshold) {
835            return;
836        }
837        List<ViewHolder> swapTargets = findSwapTargets(viewHolder);
838        if (swapTargets.size() == 0) {
839            return;
840        }
841        // may swap.
842        ViewHolder target = mCallback.chooseDropTarget(viewHolder, swapTargets, x, y);
843        if (target == null) {
844            mSwapTargets.clear();
845            mDistances.clear();
846            return;
847        }
848        final int toPosition = target.getAdapterPosition();
849        final int fromPosition = viewHolder.getAdapterPosition();
850        if (mCallback.onMove(mRecyclerView, viewHolder, target)) {
851            // keep target visible
852            mCallback.onMoved(mRecyclerView, viewHolder, fromPosition,
853                    target, toPosition, x, y);
854        }
855    }
856
857    @Override
858    public void onChildViewAttachedToWindow(View view) {
859    }
860
861    @Override
862    public void onChildViewDetachedFromWindow(View view) {
863        removeChildDrawingOrderCallbackIfNecessary(view);
864        final ViewHolder holder = mRecyclerView.getChildViewHolder(view);
865        if (holder == null) {
866            return;
867        }
868        if (mSelected != null && holder == mSelected) {
869            select(null, ACTION_STATE_IDLE);
870        } else {
871            endRecoverAnimation(holder, false); // this may push it into pending cleanup list.
872            if (mPendingCleanup.remove(holder.itemView)) {
873                mCallback.clearView(mRecyclerView, holder);
874            }
875        }
876    }
877
878    /**
879     * Returns the animation type or 0 if cannot be found.
880     */
881    private int endRecoverAnimation(ViewHolder viewHolder, boolean override) {
882        final int recoverAnimSize = mRecoverAnimations.size();
883        for (int i = recoverAnimSize - 1; i >= 0; i--) {
884            final RecoverAnimation anim = mRecoverAnimations.get(i);
885            if (anim.mViewHolder == viewHolder) {
886                anim.mOverridden |= override;
887                if (!anim.mEnded) {
888                    anim.cancel();
889                }
890                mRecoverAnimations.remove(i);
891                return anim.mAnimationType;
892            }
893        }
894        return 0;
895    }
896
897    @Override
898    public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
899            RecyclerView.State state) {
900        outRect.setEmpty();
901    }
902
903    private void obtainVelocityTracker() {
904        if (mVelocityTracker != null) {
905            mVelocityTracker.recycle();
906        }
907        mVelocityTracker = VelocityTracker.obtain();
908    }
909
910    private void releaseVelocityTracker() {
911        if (mVelocityTracker != null) {
912            mVelocityTracker.recycle();
913            mVelocityTracker = null;
914        }
915    }
916
917    private ViewHolder findSwipedView(MotionEvent motionEvent) {
918        final RecyclerView.LayoutManager lm = mRecyclerView.getLayoutManager();
919        if (mActivePointerId == ACTIVE_POINTER_ID_NONE) {
920            return null;
921        }
922        final int pointerIndex = MotionEventCompat.findPointerIndex(motionEvent, mActivePointerId);
923        final float dx = MotionEventCompat.getX(motionEvent, pointerIndex) - mInitialTouchX;
924        final float dy = MotionEventCompat.getY(motionEvent, pointerIndex) - mInitialTouchY;
925        final float absDx = Math.abs(dx);
926        final float absDy = Math.abs(dy);
927
928        if (absDx < mSlop && absDy < mSlop) {
929            return null;
930        }
931        if (absDx > absDy && lm.canScrollHorizontally()) {
932            return null;
933        } else if (absDy > absDx && lm.canScrollVertically()) {
934            return null;
935        }
936        View child = findChildView(motionEvent);
937        if (child == null) {
938            return null;
939        }
940        return mRecyclerView.getChildViewHolder(child);
941    }
942
943    /**
944     * Checks whether we should select a View for swiping.
945     */
946    private boolean checkSelectForSwipe(int action, MotionEvent motionEvent, int pointerIndex) {
947        if (mSelected != null || action != MotionEvent.ACTION_MOVE
948                || mActionState == ACTION_STATE_DRAG || !mCallback.isItemViewSwipeEnabled()) {
949            return false;
950        }
951        if (mRecyclerView.getScrollState() == RecyclerView.SCROLL_STATE_DRAGGING) {
952            return false;
953        }
954        final ViewHolder vh = findSwipedView(motionEvent);
955        if (vh == null) {
956            return false;
957        }
958        final int movementFlags = mCallback.getAbsoluteMovementFlags(mRecyclerView, vh);
959
960        final int swipeFlags = (movementFlags & ACTION_MODE_SWIPE_MASK)
961                >> (DIRECTION_FLAG_COUNT * ACTION_STATE_SWIPE);
962
963        if (swipeFlags == 0) {
964            return false;
965        }
966
967        // mDx and mDy are only set in allowed directions. We use custom x/y here instead of
968        // updateDxDy to avoid swiping if user moves more in the other direction
969        final float x = MotionEventCompat.getX(motionEvent, pointerIndex);
970        final float y = MotionEventCompat.getY(motionEvent, pointerIndex);
971
972        // Calculate the distance moved
973        final float dx = x - mInitialTouchX;
974        final float dy = y - mInitialTouchY;
975        // swipe target is chose w/o applying flags so it does not really check if swiping in that
976        // direction is allowed. This why here, we use mDx mDy to check slope value again.
977        final float absDx = Math.abs(dx);
978        final float absDy = Math.abs(dy);
979
980        if (absDx < mSlop && absDy < mSlop) {
981            return false;
982        }
983        if (absDx > absDy) {
984            if (dx < 0 && (swipeFlags & LEFT) == 0) {
985                return false;
986            }
987            if (dx > 0 && (swipeFlags & RIGHT) == 0) {
988                return false;
989            }
990        } else {
991            if (dy < 0 && (swipeFlags & UP) == 0) {
992                return false;
993            }
994            if (dy > 0 && (swipeFlags & DOWN) == 0) {
995                return false;
996            }
997        }
998        mDx = mDy = 0f;
999        mActivePointerId = MotionEventCompat.getPointerId(motionEvent, 0);
1000        select(vh, ACTION_STATE_SWIPE);
1001        return true;
1002    }
1003
1004    private View findChildView(MotionEvent event) {
1005        // first check elevated views, if none, then call RV
1006        final float x = event.getX();
1007        final float y = event.getY();
1008        if (mSelected != null) {
1009            final View selectedView = mSelected.itemView;
1010            if (hitTest(selectedView, x, y, mSelectedStartX + mDx, mSelectedStartY + mDy)) {
1011                return selectedView;
1012            }
1013        }
1014        for (int i = mRecoverAnimations.size() - 1; i >= 0; i--) {
1015            final RecoverAnimation anim = mRecoverAnimations.get(i);
1016            final View view = anim.mViewHolder.itemView;
1017            if (hitTest(view, x, y, anim.mX, anim.mY)) {
1018                return view;
1019            }
1020        }
1021        return mRecyclerView.findChildViewUnder(x, y);
1022    }
1023
1024    /**
1025     * Starts dragging the provided ViewHolder. By default, ItemTouchHelper starts a drag when a
1026     * View is long pressed. You can disable that behavior by overriding
1027     * {@link ItemTouchHelper.Callback#isLongPressDragEnabled()}.
1028     * <p>
1029     * For this method to work:
1030     * <ul>
1031     * <li>The provided ViewHolder must be a child of the RecyclerView to which this
1032     * ItemTouchHelper
1033     * is attached.</li>
1034     * <li>{@link ItemTouchHelper.Callback} must have dragging enabled.</li>
1035     * <li>There must be a previous touch event that was reported to the ItemTouchHelper
1036     * through RecyclerView's ItemTouchListener mechanism. As long as no other ItemTouchListener
1037     * grabs previous events, this should work as expected.</li>
1038     * </ul>
1039     *
1040     * For example, if you would like to let your user to be able to drag an Item by touching one
1041     * of its descendants, you may implement it as follows:
1042     * <pre>
1043     *     viewHolder.dragButton.setOnTouchListener(new View.OnTouchListener() {
1044     *         public boolean onTouch(View v, MotionEvent event) {
1045     *             if (MotionEventCompat.getActionMasked(event) == MotionEvent.ACTION_DOWN) {
1046     *                 mItemTouchHelper.startDrag(viewHolder);
1047     *             }
1048     *             return false;
1049     *         }
1050     *     });
1051     * </pre>
1052     * <p>
1053     *
1054     * @param viewHolder The ViewHolder to start dragging. It must be a direct child of
1055     *                   RecyclerView.
1056     * @see ItemTouchHelper.Callback#isItemViewSwipeEnabled()
1057     */
1058    public void startDrag(ViewHolder viewHolder) {
1059        if (!mCallback.hasDragFlag(mRecyclerView, viewHolder)) {
1060            Log.e(TAG, "Start drag has been called but swiping is not enabled");
1061            return;
1062        }
1063        if (viewHolder.itemView.getParent() != mRecyclerView) {
1064            Log.e(TAG, "Start drag has been called with a view holder which is not a child of "
1065                    + "the RecyclerView which is controlled by this ItemTouchHelper.");
1066            return;
1067        }
1068        obtainVelocityTracker();
1069        mDx = mDy = 0f;
1070        select(viewHolder, ACTION_STATE_DRAG);
1071    }
1072
1073    /**
1074     * Starts swiping the provided ViewHolder. By default, ItemTouchHelper starts swiping a View
1075     * when user swipes their finger (or mouse pointer) over the View. You can disable this
1076     * behavior
1077     * by overriding {@link ItemTouchHelper.Callback}
1078     * <p>
1079     * For this method to work:
1080     * <ul>
1081     * <li>The provided ViewHolder must be a child of the RecyclerView to which this
1082     * ItemTouchHelper is attached.</li>
1083     * <li>{@link ItemTouchHelper.Callback} must have swiping enabled.</li>
1084     * <li>There must be a previous touch event that was reported to the ItemTouchHelper
1085     * through RecyclerView's ItemTouchListener mechanism. As long as no other ItemTouchListener
1086     * grabs previous events, this should work as expected.</li>
1087     * </ul>
1088     *
1089     * For example, if you would like to let your user to be able to swipe an Item by touching one
1090     * of its descendants, you may implement it as follows:
1091     * <pre>
1092     *     viewHolder.dragButton.setOnTouchListener(new View.OnTouchListener() {
1093     *         public boolean onTouch(View v, MotionEvent event) {
1094     *             if (MotionEventCompat.getActionMasked(event) == MotionEvent.ACTION_DOWN) {
1095     *                 mItemTouchHelper.startSwipe(viewHolder);
1096     *             }
1097     *             return false;
1098     *         }
1099     *     });
1100     * </pre>
1101     *
1102     * @param viewHolder The ViewHolder to start swiping. It must be a direct child of
1103     *                   RecyclerView.
1104     */
1105    public void startSwipe(ViewHolder viewHolder) {
1106        if (!mCallback.hasSwipeFlag(mRecyclerView, viewHolder)) {
1107            Log.e(TAG, "Start swipe has been called but dragging is not enabled");
1108            return;
1109        }
1110        if (viewHolder.itemView.getParent() != mRecyclerView) {
1111            Log.e(TAG, "Start swipe has been called with a view holder which is not a child of "
1112                    + "the RecyclerView controlled by this ItemTouchHelper.");
1113            return;
1114        }
1115        obtainVelocityTracker();
1116        mDx = mDy = 0f;
1117        select(viewHolder, ACTION_STATE_SWIPE);
1118    }
1119
1120    private RecoverAnimation findAnimation(MotionEvent event) {
1121        if (mRecoverAnimations.isEmpty()) {
1122            return null;
1123        }
1124        View target = findChildView(event);
1125        for (int i = mRecoverAnimations.size() - 1; i >= 0; i--) {
1126            final RecoverAnimation anim = mRecoverAnimations.get(i);
1127            if (anim.mViewHolder.itemView == target) {
1128                return anim;
1129            }
1130        }
1131        return null;
1132    }
1133
1134    private void updateDxDy(MotionEvent ev, int directionFlags, int pointerIndex) {
1135        final float x = MotionEventCompat.getX(ev, pointerIndex);
1136        final float y = MotionEventCompat.getY(ev, pointerIndex);
1137
1138        // Calculate the distance moved
1139        mDx = x - mInitialTouchX;
1140        mDy = y - mInitialTouchY;
1141        if ((directionFlags & LEFT) == 0) {
1142            mDx = Math.max(0, mDx);
1143        }
1144        if ((directionFlags & RIGHT) == 0) {
1145            mDx = Math.min(0, mDx);
1146        }
1147        if ((directionFlags & UP) == 0) {
1148            mDy = Math.max(0, mDy);
1149        }
1150        if ((directionFlags & DOWN) == 0) {
1151            mDy = Math.min(0, mDy);
1152        }
1153    }
1154
1155    private int swipeIfNecessary(ViewHolder viewHolder) {
1156        if (mActionState == ACTION_STATE_DRAG) {
1157            return 0;
1158        }
1159        final int originalMovementFlags = mCallback.getMovementFlags(mRecyclerView, viewHolder);
1160        final int absoluteMovementFlags = mCallback.convertToAbsoluteDirection(
1161                originalMovementFlags,
1162                ViewCompat.getLayoutDirection(mRecyclerView));
1163        final int flags = (absoluteMovementFlags
1164                & ACTION_MODE_SWIPE_MASK) >> (ACTION_STATE_SWIPE * DIRECTION_FLAG_COUNT);
1165        if (flags == 0) {
1166            return 0;
1167        }
1168        final int originalFlags = (originalMovementFlags
1169                & ACTION_MODE_SWIPE_MASK) >> (ACTION_STATE_SWIPE * DIRECTION_FLAG_COUNT);
1170        int swipeDir;
1171        if (Math.abs(mDx) > Math.abs(mDy)) {
1172            if ((swipeDir = checkHorizontalSwipe(viewHolder, flags)) > 0) {
1173                // if swipe dir is not in original flags, it should be the relative direction
1174                if ((originalFlags & swipeDir) == 0) {
1175                    // convert to relative
1176                    return Callback.convertToRelativeDirection(swipeDir,
1177                            ViewCompat.getLayoutDirection(mRecyclerView));
1178                }
1179                return swipeDir;
1180            }
1181            if ((swipeDir = checkVerticalSwipe(viewHolder, flags)) > 0) {
1182                return swipeDir;
1183            }
1184        } else {
1185            if ((swipeDir = checkVerticalSwipe(viewHolder, flags)) > 0) {
1186                return swipeDir;
1187            }
1188            if ((swipeDir = checkHorizontalSwipe(viewHolder, flags)) > 0) {
1189                // if swipe dir is not in original flags, it should be the relative direction
1190                if ((originalFlags & swipeDir) == 0) {
1191                    // convert to relative
1192                    return Callback.convertToRelativeDirection(swipeDir,
1193                            ViewCompat.getLayoutDirection(mRecyclerView));
1194                }
1195                return swipeDir;
1196            }
1197        }
1198        return 0;
1199    }
1200
1201    private int checkHorizontalSwipe(ViewHolder viewHolder, int flags) {
1202        if ((flags & (LEFT | RIGHT)) != 0) {
1203            final int dirFlag = mDx > 0 ? RIGHT : LEFT;
1204            if (mVelocityTracker != null && mActivePointerId > -1) {
1205                mVelocityTracker.computeCurrentVelocity(PIXELS_PER_SECOND,
1206                        mCallback.getSwipeVelocityThreshold(mMaxSwipeVelocity));
1207                final float xVelocity = VelocityTrackerCompat
1208                        .getXVelocity(mVelocityTracker, mActivePointerId);
1209                final float yVelocity = VelocityTrackerCompat
1210                        .getYVelocity(mVelocityTracker, mActivePointerId);
1211                final int velDirFlag = xVelocity > 0f ? RIGHT : LEFT;
1212                final float absXVelocity = Math.abs(xVelocity);
1213                if ((velDirFlag & flags) != 0 && dirFlag == velDirFlag &&
1214                        absXVelocity >= mCallback.getSwipeEscapeVelocity(mSwipeEscapeVelocity) &&
1215                        absXVelocity > Math.abs(yVelocity)) {
1216                    return velDirFlag;
1217                }
1218            }
1219
1220            final float threshold = mRecyclerView.getWidth() * mCallback
1221                    .getSwipeThreshold(viewHolder);
1222
1223            if ((flags & dirFlag) != 0 && Math.abs(mDx) > threshold) {
1224                return dirFlag;
1225            }
1226        }
1227        return 0;
1228    }
1229
1230    private int checkVerticalSwipe(ViewHolder viewHolder, int flags) {
1231        if ((flags & (UP | DOWN)) != 0) {
1232            final int dirFlag = mDy > 0 ? DOWN : UP;
1233            if (mVelocityTracker != null && mActivePointerId > -1) {
1234                mVelocityTracker.computeCurrentVelocity(PIXELS_PER_SECOND,
1235                        mCallback.getSwipeVelocityThreshold(mMaxSwipeVelocity));
1236                final float xVelocity = VelocityTrackerCompat
1237                        .getXVelocity(mVelocityTracker, mActivePointerId);
1238                final float yVelocity = VelocityTrackerCompat
1239                        .getYVelocity(mVelocityTracker, mActivePointerId);
1240                final int velDirFlag = yVelocity > 0f ? DOWN : UP;
1241                final float absYVelocity = Math.abs(yVelocity);
1242                if ((velDirFlag & flags) != 0 && velDirFlag == dirFlag &&
1243                        absYVelocity >= mCallback.getSwipeEscapeVelocity(mSwipeEscapeVelocity) &&
1244                        absYVelocity > Math.abs(xVelocity)) {
1245                    return velDirFlag;
1246                }
1247            }
1248
1249            final float threshold = mRecyclerView.getHeight() * mCallback
1250                    .getSwipeThreshold(viewHolder);
1251            if ((flags & dirFlag) != 0 && Math.abs(mDy) > threshold) {
1252                return dirFlag;
1253            }
1254        }
1255        return 0;
1256    }
1257
1258    private void addChildDrawingOrderCallback() {
1259        if (Build.VERSION.SDK_INT >= 21) {
1260            return;// we use elevation on Lollipop
1261        }
1262        if (mChildDrawingOrderCallback == null) {
1263            mChildDrawingOrderCallback = new RecyclerView.ChildDrawingOrderCallback() {
1264                @Override
1265                public int onGetChildDrawingOrder(int childCount, int i) {
1266                    if (mOverdrawChild == null) {
1267                        return i;
1268                    }
1269                    int childPosition = mOverdrawChildPosition;
1270                    if (childPosition == -1) {
1271                        childPosition = mRecyclerView.indexOfChild(mOverdrawChild);
1272                        mOverdrawChildPosition = childPosition;
1273                    }
1274                    if (i == childCount - 1) {
1275                        return childPosition;
1276                    }
1277                    return i < childPosition ? i : i + 1;
1278                }
1279            };
1280        }
1281        mRecyclerView.setChildDrawingOrderCallback(mChildDrawingOrderCallback);
1282    }
1283
1284    private void removeChildDrawingOrderCallbackIfNecessary(View view) {
1285        if (view == mOverdrawChild) {
1286            mOverdrawChild = null;
1287            // only remove if we've added
1288            if (mChildDrawingOrderCallback != null) {
1289                mRecyclerView.setChildDrawingOrderCallback(null);
1290            }
1291        }
1292    }
1293
1294    /**
1295     * An interface which can be implemented by LayoutManager for better integration with
1296     * {@link ItemTouchHelper}.
1297     */
1298    public static interface ViewDropHandler {
1299
1300        /**
1301         * Called by the {@link ItemTouchHelper} after a View is dropped over another View.
1302         * <p>
1303         * A LayoutManager should implement this interface to get ready for the upcoming move
1304         * operation.
1305         * <p>
1306         * For example, LinearLayoutManager sets up a "scrollToPositionWithOffset" calls so that
1307         * the View under drag will be used as an anchor View while calculating the next layout,
1308         * making layout stay consistent.
1309         *
1310         * @param view   The View which is being dragged. It is very likely that user is still
1311         *               dragging this View so there might be other
1312         *               {@link #prepareForDrop(View, View, int, int)} after this one.
1313         * @param target The target view which is being dropped on.
1314         * @param x      The <code>left</code> offset of the View that is being dragged. This value
1315         *               includes the movement caused by the user.
1316         * @param y      The <code>top</code> offset of the View that is being dragged. This value
1317         *               includes the movement caused by the user.
1318         */
1319        public void prepareForDrop(View view, View target, int x, int y);
1320    }
1321
1322    /**
1323     * This class is the contract between ItemTouchHelper and your application. It lets you control
1324     * which touch behaviors are enabled per each ViewHolder and also receive callbacks when user
1325     * performs these actions.
1326     * <p>
1327     * To control which actions user can take on each view, you should override
1328     * {@link #getMovementFlags(RecyclerView, ViewHolder)} and return appropriate set
1329     * of direction flags. ({@link #LEFT}, {@link #RIGHT}, {@link #START}, {@link #END},
1330     * {@link #UP}, {@link #DOWN}). You can use
1331     * {@link #makeMovementFlags(int, int)} to easily construct it. Alternatively, you can use
1332     * {@link SimpleCallback}.
1333     * <p>
1334     * If user drags an item, ItemTouchHelper will call
1335     * {@link Callback#onMove(RecyclerView, ViewHolder, ViewHolder)
1336     * onMove(recyclerView, dragged, target)}.
1337     * Upon receiving this callback, you should move the item from the old position
1338     * ({@code dragged.getAdapterPosition()}) to new position ({@code target.getAdapterPosition()})
1339     * in your adapter and also call {@link RecyclerView.Adapter#notifyItemMoved(int, int)}.
1340     * To control where a View can be dropped, you can override
1341     * {@link #canDropOver(RecyclerView, ViewHolder, ViewHolder)}. When a
1342     * dragging View overlaps multiple other views, Callback chooses the closest View with which
1343     * dragged View might have changed positions. Although this approach works for many use cases,
1344     * if you have a custom LayoutManager, you can override
1345     * {@link #chooseDropTarget(ViewHolder, java.util.List, int, int)} to select a
1346     * custom drop target.
1347     * <p>
1348     * When a View is swiped, ItemTouchHelper animates it until it goes out of bounds, then calls
1349     * {@link #onSwiped(ViewHolder, int)}. At this point, you should update your
1350     * adapter (e.g. remove the item) and call related Adapter#notify event.
1351     */
1352    @SuppressWarnings("UnusedParameters")
1353    public abstract static class Callback {
1354
1355        public static final int DEFAULT_DRAG_ANIMATION_DURATION = 200;
1356
1357        public static final int DEFAULT_SWIPE_ANIMATION_DURATION = 250;
1358
1359        static final int RELATIVE_DIR_FLAGS = START | END |
1360                ((START | END) << DIRECTION_FLAG_COUNT) |
1361                ((START | END) << (2 * DIRECTION_FLAG_COUNT));
1362
1363        private static final ItemTouchUIUtil sUICallback;
1364
1365        private static final int ABS_HORIZONTAL_DIR_FLAGS = LEFT | RIGHT |
1366                ((LEFT | RIGHT) << DIRECTION_FLAG_COUNT) |
1367                ((LEFT | RIGHT) << (2 * DIRECTION_FLAG_COUNT));
1368
1369        private static final Interpolator sDragScrollInterpolator = new Interpolator() {
1370            public float getInterpolation(float t) {
1371                return t * t * t * t * t;
1372            }
1373        };
1374
1375        private static final Interpolator sDragViewScrollCapInterpolator = new Interpolator() {
1376            public float getInterpolation(float t) {
1377                t -= 1.0f;
1378                return t * t * t * t * t + 1.0f;
1379            }
1380        };
1381
1382        /**
1383         * Drag scroll speed keeps accelerating until this many milliseconds before being capped.
1384         */
1385        private static final long DRAG_SCROLL_ACCELERATION_LIMIT_TIME_MS = 2000;
1386
1387        private int mCachedMaxScrollSpeed = -1;
1388
1389        static {
1390            if (Build.VERSION.SDK_INT >= 21) {
1391                sUICallback = new ItemTouchUIUtilImpl.Lollipop();
1392            } else if (Build.VERSION.SDK_INT >= 11) {
1393                sUICallback = new ItemTouchUIUtilImpl.Honeycomb();
1394            } else {
1395                sUICallback = new ItemTouchUIUtilImpl.Gingerbread();
1396            }
1397        }
1398
1399        /**
1400         * Returns the {@link ItemTouchUIUtil} that is used by the {@link Callback} class for
1401         * visual
1402         * changes on Views in response to user interactions. {@link ItemTouchUIUtil} has different
1403         * implementations for different platform versions.
1404         * <p>
1405         * By default, {@link Callback} applies these changes on
1406         * {@link RecyclerView.ViewHolder#itemView}.
1407         * <p>
1408         * For example, if you have a use case where you only want the text to move when user
1409         * swipes over the view, you can do the following:
1410         * <pre>
1411         *     public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder){
1412         *         getDefaultUIUtil().clearView(((ItemTouchViewHolder) viewHolder).textView);
1413         *     }
1414         *     public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
1415         *         if (viewHolder != null){
1416         *             getDefaultUIUtil().onSelected(((ItemTouchViewHolder) viewHolder).textView);
1417         *         }
1418         *     }
1419         *     public void onChildDraw(Canvas c, RecyclerView recyclerView,
1420         *             RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState,
1421         *             boolean isCurrentlyActive) {
1422         *         getDefaultUIUtil().onDraw(c, recyclerView,
1423         *                 ((ItemTouchViewHolder) viewHolder).textView, dX, dY,
1424         *                 actionState, isCurrentlyActive);
1425         *         return true;
1426         *     }
1427         *     public void onChildDrawOver(Canvas c, RecyclerView recyclerView,
1428         *             RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState,
1429         *             boolean isCurrentlyActive) {
1430         *         getDefaultUIUtil().onDrawOver(c, recyclerView,
1431         *                 ((ItemTouchViewHolder) viewHolder).textView, dX, dY,
1432         *                 actionState, isCurrentlyActive);
1433         *         return true;
1434         *     }
1435         * </pre>
1436         *
1437         * @return The {@link ItemTouchUIUtil} instance that is used by the {@link Callback}
1438         */
1439        public static ItemTouchUIUtil getDefaultUIUtil() {
1440            return sUICallback;
1441        }
1442
1443        /**
1444         * Replaces a movement direction with its relative version by taking layout direction into
1445         * account.
1446         *
1447         * @param flags           The flag value that include any number of movement flags.
1448         * @param layoutDirection The layout direction of the View. Can be obtained from
1449         *                        {@link ViewCompat#getLayoutDirection(android.view.View)}.
1450         * @return Updated flags which uses relative flags ({@link #START}, {@link #END}) instead
1451         * of {@link #LEFT}, {@link #RIGHT}.
1452         * @see #convertToAbsoluteDirection(int, int)
1453         */
1454        public static int convertToRelativeDirection(int flags, int layoutDirection) {
1455            int masked = flags & ABS_HORIZONTAL_DIR_FLAGS;
1456            if (masked == 0) {
1457                return flags;// does not have any abs flags, good.
1458            }
1459            flags &= ~masked; //remove left / right.
1460            if (layoutDirection == ViewCompat.LAYOUT_DIRECTION_LTR) {
1461                // no change. just OR with 2 bits shifted mask and return
1462                flags |= masked << 2; // START is 2 bits after LEFT, END is 2 bits after RIGHT.
1463                return flags;
1464            } else {
1465                // add RIGHT flag as START
1466                flags |= ((masked << 1) & ~ABS_HORIZONTAL_DIR_FLAGS);
1467                // first clean RIGHT bit then add LEFT flag as END
1468                flags |= ((masked << 1) & ABS_HORIZONTAL_DIR_FLAGS) << 2;
1469            }
1470            return flags;
1471        }
1472
1473        /**
1474         * Convenience method to create movement flags.
1475         * <p>
1476         * For instance, if you want to let your items be drag & dropped vertically and swiped
1477         * left to be dismissed, you can call this method with:
1478         * <code>makeMovementFlags(UP | DOWN, LEFT);</code>
1479         *
1480         * @param dragFlags  The directions in which the item can be dragged.
1481         * @param swipeFlags The directions in which the item can be swiped.
1482         * @return Returns an integer composed of the given drag and swipe flags.
1483         */
1484        public static int makeMovementFlags(int dragFlags, int swipeFlags) {
1485            return makeFlag(ACTION_STATE_IDLE, swipeFlags | dragFlags) |
1486                    makeFlag(ACTION_STATE_SWIPE, swipeFlags) | makeFlag(ACTION_STATE_DRAG,
1487                    dragFlags);
1488        }
1489
1490        /**
1491         * Shifts the given direction flags to the offset of the given action state.
1492         *
1493         * @param actionState The action state you want to get flags in. Should be one of
1494         *                    {@link #ACTION_STATE_IDLE}, {@link #ACTION_STATE_SWIPE} or
1495         *                    {@link #ACTION_STATE_DRAG}.
1496         * @param directions  The direction flags. Can be composed from {@link #UP}, {@link #DOWN},
1497         *                    {@link #RIGHT}, {@link #LEFT} {@link #START} and {@link #END}.
1498         * @return And integer that represents the given directions in the provided actionState.
1499         */
1500        public static int makeFlag(int actionState, int directions) {
1501            return directions << (actionState * DIRECTION_FLAG_COUNT);
1502        }
1503
1504        /**
1505         * Should return a composite flag which defines the enabled move directions in each state
1506         * (idle, swiping, dragging).
1507         * <p>
1508         * Instead of composing this flag manually, you can use {@link #makeMovementFlags(int,
1509         * int)}
1510         * or {@link #makeFlag(int, int)}.
1511         * <p>
1512         * This flag is composed of 3 sets of 8 bits, where first 8 bits are for IDLE state, next
1513         * 8 bits are for SWIPE state and third 8 bits are for DRAG state.
1514         * Each 8 bit sections can be constructed by simply OR'ing direction flags defined in
1515         * {@link ItemTouchHelper}.
1516         * <p>
1517         * For example, if you want it to allow swiping LEFT and RIGHT but only allow starting to
1518         * swipe by swiping RIGHT, you can return:
1519         * <pre>
1520         *      makeFlag(ACTION_STATE_IDLE, RIGHT) | makeFlag(ACTION_STATE_SWIPE, LEFT | RIGHT);
1521         * </pre>
1522         * This means, allow right movement while IDLE and allow right and left movement while
1523         * swiping.
1524         *
1525         * @param recyclerView The RecyclerView to which ItemTouchHelper is attached.
1526         * @param viewHolder   The ViewHolder for which the movement information is necessary.
1527         * @return flags specifying which movements are allowed on this ViewHolder.
1528         * @see #makeMovementFlags(int, int)
1529         * @see #makeFlag(int, int)
1530         */
1531        public abstract int getMovementFlags(RecyclerView recyclerView,
1532                ViewHolder viewHolder);
1533
1534        /**
1535         * Converts a given set of flags to absolution direction which means {@link #START} and
1536         * {@link #END} are replaced with {@link #LEFT} and {@link #RIGHT} depending on the layout
1537         * direction.
1538         *
1539         * @param flags           The flag value that include any number of movement flags.
1540         * @param layoutDirection The layout direction of the RecyclerView.
1541         * @return Updated flags which includes only absolute direction values.
1542         */
1543        public int convertToAbsoluteDirection(int flags, int layoutDirection) {
1544            int masked = flags & RELATIVE_DIR_FLAGS;
1545            if (masked == 0) {
1546                return flags;// does not have any relative flags, good.
1547            }
1548            flags &= ~masked; //remove start / end
1549            if (layoutDirection == ViewCompat.LAYOUT_DIRECTION_LTR) {
1550                // no change. just OR with 2 bits shifted mask and return
1551                flags |= masked >> 2; // START is 2 bits after LEFT, END is 2 bits after RIGHT.
1552                return flags;
1553            } else {
1554                // add START flag as RIGHT
1555                flags |= ((masked >> 1) & ~RELATIVE_DIR_FLAGS);
1556                // first clean start bit then add END flag as LEFT
1557                flags |= ((masked >> 1) & RELATIVE_DIR_FLAGS) >> 2;
1558            }
1559            return flags;
1560        }
1561
1562        final int getAbsoluteMovementFlags(RecyclerView recyclerView,
1563                ViewHolder viewHolder) {
1564            final int flags = getMovementFlags(recyclerView, viewHolder);
1565            return convertToAbsoluteDirection(flags, ViewCompat.getLayoutDirection(recyclerView));
1566        }
1567
1568        private boolean hasDragFlag(RecyclerView recyclerView, ViewHolder viewHolder) {
1569            final int flags = getAbsoluteMovementFlags(recyclerView, viewHolder);
1570            return (flags & ACTION_MODE_DRAG_MASK) != 0;
1571        }
1572
1573        private boolean hasSwipeFlag(RecyclerView recyclerView,
1574                ViewHolder viewHolder) {
1575            final int flags = getAbsoluteMovementFlags(recyclerView, viewHolder);
1576            return (flags & ACTION_MODE_SWIPE_MASK) != 0;
1577        }
1578
1579        /**
1580         * Return true if the current ViewHolder can be dropped over the the target ViewHolder.
1581         * <p>
1582         * This method is used when selecting drop target for the dragged View. After Views are
1583         * eliminated either via bounds check or via this method, resulting set of views will be
1584         * passed to {@link #chooseDropTarget(ViewHolder, java.util.List, int, int)}.
1585         * <p>
1586         * Default implementation returns true.
1587         *
1588         * @param recyclerView The RecyclerView to which ItemTouchHelper is attached to.
1589         * @param current      The ViewHolder that user is dragging.
1590         * @param target       The ViewHolder which is below the dragged ViewHolder.
1591         * @return True if the dragged ViewHolder can be replaced with the target ViewHolder, false
1592         * otherwise.
1593         */
1594        public boolean canDropOver(RecyclerView recyclerView, ViewHolder current,
1595                ViewHolder target) {
1596            return true;
1597        }
1598
1599        /**
1600         * Called when ItemTouchHelper wants to move the dragged item from its old position to
1601         * the new position.
1602         * <p>
1603         * If this method returns true, ItemTouchHelper assumes {@code viewHolder} has been moved
1604         * to the adapter position of {@code target} ViewHolder
1605         * ({@link ViewHolder#getAdapterPosition()
1606         * ViewHolder#getAdapterPosition()}).
1607         * <p>
1608         * If you don't support drag & drop, this method will never be called.
1609         *
1610         * @param recyclerView The RecyclerView to which ItemTouchHelper is attached to.
1611         * @param viewHolder   The ViewHolder which is being dragged by the user.
1612         * @param target       The ViewHolder over which the currently active item is being
1613         *                     dragged.
1614         * @return True if the {@code viewHolder} has been moved to the adapter position of
1615         * {@code target}.
1616         * @see #onMoved(RecyclerView, ViewHolder, int, ViewHolder, int, int, int)
1617         */
1618        public abstract boolean onMove(RecyclerView recyclerView,
1619                ViewHolder viewHolder, ViewHolder target);
1620
1621        /**
1622         * Returns whether ItemTouchHelper should start a drag and drop operation if an item is
1623         * long pressed.
1624         * <p>
1625         * Default value returns true but you may want to disable this if you want to start
1626         * dragging on a custom view touch using {@link #startDrag(ViewHolder)}.
1627         *
1628         * @return True if ItemTouchHelper should start dragging an item when it is long pressed,
1629         * false otherwise. Default value is <code>true</code>.
1630         * @see #startDrag(ViewHolder)
1631         */
1632        public boolean isLongPressDragEnabled() {
1633            return true;
1634        }
1635
1636        /**
1637         * Returns whether ItemTouchHelper should start a swipe operation if a pointer is swiped
1638         * over the View.
1639         * <p>
1640         * Default value returns true but you may want to disable this if you want to start
1641         * swiping on a custom view touch using {@link #startSwipe(ViewHolder)}.
1642         *
1643         * @return True if ItemTouchHelper should start swiping an item when user swipes a pointer
1644         * over the View, false otherwise. Default value is <code>true</code>.
1645         * @see #startSwipe(ViewHolder)
1646         */
1647        public boolean isItemViewSwipeEnabled() {
1648            return true;
1649        }
1650
1651        /**
1652         * When finding views under a dragged view, by default, ItemTouchHelper searches for views
1653         * that overlap with the dragged View. By overriding this method, you can extend or shrink
1654         * the search box.
1655         *
1656         * @return The extra margin to be added to the hit box of the dragged View.
1657         */
1658        public int getBoundingBoxMargin() {
1659            return 0;
1660        }
1661
1662        /**
1663         * Returns the fraction that the user should move the View to be considered as swiped.
1664         * The fraction is calculated with respect to RecyclerView's bounds.
1665         * <p>
1666         * Default value is .5f, which means, to swipe a View, user must move the View at least
1667         * half of RecyclerView's width or height, depending on the swipe direction.
1668         *
1669         * @param viewHolder The ViewHolder that is being dragged.
1670         * @return A float value that denotes the fraction of the View size. Default value
1671         * is .5f .
1672         */
1673        public float getSwipeThreshold(ViewHolder viewHolder) {
1674            return .5f;
1675        }
1676
1677        /**
1678         * Returns the fraction that the user should move the View to be considered as it is
1679         * dragged. After a view is moved this amount, ItemTouchHelper starts checking for Views
1680         * below it for a possible drop.
1681         *
1682         * @param viewHolder The ViewHolder that is being dragged.
1683         * @return A float value that denotes the fraction of the View size. Default value is
1684         * .5f .
1685         */
1686        public float getMoveThreshold(ViewHolder viewHolder) {
1687            return .5f;
1688        }
1689
1690        /**
1691         * Defines the minimum velocity which will be considered as a swipe action by the user.
1692         * <p>
1693         * You can increase this value to make it harder to swipe or decrease it to make it easier.
1694         * Keep in mind that ItemTouchHelper also checks the perpendicular velocity and makes sure
1695         * current direction velocity is larger then the perpendicular one. Otherwise, user's
1696         * movement is ambiguous. You can change the threshold by overriding
1697         * {@link #getSwipeVelocityThreshold(float)}.
1698         * <p>
1699         * The velocity is calculated in pixels per second.
1700         * <p>
1701         * The default framework value is passed as a parameter so that you can modify it with a
1702         * multiplier.
1703         *
1704         * @param defaultValue The default value (in pixels per second) used by the
1705         *                     ItemTouchHelper.
1706         * @return The minimum swipe velocity. The default implementation returns the
1707         * <code>defaultValue</code> parameter.
1708         * @see #getSwipeVelocityThreshold(float)
1709         * @see #getSwipeThreshold(ViewHolder)
1710         */
1711        public float getSwipeEscapeVelocity(float defaultValue) {
1712            return defaultValue;
1713        }
1714
1715        /**
1716         * Defines the maximum velocity ItemTouchHelper will ever calculate for pointer movements.
1717         * <p>
1718         * To consider a movement as swipe, ItemTouchHelper requires it to be larger than the
1719         * perpendicular movement. If both directions reach to the max threshold, none of them will
1720         * be considered as a swipe because it is usually an indication that user rather tried to
1721         * scroll then swipe.
1722         * <p>
1723         * The velocity is calculated in pixels per second.
1724         * <p>
1725         * You can customize this behavior by changing this method. If you increase the value, it
1726         * will be easier for the user to swipe diagonally and if you decrease the value, user will
1727         * need to make a rather straight finger movement to trigger a swipe.
1728         *
1729         * @param defaultValue The default value(in pixels per second) used by the ItemTouchHelper.
1730         * @return The velocity cap for pointer movements. The default implementation returns the
1731         * <code>defaultValue</code> parameter.
1732         * @see #getSwipeEscapeVelocity(float)
1733         */
1734        public float getSwipeVelocityThreshold(float defaultValue) {
1735            return defaultValue;
1736        }
1737
1738        /**
1739         * Called by ItemTouchHelper to select a drop target from the list of ViewHolders that
1740         * are under the dragged View.
1741         * <p>
1742         * Default implementation filters the View with which dragged item have changed position
1743         * in the drag direction. For instance, if the view is dragged UP, it compares the
1744         * <code>view.getTop()</code> of the two views before and after drag started. If that value
1745         * is different, the target view passes the filter.
1746         * <p>
1747         * Among these Views which pass the test, the one closest to the dragged view is chosen.
1748         * <p>
1749         * This method is called on the main thread every time user moves the View. If you want to
1750         * override it, make sure it does not do any expensive operations.
1751         *
1752         * @param selected    The ViewHolder being dragged by the user.
1753         * @param dropTargets The list of ViewHolder that are under the dragged View and
1754         *                    candidate as a drop.
1755         * @param curX        The updated left value of the dragged View after drag translations
1756         *                    are applied. This value does not include margins added by
1757         *                    {@link RecyclerView.ItemDecoration}s.
1758         * @param curY        The updated top value of the dragged View after drag translations
1759         *                    are applied. This value does not include margins added by
1760         *                    {@link RecyclerView.ItemDecoration}s.
1761         * @return A ViewHolder to whose position the dragged ViewHolder should be
1762         * moved to.
1763         */
1764        public ViewHolder chooseDropTarget(ViewHolder selected,
1765                List<ViewHolder> dropTargets, int curX, int curY) {
1766            int right = curX + selected.itemView.getWidth();
1767            int bottom = curY + selected.itemView.getHeight();
1768            ViewHolder winner = null;
1769            int winnerScore = -1;
1770            final int dx = curX - selected.itemView.getLeft();
1771            final int dy = curY - selected.itemView.getTop();
1772            final int targetsSize = dropTargets.size();
1773            for (int i = 0; i < targetsSize; i++) {
1774                final ViewHolder target = dropTargets.get(i);
1775                if (dx > 0) {
1776                    int diff = target.itemView.getRight() - right;
1777                    if (diff < 0 && target.itemView.getRight() > selected.itemView.getRight()) {
1778                        final int score = Math.abs(diff);
1779                        if (score > winnerScore) {
1780                            winnerScore = score;
1781                            winner = target;
1782                        }
1783                    }
1784                }
1785                if (dx < 0) {
1786                    int diff = target.itemView.getLeft() - curX;
1787                    if (diff > 0 && target.itemView.getLeft() < selected.itemView.getLeft()) {
1788                        final int score = Math.abs(diff);
1789                        if (score > winnerScore) {
1790                            winnerScore = score;
1791                            winner = target;
1792                        }
1793                    }
1794                }
1795                if (dy < 0) {
1796                    int diff = target.itemView.getTop() - curY;
1797                    if (diff > 0 && target.itemView.getTop() < selected.itemView.getTop()) {
1798                        final int score = Math.abs(diff);
1799                        if (score > winnerScore) {
1800                            winnerScore = score;
1801                            winner = target;
1802                        }
1803                    }
1804                }
1805
1806                if (dy > 0) {
1807                    int diff = target.itemView.getBottom() - bottom;
1808                    if (diff < 0 && target.itemView.getBottom() > selected.itemView.getBottom()) {
1809                        final int score = Math.abs(diff);
1810                        if (score > winnerScore) {
1811                            winnerScore = score;
1812                            winner = target;
1813                        }
1814                    }
1815                }
1816            }
1817            return winner;
1818        }
1819
1820        /**
1821         * Called when a ViewHolder is swiped by the user.
1822         * <p>
1823         * If you are returning relative directions ({@link #START} , {@link #END}) from the
1824         * {@link #getMovementFlags(RecyclerView, ViewHolder)} method, this method
1825         * will also use relative directions. Otherwise, it will use absolute directions.
1826         * <p>
1827         * If you don't support swiping, this method will never be called.
1828         * <p>
1829         * ItemTouchHelper will keep a reference to the View until it is detached from
1830         * RecyclerView.
1831         * As soon as it is detached, ItemTouchHelper will call
1832         * {@link #clearView(RecyclerView, ViewHolder)}.
1833         *
1834         * @param viewHolder The ViewHolder which has been swiped by the user.
1835         * @param direction  The direction to which the ViewHolder is swiped. It is one of
1836         *                   {@link #UP}, {@link #DOWN},
1837         *                   {@link #LEFT} or {@link #RIGHT}. If your
1838         *                   {@link #getMovementFlags(RecyclerView, ViewHolder)}
1839         *                   method
1840         *                   returned relative flags instead of {@link #LEFT} / {@link #RIGHT};
1841         *                   `direction` will be relative as well. ({@link #START} or {@link
1842         *                   #END}).
1843         */
1844        public abstract void onSwiped(ViewHolder viewHolder, int direction);
1845
1846        /**
1847         * Called when the ViewHolder swiped or dragged by the ItemTouchHelper is changed.
1848         * <p/>
1849         * If you override this method, you should call super.
1850         *
1851         * @param viewHolder  The new ViewHolder that is being swiped or dragged. Might be null if
1852         *                    it is cleared.
1853         * @param actionState One of {@link ItemTouchHelper#ACTION_STATE_IDLE},
1854         *                    {@link ItemTouchHelper#ACTION_STATE_SWIPE} or
1855         *                    {@link ItemTouchHelper#ACTION_STATE_DRAG}.
1856         * @see #clearView(RecyclerView, RecyclerView.ViewHolder)
1857         */
1858        public void onSelectedChanged(ViewHolder viewHolder, int actionState) {
1859            if (viewHolder != null) {
1860                sUICallback.onSelected(viewHolder.itemView);
1861            }
1862        }
1863
1864        private int getMaxDragScroll(RecyclerView recyclerView) {
1865            if (mCachedMaxScrollSpeed == -1) {
1866                mCachedMaxScrollSpeed = recyclerView.getResources().getDimensionPixelSize(
1867                        R.dimen.item_touch_helper_max_drag_scroll_per_frame);
1868            }
1869            return mCachedMaxScrollSpeed;
1870        }
1871
1872        /**
1873         * Called when {@link #onMove(RecyclerView, ViewHolder, ViewHolder)} returns true.
1874         * <p>
1875         * ItemTouchHelper does not create an extra Bitmap or View while dragging, instead, it
1876         * modifies the existing View. Because of this reason, it is important that the View is
1877         * still part of the layout after it is moved. This may not work as intended when swapped
1878         * Views are close to RecyclerView bounds or there are gaps between them (e.g. other Views
1879         * which were not eligible for dropping over).
1880         * <p>
1881         * This method is responsible to give necessary hint to the LayoutManager so that it will
1882         * keep the View in visible area. For example, for LinearLayoutManager, this is as simple
1883         * as calling {@link LinearLayoutManager#scrollToPositionWithOffset(int, int)}.
1884         *
1885         * Default implementation calls {@link RecyclerView#scrollToPosition(int)} if the View's
1886         * new position is likely to be out of bounds.
1887         * <p>
1888         * It is important to ensure the ViewHolder will stay visible as otherwise, it might be
1889         * removed by the LayoutManager if the move causes the View to go out of bounds. In that
1890         * case, drag will end prematurely.
1891         *
1892         * @param recyclerView The RecyclerView controlled by the ItemTouchHelper.
1893         * @param viewHolder   The ViewHolder under user's control.
1894         * @param fromPos      The previous adapter position of the dragged item (before it was
1895         *                     moved).
1896         * @param target       The ViewHolder on which the currently active item has been dropped.
1897         * @param toPos        The new adapter position of the dragged item.
1898         * @param x            The updated left value of the dragged View after drag translations
1899         *                     are applied. This value does not include margins added by
1900         *                     {@link RecyclerView.ItemDecoration}s.
1901         * @param y            The updated top value of the dragged View after drag translations
1902         *                     are applied. This value does not include margins added by
1903         *                     {@link RecyclerView.ItemDecoration}s.
1904         */
1905        public void onMoved(final RecyclerView recyclerView,
1906                final ViewHolder viewHolder, int fromPos, final ViewHolder target, int toPos, int x,
1907                int y) {
1908            final RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
1909            if (layoutManager instanceof ViewDropHandler) {
1910                ((ViewDropHandler) layoutManager).prepareForDrop(viewHolder.itemView,
1911                        target.itemView, x, y);
1912                return;
1913            }
1914
1915            // if layout manager cannot handle it, do some guesswork
1916            if (layoutManager.canScrollHorizontally()) {
1917                final int minLeft = layoutManager.getDecoratedLeft(target.itemView);
1918                if (minLeft <= recyclerView.getPaddingLeft()) {
1919                    recyclerView.scrollToPosition(toPos);
1920                }
1921                final int maxRight = layoutManager.getDecoratedRight(target.itemView);
1922                if (maxRight >= recyclerView.getWidth() - recyclerView.getPaddingRight()) {
1923                    recyclerView.scrollToPosition(toPos);
1924                }
1925            }
1926
1927            if (layoutManager.canScrollVertically()) {
1928                final int minTop = layoutManager.getDecoratedTop(target.itemView);
1929                if (minTop <= recyclerView.getPaddingTop()) {
1930                    recyclerView.scrollToPosition(toPos);
1931                }
1932                final int maxBottom = layoutManager.getDecoratedBottom(target.itemView);
1933                if (maxBottom >= recyclerView.getHeight() - recyclerView.getPaddingBottom()) {
1934                    recyclerView.scrollToPosition(toPos);
1935                }
1936            }
1937        }
1938
1939        private void onDraw(Canvas c, RecyclerView parent, ViewHolder selected,
1940                List<ItemTouchHelper.RecoverAnimation> recoverAnimationList,
1941                int actionState, float dX, float dY) {
1942            final int recoverAnimSize = recoverAnimationList.size();
1943            for (int i = 0; i < recoverAnimSize; i++) {
1944                final ItemTouchHelper.RecoverAnimation anim = recoverAnimationList.get(i);
1945                anim.update();
1946                final int count = c.save();
1947                onChildDraw(c, parent, anim.mViewHolder, anim.mX, anim.mY, anim.mActionState,
1948                        false);
1949                c.restoreToCount(count);
1950            }
1951            if (selected != null) {
1952                final int count = c.save();
1953                onChildDraw(c, parent, selected, dX, dY, actionState, true);
1954                c.restoreToCount(count);
1955            }
1956        }
1957
1958        private void onDrawOver(Canvas c, RecyclerView parent, ViewHolder selected,
1959                List<ItemTouchHelper.RecoverAnimation> recoverAnimationList,
1960                int actionState, float dX, float dY) {
1961            final int recoverAnimSize = recoverAnimationList.size();
1962            for (int i = 0; i < recoverAnimSize; i++) {
1963                final ItemTouchHelper.RecoverAnimation anim = recoverAnimationList.get(i);
1964                final int count = c.save();
1965                onChildDrawOver(c, parent, anim.mViewHolder, anim.mX, anim.mY, anim.mActionState,
1966                        false);
1967                c.restoreToCount(count);
1968            }
1969            if (selected != null) {
1970                final int count = c.save();
1971                onChildDrawOver(c, parent, selected, dX, dY, actionState, true);
1972                c.restoreToCount(count);
1973            }
1974            boolean hasRunningAnimation = false;
1975            for (int i = recoverAnimSize - 1; i >= 0; i--) {
1976                final RecoverAnimation anim = recoverAnimationList.get(i);
1977                if (anim.mEnded && !anim.mIsPendingCleanup) {
1978                    recoverAnimationList.remove(i);
1979                } else if (!anim.mEnded) {
1980                    hasRunningAnimation = true;
1981                }
1982            }
1983            if (hasRunningAnimation) {
1984                parent.invalidate();
1985            }
1986        }
1987
1988        /**
1989         * Called by the ItemTouchHelper when the user interaction with an element is over and it
1990         * also completed its animation.
1991         * <p>
1992         * This is a good place to clear all changes on the View that was done in
1993         * {@link #onSelectedChanged(RecyclerView.ViewHolder, int)},
1994         * {@link #onChildDraw(Canvas, RecyclerView, ViewHolder, float, float, int,
1995         * boolean)} or
1996         * {@link #onChildDrawOver(Canvas, RecyclerView, ViewHolder, float, float, int, boolean)}.
1997         *
1998         * @param recyclerView The RecyclerView which is controlled by the ItemTouchHelper.
1999         * @param viewHolder   The View that was interacted by the user.
2000         */
2001        public void clearView(RecyclerView recyclerView, ViewHolder viewHolder) {
2002            sUICallback.clearView(viewHolder.itemView);
2003        }
2004
2005        /**
2006         * Called by ItemTouchHelper on RecyclerView's onDraw callback.
2007         * <p>
2008         * If you would like to customize how your View's respond to user interactions, this is
2009         * a good place to override.
2010         * <p>
2011         * Default implementation translates the child by the given <code>dX</code>,
2012         * <code>dY</code>.
2013         * ItemTouchHelper also takes care of drawing the child after other children if it is being
2014         * dragged. This is done using child re-ordering mechanism. On platforms prior to L, this
2015         * is
2016         * achieved via {@link android.view.ViewGroup#getChildDrawingOrder(int, int)} and on L
2017         * and after, it changes View's elevation value to be greater than all other children.)
2018         *
2019         * @param c                 The canvas which RecyclerView is drawing its children
2020         * @param recyclerView      The RecyclerView to which ItemTouchHelper is attached to
2021         * @param viewHolder        The ViewHolder which is being interacted by the User or it was
2022         *                          interacted and simply animating to its original position
2023         * @param dX                The amount of horizontal displacement caused by user's action
2024         * @param dY                The amount of vertical displacement caused by user's action
2025         * @param actionState       The type of interaction on the View. Is either {@link
2026         *                          #ACTION_STATE_DRAG} or {@link #ACTION_STATE_SWIPE}.
2027         * @param isCurrentlyActive True if this view is currently being controlled by the user or
2028         *                          false it is simply animating back to its original state.
2029         * @see #onChildDrawOver(Canvas, RecyclerView, ViewHolder, float, float, int,
2030         * boolean)
2031         */
2032        public void onChildDraw(Canvas c, RecyclerView recyclerView,
2033                ViewHolder viewHolder,
2034                float dX, float dY, int actionState, boolean isCurrentlyActive) {
2035            sUICallback.onDraw(c, recyclerView, viewHolder.itemView, dX, dY, actionState,
2036                    isCurrentlyActive);
2037        }
2038
2039        /**
2040         * Called by ItemTouchHelper on RecyclerView's onDraw callback.
2041         * <p>
2042         * If you would like to customize how your View's respond to user interactions, this is
2043         * a good place to override.
2044         * <p>
2045         * Default implementation translates the child by the given <code>dX</code>,
2046         * <code>dY</code>.
2047         * ItemTouchHelper also takes care of drawing the child after other children if it is being
2048         * dragged. This is done using child re-ordering mechanism. On platforms prior to L, this
2049         * is
2050         * achieved via {@link android.view.ViewGroup#getChildDrawingOrder(int, int)} and on L
2051         * and after, it changes View's elevation value to be greater than all other children.)
2052         *
2053         * @param c                 The canvas which RecyclerView is drawing its children
2054         * @param recyclerView      The RecyclerView to which ItemTouchHelper is attached to
2055         * @param viewHolder        The ViewHolder which is being interacted by the User or it was
2056         *                          interacted and simply animating to its original position
2057         * @param dX                The amount of horizontal displacement caused by user's action
2058         * @param dY                The amount of vertical displacement caused by user's action
2059         * @param actionState       The type of interaction on the View. Is either {@link
2060         *                          #ACTION_STATE_DRAG} or {@link #ACTION_STATE_SWIPE}.
2061         * @param isCurrentlyActive True if this view is currently being controlled by the user or
2062         *                          false it is simply animating back to its original state.
2063         * @see #onChildDrawOver(Canvas, RecyclerView, ViewHolder, float, float, int,
2064         * boolean)
2065         */
2066        public void onChildDrawOver(Canvas c, RecyclerView recyclerView,
2067                ViewHolder viewHolder,
2068                float dX, float dY, int actionState, boolean isCurrentlyActive) {
2069            sUICallback.onDrawOver(c, recyclerView, viewHolder.itemView, dX, dY, actionState,
2070                    isCurrentlyActive);
2071        }
2072
2073        /**
2074         * Called by the ItemTouchHelper when user action finished on a ViewHolder and now the View
2075         * will be animated to its final position.
2076         * <p>
2077         * Default implementation uses ItemAnimator's duration values. If
2078         * <code>animationType</code> is {@link #ANIMATION_TYPE_DRAG}, it returns
2079         * {@link RecyclerView.ItemAnimator#getMoveDuration()}, otherwise, it returns
2080         * {@link RecyclerView.ItemAnimator#getRemoveDuration()}. If RecyclerView does not have
2081         * any {@link RecyclerView.ItemAnimator} attached, this method returns
2082         * {@code DEFAULT_DRAG_ANIMATION_DURATION} or {@code DEFAULT_SWIPE_ANIMATION_DURATION}
2083         * depending on the animation type.
2084         *
2085         * @param recyclerView  The RecyclerView to which the ItemTouchHelper is attached to.
2086         * @param animationType The type of animation. Is one of {@link #ANIMATION_TYPE_DRAG},
2087         *                      {@link #ANIMATION_TYPE_SWIPE_CANCEL} or
2088         *                      {@link #ANIMATION_TYPE_SWIPE_SUCCESS}.
2089         * @param animateDx     The horizontal distance that the animation will offset
2090         * @param animateDy     The vertical distance that the animation will offset
2091         * @return The duration for the animation
2092         */
2093        public long getAnimationDuration(RecyclerView recyclerView, int animationType,
2094                float animateDx, float animateDy) {
2095            final RecyclerView.ItemAnimator itemAnimator = recyclerView.getItemAnimator();
2096            if (itemAnimator == null) {
2097                return animationType == ANIMATION_TYPE_DRAG ? DEFAULT_DRAG_ANIMATION_DURATION
2098                        : DEFAULT_SWIPE_ANIMATION_DURATION;
2099            } else {
2100                return animationType == ANIMATION_TYPE_DRAG ? itemAnimator.getMoveDuration()
2101                        : itemAnimator.getRemoveDuration();
2102            }
2103        }
2104
2105        /**
2106         * Called by the ItemTouchHelper when user is dragging a view out of bounds.
2107         * <p>
2108         * You can override this method to decide how much RecyclerView should scroll in response
2109         * to this action. Default implementation calculates a value based on the amount of View
2110         * out of bounds and the time it spent there. The longer user keeps the View out of bounds,
2111         * the faster the list will scroll. Similarly, the larger portion of the View is out of
2112         * bounds, the faster the RecyclerView will scroll.
2113         *
2114         * @param recyclerView        The RecyclerView instance to which ItemTouchHelper is
2115         *                            attached to.
2116         * @param viewSize            The total size of the View in scroll direction, excluding
2117         *                            item decorations.
2118         * @param viewSizeOutOfBounds The total size of the View that is out of bounds. This value
2119         *                            is negative if the View is dragged towards left or top edge.
2120         * @param totalSize           The total size of RecyclerView in the scroll direction.
2121         * @param msSinceStartScroll  The time passed since View is kept out of bounds.
2122         * @return The amount that RecyclerView should scroll. Keep in mind that this value will
2123         * be passed to {@link RecyclerView#scrollBy(int, int)} method.
2124         */
2125        public int interpolateOutOfBoundsScroll(RecyclerView recyclerView,
2126                int viewSize, int viewSizeOutOfBounds,
2127                int totalSize, long msSinceStartScroll) {
2128            final int maxScroll = getMaxDragScroll(recyclerView);
2129            final int absOutOfBounds = Math.abs(viewSizeOutOfBounds);
2130            final int direction = (int) Math.signum(viewSizeOutOfBounds);
2131            // might be negative if other direction
2132            float outOfBoundsRatio = Math.min(1f, 1f * absOutOfBounds / viewSize);
2133            final int cappedScroll = (int) (direction * maxScroll *
2134                    sDragViewScrollCapInterpolator.getInterpolation(outOfBoundsRatio));
2135            final float timeRatio;
2136            if (msSinceStartScroll > DRAG_SCROLL_ACCELERATION_LIMIT_TIME_MS) {
2137                timeRatio = 1f;
2138            } else {
2139                timeRatio = (float) msSinceStartScroll / DRAG_SCROLL_ACCELERATION_LIMIT_TIME_MS;
2140            }
2141            final int value = (int) (cappedScroll * sDragScrollInterpolator
2142                    .getInterpolation(timeRatio));
2143            if (value == 0) {
2144                return viewSizeOutOfBounds > 0 ? 1 : -1;
2145            }
2146            return value;
2147        }
2148    }
2149
2150    /**
2151     * A simple wrapper to the default Callback which you can construct with drag and swipe
2152     * directions and this class will handle the flag callbacks. You should still override onMove
2153     * or
2154     * onSwiped depending on your use case.
2155     *
2156     * <pre>
2157     * ItemTouchHelper mIth = new ItemTouchHelper(
2158     *     new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN,
2159     *         ItemTouchHelper.LEFT) {
2160     *         public abstract boolean onMove(RecyclerView recyclerView,
2161     *             ViewHolder viewHolder, ViewHolder target) {
2162     *             final int fromPos = viewHolder.getAdapterPosition();
2163     *             final int toPos = viewHolder.getAdapterPosition();
2164     *             // move item in `fromPos` to `toPos` in adapter.
2165     *             return true;// true if moved, false otherwise
2166     *         }
2167     *         public void onSwiped(ViewHolder viewHolder, int direction) {
2168     *             // remove from adapter
2169     *         }
2170     * });
2171     * </pre>
2172     */
2173    public abstract static class SimpleCallback extends Callback {
2174
2175        private int mDefaultSwipeDirs;
2176
2177        private int mDefaultDragDirs;
2178
2179        /**
2180         * Creates a Callback for the given drag and swipe allowance. These values serve as
2181         * defaults
2182         * and if you want to customize behavior per ViewHolder, you can override
2183         * {@link #getSwipeDirs(RecyclerView, ViewHolder)}
2184         * and / or {@link #getDragDirs(RecyclerView, ViewHolder)}.
2185         *
2186         * @param dragDirs  Binary OR of direction flags in which the Views can be dragged. Must be
2187         *                  composed of {@link #LEFT}, {@link #RIGHT}, {@link #START}, {@link
2188         *                  #END},
2189         *                  {@link #UP} and {@link #DOWN}.
2190         * @param swipeDirs Binary OR of direction flags in which the Views can be swiped. Must be
2191         *                  composed of {@link #LEFT}, {@link #RIGHT}, {@link #START}, {@link
2192         *                  #END},
2193         *                  {@link #UP} and {@link #DOWN}.
2194         */
2195        public SimpleCallback(int dragDirs, int swipeDirs) {
2196            mDefaultSwipeDirs = swipeDirs;
2197            mDefaultDragDirs = dragDirs;
2198        }
2199
2200        /**
2201         * Updates the default swipe directions. For example, you can use this method to toggle
2202         * certain directions depending on your use case.
2203         *
2204         * @param defaultSwipeDirs Binary OR of directions in which the ViewHolders can be swiped.
2205         */
2206        public void setDefaultSwipeDirs(int defaultSwipeDirs) {
2207            mDefaultSwipeDirs = defaultSwipeDirs;
2208        }
2209
2210        /**
2211         * Updates the default drag directions. For example, you can use this method to toggle
2212         * certain directions depending on your use case.
2213         *
2214         * @param defaultDragDirs Binary OR of directions in which the ViewHolders can be dragged.
2215         */
2216        public void setDefaultDragDirs(int defaultDragDirs) {
2217            mDefaultDragDirs = defaultDragDirs;
2218        }
2219
2220        /**
2221         * Returns the swipe directions for the provided ViewHolder.
2222         * Default implementation returns the swipe directions that was set via constructor or
2223         * {@link #setDefaultSwipeDirs(int)}.
2224         *
2225         * @param recyclerView The RecyclerView to which the ItemTouchHelper is attached to.
2226         * @param viewHolder   The RecyclerView for which the swipe drection is queried.
2227         * @return A binary OR of direction flags.
2228         */
2229        public int getSwipeDirs(RecyclerView recyclerView, ViewHolder viewHolder) {
2230            return mDefaultSwipeDirs;
2231        }
2232
2233        /**
2234         * Returns the drag directions for the provided ViewHolder.
2235         * Default implementation returns the drag directions that was set via constructor or
2236         * {@link #setDefaultDragDirs(int)}.
2237         *
2238         * @param recyclerView The RecyclerView to which the ItemTouchHelper is attached to.
2239         * @param viewHolder   The RecyclerView for which the swipe drection is queried.
2240         * @return A binary OR of direction flags.
2241         */
2242        public int getDragDirs(RecyclerView recyclerView, ViewHolder viewHolder) {
2243            return mDefaultDragDirs;
2244        }
2245
2246        @Override
2247        public int getMovementFlags(RecyclerView recyclerView, ViewHolder viewHolder) {
2248            return makeMovementFlags(getDragDirs(recyclerView, viewHolder),
2249                    getSwipeDirs(recyclerView, viewHolder));
2250        }
2251    }
2252
2253    private class ItemTouchHelperGestureListener extends GestureDetector.SimpleOnGestureListener {
2254
2255        @Override
2256        public boolean onDown(MotionEvent e) {
2257            return true;
2258        }
2259
2260        @Override
2261        public void onLongPress(MotionEvent e) {
2262            View child = findChildView(e);
2263            if (child != null) {
2264                ViewHolder vh = mRecyclerView.getChildViewHolder(child);
2265                if (vh != null) {
2266                    if (!mCallback.hasDragFlag(mRecyclerView, vh)) {
2267                        return;
2268                    }
2269                    int pointerId = MotionEventCompat.getPointerId(e, 0);
2270                    // Long press is deferred.
2271                    // Check w/ active pointer id to avoid selecting after motion
2272                    // event is canceled.
2273                    if (pointerId == mActivePointerId) {
2274                        final int index = MotionEventCompat
2275                                .findPointerIndex(e, mActivePointerId);
2276                        final float x = MotionEventCompat.getX(e, index);
2277                        final float y = MotionEventCompat.getY(e, index);
2278                        mInitialTouchX = x;
2279                        mInitialTouchY = y;
2280                        mDx = mDy = 0f;
2281                        if (DEBUG) {
2282                            Log.d(TAG,
2283                                    "onlong press: x:" + mInitialTouchX + ",y:" + mInitialTouchY);
2284                        }
2285                        if (mCallback.isLongPressDragEnabled()) {
2286                            select(vh, ACTION_STATE_DRAG);
2287                        }
2288                    }
2289                }
2290            }
2291        }
2292    }
2293
2294    private class RecoverAnimation implements AnimatorListenerCompat {
2295
2296        final float mStartDx;
2297
2298        final float mStartDy;
2299
2300        final float mTargetX;
2301
2302        final float mTargetY;
2303
2304        final ViewHolder mViewHolder;
2305
2306        final int mActionState;
2307
2308        private final ValueAnimatorCompat mValueAnimator;
2309
2310        private final int mAnimationType;
2311
2312        public boolean mIsPendingCleanup;
2313
2314        float mX;
2315
2316        float mY;
2317
2318        // if user starts touching a recovering view, we put it into interaction mode again,
2319        // instantly.
2320        boolean mOverridden = false;
2321
2322        private boolean mEnded = false;
2323
2324        private float mFraction;
2325
2326        public RecoverAnimation(ViewHolder viewHolder, int animationType,
2327                int actionState, float startDx, float startDy, float targetX, float targetY) {
2328            mActionState = actionState;
2329            mAnimationType = animationType;
2330            mViewHolder = viewHolder;
2331            mStartDx = startDx;
2332            mStartDy = startDy;
2333            mTargetX = targetX;
2334            mTargetY = targetY;
2335            mValueAnimator = AnimatorCompatHelper.emptyValueAnimator();
2336            mValueAnimator.addUpdateListener(
2337                    new AnimatorUpdateListenerCompat() {
2338                        @Override
2339                        public void onAnimationUpdate(ValueAnimatorCompat animation) {
2340                            setFraction(animation.getAnimatedFraction());
2341                        }
2342                    });
2343            mValueAnimator.setTarget(viewHolder.itemView);
2344            mValueAnimator.addListener(this);
2345            setFraction(0f);
2346        }
2347
2348        public void setDuration(long duration) {
2349            mValueAnimator.setDuration(duration);
2350        }
2351
2352        public void start() {
2353            mViewHolder.setIsRecyclable(false);
2354            mValueAnimator.start();
2355        }
2356
2357        public void cancel() {
2358            mValueAnimator.cancel();
2359        }
2360
2361        public void setFraction(float fraction) {
2362            mFraction = fraction;
2363        }
2364
2365        /**
2366         * We run updates on onDraw method but use the fraction from animator callback.
2367         * This way, we can sync translate x/y values w/ the animators to avoid one-off frames.
2368         */
2369        public void update() {
2370            if (mStartDx == mTargetX) {
2371                mX = ViewCompat.getTranslationX(mViewHolder.itemView);
2372            } else {
2373                mX = mStartDx + mFraction * (mTargetX - mStartDx);
2374            }
2375            if (mStartDy == mTargetY) {
2376                mY = ViewCompat.getTranslationY(mViewHolder.itemView);
2377            } else {
2378                mY = mStartDy + mFraction * (mTargetY - mStartDy);
2379            }
2380        }
2381
2382        @Override
2383        public void onAnimationStart(ValueAnimatorCompat animation) {
2384
2385        }
2386
2387        @Override
2388        public void onAnimationEnd(ValueAnimatorCompat animation) {
2389            if (!mEnded) {
2390                mViewHolder.setIsRecyclable(true);
2391            }
2392            mEnded = true;
2393        }
2394
2395        @Override
2396        public void onAnimationCancel(ValueAnimatorCompat animation) {
2397            setFraction(1f); //make sure we recover the view's state.
2398        }
2399
2400        @Override
2401        public void onAnimationRepeat(ValueAnimatorCompat animation) {
2402
2403        }
2404    }
2405}