ItemTouchHelper.java revision ae7189188acb2bc972b08d9fbd59939f341c7829
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.animation.Animator;
20import android.animation.ValueAnimator;
21import android.content.res.Resources;
22import android.graphics.Canvas;
23import android.graphics.Rect;
24import android.os.Build;
25import android.support.annotation.Nullable;
26import android.support.v4.view.GestureDetectorCompat;
27import android.support.v4.view.MotionEventCompat;
28import android.support.v4.view.VelocityTrackerCompat;
29import android.support.v4.view.ViewCompat;
30import android.support.v7.recyclerview.R;
31import android.support.v7.widget.LinearLayoutManager;
32import android.support.v7.widget.RecyclerView;
33import android.support.v7.widget.RecyclerView.OnItemTouchListener;
34import android.support.v7.widget.RecyclerView.ViewHolder;
35import android.util.Log;
36import android.view.GestureDetector;
37import android.view.HapticFeedbackConstants;
38import android.view.MotionEvent;
39import android.view.VelocityTracker;
40import android.view.View;
41import android.view.ViewConfiguration;
42import android.view.ViewParent;
43import android.view.animation.Interpolator;
44
45import java.util.ArrayList;
46import java.util.List;
47
48/**
49 * This is a utility class to add swipe to dismiss and drag & drop support to RecyclerView.
50 * <p>
51 * It works with a RecyclerView and a Callback class, which configures what type of interactions
52 * are enabled and also receives events when user performs these actions.
53 * <p>
54 * Depending on which functionality you support, you should override
55 * {@link Callback#onMove(RecyclerView, ViewHolder, ViewHolder)} and / or
56 * {@link Callback#onSwiped(ViewHolder, int)}.
57 * <p>
58 * This class is designed to work with any LayoutManager but for certain situations, it can be
59 * optimized for your custom LayoutManager by extending methods in the
60 * {@link ItemTouchHelper.Callback} class or implementing {@link ItemTouchHelper.ViewDropHandler}
61 * interface in your LayoutManager.
62 * <p>
63 * By default, ItemTouchHelper moves the items' translateX/Y properties to reposition them. On
64 * platforms older than Honeycomb, ItemTouchHelper uses canvas translations and View's visibility
65 * property to move items in response to touch events. You can customize these behaviors by
66 * overriding {@link Callback#onChildDraw(Canvas, RecyclerView, ViewHolder, float, float, int,
67 * boolean)}
68 * or {@link Callback#onChildDrawOver(Canvas, RecyclerView, ViewHolder, float, float, int,
69 * boolean)}.
70 * <p/>
71 * Most of the time, you only need to override <code>onChildDraw</code> but due to limitations of
72 * platform prior to Honeycomb, you may need to implement <code>onChildDrawOver</code> as well.
73 */
74public class ItemTouchHelper extends RecyclerView.ItemDecoration
75        implements RecyclerView.OnChildAttachStateChangeListener {
76
77    /**
78     * Up direction, used for swipe & drag control.
79     */
80    public static final int UP = 1;
81
82    /**
83     * Down direction, used for swipe & drag control.
84     */
85    public static final int DOWN = 1 << 1;
86
87    /**
88     * Left direction, used for swipe & drag control.
89     */
90    public static final int LEFT = 1 << 2;
91
92    /**
93     * Right direction, used for swipe & drag control.
94     */
95    public static final int RIGHT = 1 << 3;
96
97    // If you change these relative direction values, update Callback#convertToAbsoluteDirection,
98    // Callback#convertToRelativeDirection.
99    /**
100     * Horizontal start direction. Resolved to LEFT or RIGHT depending on RecyclerView's layout
101     * direction. Used for swipe & drag control.
102     */
103    public static final int START = LEFT << 2;
104
105    /**
106     * Horizontal end direction. Resolved to LEFT or RIGHT depending on RecyclerView's layout
107     * direction. Used for swipe & drag control.
108     */
109    public static final int END = RIGHT << 2;
110
111    /**
112     * ItemTouchHelper is in idle state. At this state, either there is no related motion event by
113     * the user or latest motion events have not yet triggered a swipe or drag.
114     */
115    public static final int ACTION_STATE_IDLE = 0;
116
117    /**
118     * A View is currently being swiped.
119     */
120    public static final int ACTION_STATE_SWIPE = 1;
121
122    /**
123     * A View is currently being dragged.
124     */
125    public static final int ACTION_STATE_DRAG = 2;
126
127    /**
128     * Animation type for views which are swiped successfully.
129     */
130    public static final int ANIMATION_TYPE_SWIPE_SUCCESS = 1 << 1;
131
132    /**
133     * Animation type for views which are not completely swiped thus will animate back to their
134     * original position.
135     */
136    public static final int ANIMATION_TYPE_SWIPE_CANCEL = 1 << 2;
137
138    /**
139     * Animation type for views that were dragged and now will animate to their final position.
140     */
141    public static final int ANIMATION_TYPE_DRAG = 1 << 3;
142
143    static final String TAG = "ItemTouchHelper";
144
145    static final boolean DEBUG = false;
146
147    static final int ACTIVE_POINTER_ID_NONE = -1;
148
149    static final int DIRECTION_FLAG_COUNT = 8;
150
151    private static final int ACTION_MODE_IDLE_MASK = (1 << DIRECTION_FLAG_COUNT) - 1;
152
153    static final int ACTION_MODE_SWIPE_MASK = ACTION_MODE_IDLE_MASK << DIRECTION_FLAG_COUNT;
154
155    static final int ACTION_MODE_DRAG_MASK = ACTION_MODE_SWIPE_MASK << DIRECTION_FLAG_COUNT;
156
157    /**
158     * The unit we are using to track velocity
159     */
160    private static final int PIXELS_PER_SECOND = 1000;
161
162    /**
163     * Views, whose state should be cleared after they are detached from RecyclerView.
164     * This is necessary after swipe dismissing an item. We wait until animator finishes its job
165     * to clean these views.
166     */
167    final List<View> mPendingCleanup = new ArrayList<View>();
168
169    /**
170     * Re-use array to calculate dx dy for a ViewHolder
171     */
172    private final float[] mTmpPosition = new float[2];
173
174    /**
175     * Currently selected view holder
176     */
177    ViewHolder mSelected = null;
178
179    /**
180     * The reference coordinates for the action start. For drag & drop, this is the time long
181     * press is completed vs for swipe, this is the initial touch point.
182     */
183    float mInitialTouchX;
184
185    float mInitialTouchY;
186
187    /**
188     * Set when ItemTouchHelper is assigned to a RecyclerView.
189     */
190    float mSwipeEscapeVelocity;
191
192    /**
193     * Set when ItemTouchHelper is assigned to a RecyclerView.
194     */
195    float mMaxSwipeVelocity;
196
197    /**
198     * The diff between the last event and initial touch.
199     */
200    float mDx;
201
202    float mDy;
203
204    /**
205     * The coordinates of the selected view at the time it is selected. We record these values
206     * when action starts so that we can consistently position it even if LayoutManager moves the
207     * View.
208     */
209    float mSelectedStartX;
210
211    float mSelectedStartY;
212
213    /**
214     * The pointer we are tracking.
215     */
216    int mActivePointerId = ACTIVE_POINTER_ID_NONE;
217
218    /**
219     * Developer callback which controls the behavior of ItemTouchHelper.
220     */
221    Callback mCallback;
222
223    /**
224     * Current mode.
225     */
226    int mActionState = ACTION_STATE_IDLE;
227
228    /**
229     * The direction flags obtained from unmasking
230     * {@link Callback#getAbsoluteMovementFlags(RecyclerView, ViewHolder)} for the current
231     * action state.
232     */
233    int mSelectedFlags;
234
235    /**
236     * When a View is dragged or swiped and needs to go back to where it was, we create a Recover
237     * Animation and animate it to its location using this custom Animator, instead of using
238     * framework Animators.
239     * Using framework animators has the side effect of clashing with ItemAnimator, creating
240     * jumpy UIs.
241     */
242    List<RecoverAnimation> mRecoverAnimations = new ArrayList<RecoverAnimation>();
243
244    private int mSlop;
245
246    RecyclerView mRecyclerView;
247
248    /**
249     * When user drags a view to the edge, we start scrolling the LayoutManager as long as View
250     * is partially out of bounds.
251     */
252    final Runnable mScrollRunnable = new Runnable() {
253        @Override
254        public void run() {
255            if (mSelected != null && scrollIfNecessary()) {
256                if (mSelected != null) { //it might be lost during scrolling
257                    moveIfNecessary(mSelected);
258                }
259                mRecyclerView.removeCallbacks(mScrollRunnable);
260                ViewCompat.postOnAnimation(mRecyclerView, this);
261            }
262        }
263    };
264
265    /**
266     * Used for detecting fling swipe
267     */
268    VelocityTracker mVelocityTracker;
269
270    //re-used list for selecting a swap target
271    private List<ViewHolder> mSwapTargets;
272
273    //re used for for sorting swap targets
274    private List<Integer> mDistances;
275
276    /**
277     * If drag & drop is supported, we use child drawing order to bring them to front.
278     */
279    private RecyclerView.ChildDrawingOrderCallback mChildDrawingOrderCallback = null;
280
281    /**
282     * This keeps a reference to the child dragged by the user. Even after user stops dragging,
283     * until view reaches its final position (end of recover animation), we keep a reference so
284     * that it can be drawn above other children.
285     */
286    View mOverdrawChild = null;
287
288    /**
289     * We cache the position of the overdraw child to avoid recalculating it each time child
290     * position callback is called. This value is invalidated whenever a child is attached or
291     * detached.
292     */
293    int mOverdrawChildPosition = -1;
294
295    /**
296     * Used to detect long press.
297     */
298    GestureDetectorCompat mGestureDetector;
299
300    private final OnItemTouchListener mOnItemTouchListener
301            = new OnItemTouchListener() {
302        @Override
303        public boolean onInterceptTouchEvent(RecyclerView recyclerView, MotionEvent event) {
304            mGestureDetector.onTouchEvent(event);
305            if (DEBUG) {
306                Log.d(TAG, "intercept: x:" + event.getX() + ",y:" + event.getY() + ", " + event);
307            }
308            final int action = MotionEventCompat.getActionMasked(event);
309            if (action == MotionEvent.ACTION_DOWN) {
310                mActivePointerId = event.getPointerId(0);
311                mInitialTouchX = event.getX();
312                mInitialTouchY = event.getY();
313                obtainVelocityTracker();
314                if (mSelected == null) {
315                    final RecoverAnimation animation = findAnimation(event);
316                    if (animation != null) {
317                        mInitialTouchX -= animation.mX;
318                        mInitialTouchY -= animation.mY;
319                        endRecoverAnimation(animation.mViewHolder, true);
320                        if (mPendingCleanup.remove(animation.mViewHolder.itemView)) {
321                            mCallback.clearView(mRecyclerView, animation.mViewHolder);
322                        }
323                        select(animation.mViewHolder, animation.mActionState);
324                        updateDxDy(event, mSelectedFlags, 0);
325                    }
326                }
327            } else if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
328                mActivePointerId = ACTIVE_POINTER_ID_NONE;
329                select(null, ACTION_STATE_IDLE);
330            } else if (mActivePointerId != ACTIVE_POINTER_ID_NONE) {
331                // in a non scroll orientation, if distance change is above threshold, we
332                // can select the item
333                final int index = event.findPointerIndex(mActivePointerId);
334                if (DEBUG) {
335                    Log.d(TAG, "pointer index " + index);
336                }
337                if (index >= 0) {
338                    checkSelectForSwipe(action, event, index);
339                }
340            }
341            if (mVelocityTracker != null) {
342                mVelocityTracker.addMovement(event);
343            }
344            return mSelected != null;
345        }
346
347        @Override
348        public void onTouchEvent(RecyclerView recyclerView, MotionEvent event) {
349            mGestureDetector.onTouchEvent(event);
350            if (DEBUG) {
351                Log.d(TAG,
352                        "on touch: x:" + mInitialTouchX + ",y:" + mInitialTouchY + ", :" + event);
353            }
354            if (mVelocityTracker != null) {
355                mVelocityTracker.addMovement(event);
356            }
357            if (mActivePointerId == ACTIVE_POINTER_ID_NONE) {
358                return;
359            }
360            final int action = MotionEventCompat.getActionMasked(event);
361            final int activePointerIndex = event.findPointerIndex(mActivePointerId);
362            if (activePointerIndex >= 0) {
363                checkSelectForSwipe(action, event, activePointerIndex);
364            }
365            ViewHolder viewHolder = mSelected;
366            if (viewHolder == null) {
367                return;
368            }
369            switch (action) {
370                case MotionEvent.ACTION_MOVE: {
371                    // Find the index of the active pointer and fetch its position
372                    if (activePointerIndex >= 0) {
373                        updateDxDy(event, mSelectedFlags, activePointerIndex);
374                        moveIfNecessary(viewHolder);
375                        mRecyclerView.removeCallbacks(mScrollRunnable);
376                        mScrollRunnable.run();
377                        mRecyclerView.invalidate();
378                    }
379                    break;
380                }
381                case MotionEvent.ACTION_CANCEL:
382                    if (mVelocityTracker != null) {
383                        mVelocityTracker.clear();
384                    }
385                    // fall through
386                case MotionEvent.ACTION_UP:
387                    select(null, ACTION_STATE_IDLE);
388                    mActivePointerId = ACTIVE_POINTER_ID_NONE;
389                    break;
390                case MotionEvent.ACTION_POINTER_UP: {
391                    final int pointerIndex = MotionEventCompat.getActionIndex(event);
392                    final int pointerId = event.getPointerId(pointerIndex);
393                    if (pointerId == mActivePointerId) {
394                        // This was our active pointer going up. Choose a new
395                        // active pointer and adjust accordingly.
396                        final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
397                        mActivePointerId = event.getPointerId(newPointerIndex);
398                        updateDxDy(event, mSelectedFlags, pointerIndex);
399                    }
400                    break;
401                }
402            }
403        }
404
405        @Override
406        public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
407            if (!disallowIntercept) {
408                return;
409            }
410            select(null, ACTION_STATE_IDLE);
411        }
412    };
413
414    /**
415     * Temporary rect instance that is used when we need to lookup Item decorations.
416     */
417    private Rect mTmpRect;
418
419    /**
420     * When user started to drag scroll. Reset when we don't scroll
421     */
422    private long mDragScrollStartTimeInMs;
423
424    /**
425     * Creates an ItemTouchHelper that will work with the given Callback.
426     * <p>
427     * You can attach ItemTouchHelper to a RecyclerView via
428     * {@link #attachToRecyclerView(RecyclerView)}. Upon attaching, it will add an item decoration,
429     * an onItemTouchListener and a Child attach / detach listener to the RecyclerView.
430     *
431     * @param callback The Callback which controls the behavior of this touch helper.
432     */
433    public ItemTouchHelper(Callback callback) {
434        mCallback = callback;
435    }
436
437    private static boolean hitTest(View child, float x, float y, float left, float top) {
438        return x >= left &&
439                x <= left + child.getWidth() &&
440                y >= top &&
441                y <= top + child.getHeight();
442    }
443
444    /**
445     * Attaches the ItemTouchHelper to the provided RecyclerView. If TouchHelper is already
446     * attached to a RecyclerView, it will first detach from the previous one. You can call this
447     * method with {@code null} to detach it from the current RecyclerView.
448     *
449     * @param recyclerView The RecyclerView instance to which you want to add this helper or
450     *                     {@code null} if you want to remove ItemTouchHelper from the current
451     *                     RecyclerView.
452     */
453    public void attachToRecyclerView(@Nullable 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    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(Animator 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    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    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    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    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    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    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 = motionEvent.findPointerIndex(mActivePointerId);
923        final float dx = motionEvent.getX(pointerIndex) - mInitialTouchX;
924        final float dy = motionEvent.getY(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    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 = motionEvent.getX(pointerIndex);
970        final float y = motionEvent.getY(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 = motionEvent.getPointerId(0);
1000        select(vh, ACTION_STATE_SWIPE);
1001        return true;
1002    }
1003
1004    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    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    void updateDxDy(MotionEvent ev, int directionFlags, int pointerIndex) {
1135        final float x = ev.getX(pointerIndex);
1136        final float y = ev.getY(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    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            @Override
1371            public float getInterpolation(float t) {
1372                return t * t * t * t * t;
1373            }
1374        };
1375
1376        private static final Interpolator sDragViewScrollCapInterpolator = new Interpolator() {
1377            @Override
1378            public float getInterpolation(float t) {
1379                t -= 1.0f;
1380                return t * t * t * t * t + 1.0f;
1381            }
1382        };
1383
1384        /**
1385         * Drag scroll speed keeps accelerating until this many milliseconds before being capped.
1386         */
1387        private static final long DRAG_SCROLL_ACCELERATION_LIMIT_TIME_MS = 2000;
1388
1389        private int mCachedMaxScrollSpeed = -1;
1390
1391        static {
1392            if (Build.VERSION.SDK_INT >= 21) {
1393                sUICallback = new ItemTouchUIUtilImpl.Lollipop();
1394            } else if (Build.VERSION.SDK_INT >= 11) {
1395                sUICallback = new ItemTouchUIUtilImpl.Honeycomb();
1396            } else {
1397                sUICallback = new ItemTouchUIUtilImpl.Gingerbread();
1398            }
1399        }
1400
1401        /**
1402         * Returns the {@link ItemTouchUIUtil} that is used by the {@link Callback} class for
1403         * visual
1404         * changes on Views in response to user interactions. {@link ItemTouchUIUtil} has different
1405         * implementations for different platform versions.
1406         * <p>
1407         * By default, {@link Callback} applies these changes on
1408         * {@link RecyclerView.ViewHolder#itemView}.
1409         * <p>
1410         * For example, if you have a use case where you only want the text to move when user
1411         * swipes over the view, you can do the following:
1412         * <pre>
1413         *     public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder){
1414         *         getDefaultUIUtil().clearView(((ItemTouchViewHolder) viewHolder).textView);
1415         *     }
1416         *     public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
1417         *         if (viewHolder != null){
1418         *             getDefaultUIUtil().onSelected(((ItemTouchViewHolder) viewHolder).textView);
1419         *         }
1420         *     }
1421         *     public void onChildDraw(Canvas c, RecyclerView recyclerView,
1422         *             RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState,
1423         *             boolean isCurrentlyActive) {
1424         *         getDefaultUIUtil().onDraw(c, recyclerView,
1425         *                 ((ItemTouchViewHolder) viewHolder).textView, dX, dY,
1426         *                 actionState, isCurrentlyActive);
1427         *         return true;
1428         *     }
1429         *     public void onChildDrawOver(Canvas c, RecyclerView recyclerView,
1430         *             RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState,
1431         *             boolean isCurrentlyActive) {
1432         *         getDefaultUIUtil().onDrawOver(c, recyclerView,
1433         *                 ((ItemTouchViewHolder) viewHolder).textView, dX, dY,
1434         *                 actionState, isCurrentlyActive);
1435         *         return true;
1436         *     }
1437         * </pre>
1438         *
1439         * @return The {@link ItemTouchUIUtil} instance that is used by the {@link Callback}
1440         */
1441        public static ItemTouchUIUtil getDefaultUIUtil() {
1442            return sUICallback;
1443        }
1444
1445        /**
1446         * Replaces a movement direction with its relative version by taking layout direction into
1447         * account.
1448         *
1449         * @param flags           The flag value that include any number of movement flags.
1450         * @param layoutDirection The layout direction of the View. Can be obtained from
1451         *                        {@link ViewCompat#getLayoutDirection(android.view.View)}.
1452         * @return Updated flags which uses relative flags ({@link #START}, {@link #END}) instead
1453         * of {@link #LEFT}, {@link #RIGHT}.
1454         * @see #convertToAbsoluteDirection(int, int)
1455         */
1456        public static int convertToRelativeDirection(int flags, int layoutDirection) {
1457            int masked = flags & ABS_HORIZONTAL_DIR_FLAGS;
1458            if (masked == 0) {
1459                return flags;// does not have any abs flags, good.
1460            }
1461            flags &= ~masked; //remove left / right.
1462            if (layoutDirection == ViewCompat.LAYOUT_DIRECTION_LTR) {
1463                // no change. just OR with 2 bits shifted mask and return
1464                flags |= masked << 2; // START is 2 bits after LEFT, END is 2 bits after RIGHT.
1465                return flags;
1466            } else {
1467                // add RIGHT flag as START
1468                flags |= ((masked << 1) & ~ABS_HORIZONTAL_DIR_FLAGS);
1469                // first clean RIGHT bit then add LEFT flag as END
1470                flags |= ((masked << 1) & ABS_HORIZONTAL_DIR_FLAGS) << 2;
1471            }
1472            return flags;
1473        }
1474
1475        /**
1476         * Convenience method to create movement flags.
1477         * <p>
1478         * For instance, if you want to let your items be drag & dropped vertically and swiped
1479         * left to be dismissed, you can call this method with:
1480         * <code>makeMovementFlags(UP | DOWN, LEFT);</code>
1481         *
1482         * @param dragFlags  The directions in which the item can be dragged.
1483         * @param swipeFlags The directions in which the item can be swiped.
1484         * @return Returns an integer composed of the given drag and swipe flags.
1485         */
1486        public static int makeMovementFlags(int dragFlags, int swipeFlags) {
1487            return makeFlag(ACTION_STATE_IDLE, swipeFlags | dragFlags) |
1488                    makeFlag(ACTION_STATE_SWIPE, swipeFlags) | makeFlag(ACTION_STATE_DRAG,
1489                    dragFlags);
1490        }
1491
1492        /**
1493         * Shifts the given direction flags to the offset of the given action state.
1494         *
1495         * @param actionState The action state you want to get flags in. Should be one of
1496         *                    {@link #ACTION_STATE_IDLE}, {@link #ACTION_STATE_SWIPE} or
1497         *                    {@link #ACTION_STATE_DRAG}.
1498         * @param directions  The direction flags. Can be composed from {@link #UP}, {@link #DOWN},
1499         *                    {@link #RIGHT}, {@link #LEFT} {@link #START} and {@link #END}.
1500         * @return And integer that represents the given directions in the provided actionState.
1501         */
1502        public static int makeFlag(int actionState, int directions) {
1503            return directions << (actionState * DIRECTION_FLAG_COUNT);
1504        }
1505
1506        /**
1507         * Should return a composite flag which defines the enabled move directions in each state
1508         * (idle, swiping, dragging).
1509         * <p>
1510         * Instead of composing this flag manually, you can use {@link #makeMovementFlags(int,
1511         * int)}
1512         * or {@link #makeFlag(int, int)}.
1513         * <p>
1514         * This flag is composed of 3 sets of 8 bits, where first 8 bits are for IDLE state, next
1515         * 8 bits are for SWIPE state and third 8 bits are for DRAG state.
1516         * Each 8 bit sections can be constructed by simply OR'ing direction flags defined in
1517         * {@link ItemTouchHelper}.
1518         * <p>
1519         * For example, if you want it to allow swiping LEFT and RIGHT but only allow starting to
1520         * swipe by swiping RIGHT, you can return:
1521         * <pre>
1522         *      makeFlag(ACTION_STATE_IDLE, RIGHT) | makeFlag(ACTION_STATE_SWIPE, LEFT | RIGHT);
1523         * </pre>
1524         * This means, allow right movement while IDLE and allow right and left movement while
1525         * swiping.
1526         *
1527         * @param recyclerView The RecyclerView to which ItemTouchHelper is attached.
1528         * @param viewHolder   The ViewHolder for which the movement information is necessary.
1529         * @return flags specifying which movements are allowed on this ViewHolder.
1530         * @see #makeMovementFlags(int, int)
1531         * @see #makeFlag(int, int)
1532         */
1533        public abstract int getMovementFlags(RecyclerView recyclerView,
1534                ViewHolder viewHolder);
1535
1536        /**
1537         * Converts a given set of flags to absolution direction which means {@link #START} and
1538         * {@link #END} are replaced with {@link #LEFT} and {@link #RIGHT} depending on the layout
1539         * direction.
1540         *
1541         * @param flags           The flag value that include any number of movement flags.
1542         * @param layoutDirection The layout direction of the RecyclerView.
1543         * @return Updated flags which includes only absolute direction values.
1544         */
1545        public int convertToAbsoluteDirection(int flags, int layoutDirection) {
1546            int masked = flags & RELATIVE_DIR_FLAGS;
1547            if (masked == 0) {
1548                return flags;// does not have any relative flags, good.
1549            }
1550            flags &= ~masked; //remove start / end
1551            if (layoutDirection == ViewCompat.LAYOUT_DIRECTION_LTR) {
1552                // no change. just OR with 2 bits shifted mask and return
1553                flags |= masked >> 2; // START is 2 bits after LEFT, END is 2 bits after RIGHT.
1554                return flags;
1555            } else {
1556                // add START flag as RIGHT
1557                flags |= ((masked >> 1) & ~RELATIVE_DIR_FLAGS);
1558                // first clean start bit then add END flag as LEFT
1559                flags |= ((masked >> 1) & RELATIVE_DIR_FLAGS) >> 2;
1560            }
1561            return flags;
1562        }
1563
1564        final int getAbsoluteMovementFlags(RecyclerView recyclerView,
1565                ViewHolder viewHolder) {
1566            final int flags = getMovementFlags(recyclerView, viewHolder);
1567            return convertToAbsoluteDirection(flags, ViewCompat.getLayoutDirection(recyclerView));
1568        }
1569
1570        boolean hasDragFlag(RecyclerView recyclerView, ViewHolder viewHolder) {
1571            final int flags = getAbsoluteMovementFlags(recyclerView, viewHolder);
1572            return (flags & ACTION_MODE_DRAG_MASK) != 0;
1573        }
1574
1575        boolean hasSwipeFlag(RecyclerView recyclerView,
1576                ViewHolder viewHolder) {
1577            final int flags = getAbsoluteMovementFlags(recyclerView, viewHolder);
1578            return (flags & ACTION_MODE_SWIPE_MASK) != 0;
1579        }
1580
1581        /**
1582         * Return true if the current ViewHolder can be dropped over the the target ViewHolder.
1583         * <p>
1584         * This method is used when selecting drop target for the dragged View. After Views are
1585         * eliminated either via bounds check or via this method, resulting set of views will be
1586         * passed to {@link #chooseDropTarget(ViewHolder, java.util.List, int, int)}.
1587         * <p>
1588         * Default implementation returns true.
1589         *
1590         * @param recyclerView The RecyclerView to which ItemTouchHelper is attached to.
1591         * @param current      The ViewHolder that user is dragging.
1592         * @param target       The ViewHolder which is below the dragged ViewHolder.
1593         * @return True if the dragged ViewHolder can be replaced with the target ViewHolder, false
1594         * otherwise.
1595         */
1596        public boolean canDropOver(RecyclerView recyclerView, ViewHolder current,
1597                ViewHolder target) {
1598            return true;
1599        }
1600
1601        /**
1602         * Called when ItemTouchHelper wants to move the dragged item from its old position to
1603         * the new position.
1604         * <p>
1605         * If this method returns true, ItemTouchHelper assumes {@code viewHolder} has been moved
1606         * to the adapter position of {@code target} ViewHolder
1607         * ({@link ViewHolder#getAdapterPosition()
1608         * ViewHolder#getAdapterPosition()}).
1609         * <p>
1610         * If you don't support drag & drop, this method will never be called.
1611         *
1612         * @param recyclerView The RecyclerView to which ItemTouchHelper is attached to.
1613         * @param viewHolder   The ViewHolder which is being dragged by the user.
1614         * @param target       The ViewHolder over which the currently active item is being
1615         *                     dragged.
1616         * @return True if the {@code viewHolder} has been moved to the adapter position of
1617         * {@code target}.
1618         * @see #onMoved(RecyclerView, ViewHolder, int, ViewHolder, int, int, int)
1619         */
1620        public abstract boolean onMove(RecyclerView recyclerView,
1621                ViewHolder viewHolder, ViewHolder target);
1622
1623        /**
1624         * Returns whether ItemTouchHelper should start a drag and drop operation if an item is
1625         * long pressed.
1626         * <p>
1627         * Default value returns true but you may want to disable this if you want to start
1628         * dragging on a custom view touch using {@link #startDrag(ViewHolder)}.
1629         *
1630         * @return True if ItemTouchHelper should start dragging an item when it is long pressed,
1631         * false otherwise. Default value is <code>true</code>.
1632         * @see #startDrag(ViewHolder)
1633         */
1634        public boolean isLongPressDragEnabled() {
1635            return true;
1636        }
1637
1638        /**
1639         * Returns whether ItemTouchHelper should start a swipe operation if a pointer is swiped
1640         * over the View.
1641         * <p>
1642         * Default value returns true but you may want to disable this if you want to start
1643         * swiping on a custom view touch using {@link #startSwipe(ViewHolder)}.
1644         *
1645         * @return True if ItemTouchHelper should start swiping an item when user swipes a pointer
1646         * over the View, false otherwise. Default value is <code>true</code>.
1647         * @see #startSwipe(ViewHolder)
1648         */
1649        public boolean isItemViewSwipeEnabled() {
1650            return true;
1651        }
1652
1653        /**
1654         * When finding views under a dragged view, by default, ItemTouchHelper searches for views
1655         * that overlap with the dragged View. By overriding this method, you can extend or shrink
1656         * the search box.
1657         *
1658         * @return The extra margin to be added to the hit box of the dragged View.
1659         */
1660        public int getBoundingBoxMargin() {
1661            return 0;
1662        }
1663
1664        /**
1665         * Returns the fraction that the user should move the View to be considered as swiped.
1666         * The fraction is calculated with respect to RecyclerView's bounds.
1667         * <p>
1668         * Default value is .5f, which means, to swipe a View, user must move the View at least
1669         * half of RecyclerView's width or height, depending on the swipe direction.
1670         *
1671         * @param viewHolder The ViewHolder that is being dragged.
1672         * @return A float value that denotes the fraction of the View size. Default value
1673         * is .5f .
1674         */
1675        public float getSwipeThreshold(ViewHolder viewHolder) {
1676            return .5f;
1677        }
1678
1679        /**
1680         * Returns the fraction that the user should move the View to be considered as it is
1681         * dragged. After a view is moved this amount, ItemTouchHelper starts checking for Views
1682         * below it for a possible drop.
1683         *
1684         * @param viewHolder The ViewHolder that is being dragged.
1685         * @return A float value that denotes the fraction of the View size. Default value is
1686         * .5f .
1687         */
1688        public float getMoveThreshold(ViewHolder viewHolder) {
1689            return .5f;
1690        }
1691
1692        /**
1693         * Defines the minimum velocity which will be considered as a swipe action by the user.
1694         * <p>
1695         * You can increase this value to make it harder to swipe or decrease it to make it easier.
1696         * Keep in mind that ItemTouchHelper also checks the perpendicular velocity and makes sure
1697         * current direction velocity is larger then the perpendicular one. Otherwise, user's
1698         * movement is ambiguous. You can change the threshold by overriding
1699         * {@link #getSwipeVelocityThreshold(float)}.
1700         * <p>
1701         * The velocity is calculated in pixels per second.
1702         * <p>
1703         * The default framework value is passed as a parameter so that you can modify it with a
1704         * multiplier.
1705         *
1706         * @param defaultValue The default value (in pixels per second) used by the
1707         *                     ItemTouchHelper.
1708         * @return The minimum swipe velocity. The default implementation returns the
1709         * <code>defaultValue</code> parameter.
1710         * @see #getSwipeVelocityThreshold(float)
1711         * @see #getSwipeThreshold(ViewHolder)
1712         */
1713        public float getSwipeEscapeVelocity(float defaultValue) {
1714            return defaultValue;
1715        }
1716
1717        /**
1718         * Defines the maximum velocity ItemTouchHelper will ever calculate for pointer movements.
1719         * <p>
1720         * To consider a movement as swipe, ItemTouchHelper requires it to be larger than the
1721         * perpendicular movement. If both directions reach to the max threshold, none of them will
1722         * be considered as a swipe because it is usually an indication that user rather tried to
1723         * scroll then swipe.
1724         * <p>
1725         * The velocity is calculated in pixels per second.
1726         * <p>
1727         * You can customize this behavior by changing this method. If you increase the value, it
1728         * will be easier for the user to swipe diagonally and if you decrease the value, user will
1729         * need to make a rather straight finger movement to trigger a swipe.
1730         *
1731         * @param defaultValue The default value(in pixels per second) used by the ItemTouchHelper.
1732         * @return The velocity cap for pointer movements. The default implementation returns the
1733         * <code>defaultValue</code> parameter.
1734         * @see #getSwipeEscapeVelocity(float)
1735         */
1736        public float getSwipeVelocityThreshold(float defaultValue) {
1737            return defaultValue;
1738        }
1739
1740        /**
1741         * Called by ItemTouchHelper to select a drop target from the list of ViewHolders that
1742         * are under the dragged View.
1743         * <p>
1744         * Default implementation filters the View with which dragged item have changed position
1745         * in the drag direction. For instance, if the view is dragged UP, it compares the
1746         * <code>view.getTop()</code> of the two views before and after drag started. If that value
1747         * is different, the target view passes the filter.
1748         * <p>
1749         * Among these Views which pass the test, the one closest to the dragged view is chosen.
1750         * <p>
1751         * This method is called on the main thread every time user moves the View. If you want to
1752         * override it, make sure it does not do any expensive operations.
1753         *
1754         * @param selected    The ViewHolder being dragged by the user.
1755         * @param dropTargets The list of ViewHolder that are under the dragged View and
1756         *                    candidate as a drop.
1757         * @param curX        The updated left value of the dragged View after drag translations
1758         *                    are applied. This value does not include margins added by
1759         *                    {@link RecyclerView.ItemDecoration}s.
1760         * @param curY        The updated top value of the dragged View after drag translations
1761         *                    are applied. This value does not include margins added by
1762         *                    {@link RecyclerView.ItemDecoration}s.
1763         * @return A ViewHolder to whose position the dragged ViewHolder should be
1764         * moved to.
1765         */
1766        public ViewHolder chooseDropTarget(ViewHolder selected,
1767                List<ViewHolder> dropTargets, int curX, int curY) {
1768            int right = curX + selected.itemView.getWidth();
1769            int bottom = curY + selected.itemView.getHeight();
1770            ViewHolder winner = null;
1771            int winnerScore = -1;
1772            final int dx = curX - selected.itemView.getLeft();
1773            final int dy = curY - selected.itemView.getTop();
1774            final int targetsSize = dropTargets.size();
1775            for (int i = 0; i < targetsSize; i++) {
1776                final ViewHolder target = dropTargets.get(i);
1777                if (dx > 0) {
1778                    int diff = target.itemView.getRight() - right;
1779                    if (diff < 0 && target.itemView.getRight() > selected.itemView.getRight()) {
1780                        final int score = Math.abs(diff);
1781                        if (score > winnerScore) {
1782                            winnerScore = score;
1783                            winner = target;
1784                        }
1785                    }
1786                }
1787                if (dx < 0) {
1788                    int diff = target.itemView.getLeft() - curX;
1789                    if (diff > 0 && target.itemView.getLeft() < selected.itemView.getLeft()) {
1790                        final int score = Math.abs(diff);
1791                        if (score > winnerScore) {
1792                            winnerScore = score;
1793                            winner = target;
1794                        }
1795                    }
1796                }
1797                if (dy < 0) {
1798                    int diff = target.itemView.getTop() - curY;
1799                    if (diff > 0 && target.itemView.getTop() < selected.itemView.getTop()) {
1800                        final int score = Math.abs(diff);
1801                        if (score > winnerScore) {
1802                            winnerScore = score;
1803                            winner = target;
1804                        }
1805                    }
1806                }
1807
1808                if (dy > 0) {
1809                    int diff = target.itemView.getBottom() - bottom;
1810                    if (diff < 0 && target.itemView.getBottom() > selected.itemView.getBottom()) {
1811                        final int score = Math.abs(diff);
1812                        if (score > winnerScore) {
1813                            winnerScore = score;
1814                            winner = target;
1815                        }
1816                    }
1817                }
1818            }
1819            return winner;
1820        }
1821
1822        /**
1823         * Called when a ViewHolder is swiped by the user.
1824         * <p>
1825         * If you are returning relative directions ({@link #START} , {@link #END}) from the
1826         * {@link #getMovementFlags(RecyclerView, ViewHolder)} method, this method
1827         * will also use relative directions. Otherwise, it will use absolute directions.
1828         * <p>
1829         * If you don't support swiping, this method will never be called.
1830         * <p>
1831         * ItemTouchHelper will keep a reference to the View until it is detached from
1832         * RecyclerView.
1833         * As soon as it is detached, ItemTouchHelper will call
1834         * {@link #clearView(RecyclerView, ViewHolder)}.
1835         *
1836         * @param viewHolder The ViewHolder which has been swiped by the user.
1837         * @param direction  The direction to which the ViewHolder is swiped. It is one of
1838         *                   {@link #UP}, {@link #DOWN},
1839         *                   {@link #LEFT} or {@link #RIGHT}. If your
1840         *                   {@link #getMovementFlags(RecyclerView, ViewHolder)}
1841         *                   method
1842         *                   returned relative flags instead of {@link #LEFT} / {@link #RIGHT};
1843         *                   `direction` will be relative as well. ({@link #START} or {@link
1844         *                   #END}).
1845         */
1846        public abstract void onSwiped(ViewHolder viewHolder, int direction);
1847
1848        /**
1849         * Called when the ViewHolder swiped or dragged by the ItemTouchHelper is changed.
1850         * <p/>
1851         * If you override this method, you should call super.
1852         *
1853         * @param viewHolder  The new ViewHolder that is being swiped or dragged. Might be null if
1854         *                    it is cleared.
1855         * @param actionState One of {@link ItemTouchHelper#ACTION_STATE_IDLE},
1856         *                    {@link ItemTouchHelper#ACTION_STATE_SWIPE} or
1857         *                    {@link ItemTouchHelper#ACTION_STATE_DRAG}.
1858         * @see #clearView(RecyclerView, RecyclerView.ViewHolder)
1859         */
1860        public void onSelectedChanged(ViewHolder viewHolder, int actionState) {
1861            if (viewHolder != null) {
1862                sUICallback.onSelected(viewHolder.itemView);
1863            }
1864        }
1865
1866        private int getMaxDragScroll(RecyclerView recyclerView) {
1867            if (mCachedMaxScrollSpeed == -1) {
1868                mCachedMaxScrollSpeed = recyclerView.getResources().getDimensionPixelSize(
1869                        R.dimen.item_touch_helper_max_drag_scroll_per_frame);
1870            }
1871            return mCachedMaxScrollSpeed;
1872        }
1873
1874        /**
1875         * Called when {@link #onMove(RecyclerView, ViewHolder, ViewHolder)} returns true.
1876         * <p>
1877         * ItemTouchHelper does not create an extra Bitmap or View while dragging, instead, it
1878         * modifies the existing View. Because of this reason, it is important that the View is
1879         * still part of the layout after it is moved. This may not work as intended when swapped
1880         * Views are close to RecyclerView bounds or there are gaps between them (e.g. other Views
1881         * which were not eligible for dropping over).
1882         * <p>
1883         * This method is responsible to give necessary hint to the LayoutManager so that it will
1884         * keep the View in visible area. For example, for LinearLayoutManager, this is as simple
1885         * as calling {@link LinearLayoutManager#scrollToPositionWithOffset(int, int)}.
1886         *
1887         * Default implementation calls {@link RecyclerView#scrollToPosition(int)} if the View's
1888         * new position is likely to be out of bounds.
1889         * <p>
1890         * It is important to ensure the ViewHolder will stay visible as otherwise, it might be
1891         * removed by the LayoutManager if the move causes the View to go out of bounds. In that
1892         * case, drag will end prematurely.
1893         *
1894         * @param recyclerView The RecyclerView controlled by the ItemTouchHelper.
1895         * @param viewHolder   The ViewHolder under user's control.
1896         * @param fromPos      The previous adapter position of the dragged item (before it was
1897         *                     moved).
1898         * @param target       The ViewHolder on which the currently active item has been dropped.
1899         * @param toPos        The new adapter position of the dragged item.
1900         * @param x            The updated left value of the dragged View after drag translations
1901         *                     are applied. This value does not include margins added by
1902         *                     {@link RecyclerView.ItemDecoration}s.
1903         * @param y            The updated top value of the dragged View after drag translations
1904         *                     are applied. This value does not include margins added by
1905         *                     {@link RecyclerView.ItemDecoration}s.
1906         */
1907        public void onMoved(final RecyclerView recyclerView,
1908                final ViewHolder viewHolder, int fromPos, final ViewHolder target, int toPos, int x,
1909                int y) {
1910            final RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
1911            if (layoutManager instanceof ViewDropHandler) {
1912                ((ViewDropHandler) layoutManager).prepareForDrop(viewHolder.itemView,
1913                        target.itemView, x, y);
1914                return;
1915            }
1916
1917            // if layout manager cannot handle it, do some guesswork
1918            if (layoutManager.canScrollHorizontally()) {
1919                final int minLeft = layoutManager.getDecoratedLeft(target.itemView);
1920                if (minLeft <= recyclerView.getPaddingLeft()) {
1921                    recyclerView.scrollToPosition(toPos);
1922                }
1923                final int maxRight = layoutManager.getDecoratedRight(target.itemView);
1924                if (maxRight >= recyclerView.getWidth() - recyclerView.getPaddingRight()) {
1925                    recyclerView.scrollToPosition(toPos);
1926                }
1927            }
1928
1929            if (layoutManager.canScrollVertically()) {
1930                final int minTop = layoutManager.getDecoratedTop(target.itemView);
1931                if (minTop <= recyclerView.getPaddingTop()) {
1932                    recyclerView.scrollToPosition(toPos);
1933                }
1934                final int maxBottom = layoutManager.getDecoratedBottom(target.itemView);
1935                if (maxBottom >= recyclerView.getHeight() - recyclerView.getPaddingBottom()) {
1936                    recyclerView.scrollToPosition(toPos);
1937                }
1938            }
1939        }
1940
1941        void onDraw(Canvas c, RecyclerView parent, ViewHolder selected,
1942                List<ItemTouchHelper.RecoverAnimation> recoverAnimationList,
1943                int actionState, float dX, float dY) {
1944            final int recoverAnimSize = recoverAnimationList.size();
1945            for (int i = 0; i < recoverAnimSize; i++) {
1946                final ItemTouchHelper.RecoverAnimation anim = recoverAnimationList.get(i);
1947                anim.update();
1948                final int count = c.save();
1949                onChildDraw(c, parent, anim.mViewHolder, anim.mX, anim.mY, anim.mActionState,
1950                        false);
1951                c.restoreToCount(count);
1952            }
1953            if (selected != null) {
1954                final int count = c.save();
1955                onChildDraw(c, parent, selected, dX, dY, actionState, true);
1956                c.restoreToCount(count);
1957            }
1958        }
1959
1960        void onDrawOver(Canvas c, RecyclerView parent, ViewHolder selected,
1961                List<ItemTouchHelper.RecoverAnimation> recoverAnimationList,
1962                int actionState, float dX, float dY) {
1963            final int recoverAnimSize = recoverAnimationList.size();
1964            for (int i = 0; i < recoverAnimSize; i++) {
1965                final ItemTouchHelper.RecoverAnimation anim = recoverAnimationList.get(i);
1966                final int count = c.save();
1967                onChildDrawOver(c, parent, anim.mViewHolder, anim.mX, anim.mY, anim.mActionState,
1968                        false);
1969                c.restoreToCount(count);
1970            }
1971            if (selected != null) {
1972                final int count = c.save();
1973                onChildDrawOver(c, parent, selected, dX, dY, actionState, true);
1974                c.restoreToCount(count);
1975            }
1976            boolean hasRunningAnimation = false;
1977            for (int i = recoverAnimSize - 1; i >= 0; i--) {
1978                final RecoverAnimation anim = recoverAnimationList.get(i);
1979                if (anim.mEnded && !anim.mIsPendingCleanup) {
1980                    recoverAnimationList.remove(i);
1981                } else if (!anim.mEnded) {
1982                    hasRunningAnimation = true;
1983                }
1984            }
1985            if (hasRunningAnimation) {
1986                parent.invalidate();
1987            }
1988        }
1989
1990        /**
1991         * Called by the ItemTouchHelper when the user interaction with an element is over and it
1992         * also completed its animation.
1993         * <p>
1994         * This is a good place to clear all changes on the View that was done in
1995         * {@link #onSelectedChanged(RecyclerView.ViewHolder, int)},
1996         * {@link #onChildDraw(Canvas, RecyclerView, ViewHolder, float, float, int,
1997         * boolean)} or
1998         * {@link #onChildDrawOver(Canvas, RecyclerView, ViewHolder, float, float, int, boolean)}.
1999         *
2000         * @param recyclerView The RecyclerView which is controlled by the ItemTouchHelper.
2001         * @param viewHolder   The View that was interacted by the user.
2002         */
2003        public void clearView(RecyclerView recyclerView, ViewHolder viewHolder) {
2004            sUICallback.clearView(viewHolder.itemView);
2005        }
2006
2007        /**
2008         * Called by ItemTouchHelper on RecyclerView's onDraw callback.
2009         * <p>
2010         * If you would like to customize how your View's respond to user interactions, this is
2011         * a good place to override.
2012         * <p>
2013         * Default implementation translates the child by the given <code>dX</code>,
2014         * <code>dY</code>.
2015         * ItemTouchHelper also takes care of drawing the child after other children if it is being
2016         * dragged. This is done using child re-ordering mechanism. On platforms prior to L, this
2017         * is
2018         * achieved via {@link android.view.ViewGroup#getChildDrawingOrder(int, int)} and on L
2019         * and after, it changes View's elevation value to be greater than all other children.)
2020         *
2021         * @param c                 The canvas which RecyclerView is drawing its children
2022         * @param recyclerView      The RecyclerView to which ItemTouchHelper is attached to
2023         * @param viewHolder        The ViewHolder which is being interacted by the User or it was
2024         *                          interacted and simply animating to its original position
2025         * @param dX                The amount of horizontal displacement caused by user's action
2026         * @param dY                The amount of vertical displacement caused by user's action
2027         * @param actionState       The type of interaction on the View. Is either {@link
2028         *                          #ACTION_STATE_DRAG} or {@link #ACTION_STATE_SWIPE}.
2029         * @param isCurrentlyActive True if this view is currently being controlled by the user or
2030         *                          false it is simply animating back to its original state.
2031         * @see #onChildDrawOver(Canvas, RecyclerView, ViewHolder, float, float, int,
2032         * boolean)
2033         */
2034        public void onChildDraw(Canvas c, RecyclerView recyclerView,
2035                ViewHolder viewHolder,
2036                float dX, float dY, int actionState, boolean isCurrentlyActive) {
2037            sUICallback.onDraw(c, recyclerView, viewHolder.itemView, dX, dY, actionState,
2038                    isCurrentlyActive);
2039        }
2040
2041        /**
2042         * Called by ItemTouchHelper on RecyclerView's onDraw callback.
2043         * <p>
2044         * If you would like to customize how your View's respond to user interactions, this is
2045         * a good place to override.
2046         * <p>
2047         * Default implementation translates the child by the given <code>dX</code>,
2048         * <code>dY</code>.
2049         * ItemTouchHelper also takes care of drawing the child after other children if it is being
2050         * dragged. This is done using child re-ordering mechanism. On platforms prior to L, this
2051         * is
2052         * achieved via {@link android.view.ViewGroup#getChildDrawingOrder(int, int)} and on L
2053         * and after, it changes View's elevation value to be greater than all other children.)
2054         *
2055         * @param c                 The canvas which RecyclerView is drawing its children
2056         * @param recyclerView      The RecyclerView to which ItemTouchHelper is attached to
2057         * @param viewHolder        The ViewHolder which is being interacted by the User or it was
2058         *                          interacted and simply animating to its original position
2059         * @param dX                The amount of horizontal displacement caused by user's action
2060         * @param dY                The amount of vertical displacement caused by user's action
2061         * @param actionState       The type of interaction on the View. Is either {@link
2062         *                          #ACTION_STATE_DRAG} or {@link #ACTION_STATE_SWIPE}.
2063         * @param isCurrentlyActive True if this view is currently being controlled by the user or
2064         *                          false it is simply animating back to its original state.
2065         * @see #onChildDrawOver(Canvas, RecyclerView, ViewHolder, float, float, int,
2066         * boolean)
2067         */
2068        public void onChildDrawOver(Canvas c, RecyclerView recyclerView,
2069                ViewHolder viewHolder,
2070                float dX, float dY, int actionState, boolean isCurrentlyActive) {
2071            sUICallback.onDrawOver(c, recyclerView, viewHolder.itemView, dX, dY, actionState,
2072                    isCurrentlyActive);
2073        }
2074
2075        /**
2076         * Called by the ItemTouchHelper when user action finished on a ViewHolder and now the View
2077         * will be animated to its final position.
2078         * <p>
2079         * Default implementation uses ItemAnimator's duration values. If
2080         * <code>animationType</code> is {@link #ANIMATION_TYPE_DRAG}, it returns
2081         * {@link RecyclerView.ItemAnimator#getMoveDuration()}, otherwise, it returns
2082         * {@link RecyclerView.ItemAnimator#getRemoveDuration()}. If RecyclerView does not have
2083         * any {@link RecyclerView.ItemAnimator} attached, this method returns
2084         * {@code DEFAULT_DRAG_ANIMATION_DURATION} or {@code DEFAULT_SWIPE_ANIMATION_DURATION}
2085         * depending on the animation type.
2086         *
2087         * @param recyclerView  The RecyclerView to which the ItemTouchHelper is attached to.
2088         * @param animationType The type of animation. Is one of {@link #ANIMATION_TYPE_DRAG},
2089         *                      {@link #ANIMATION_TYPE_SWIPE_CANCEL} or
2090         *                      {@link #ANIMATION_TYPE_SWIPE_SUCCESS}.
2091         * @param animateDx     The horizontal distance that the animation will offset
2092         * @param animateDy     The vertical distance that the animation will offset
2093         * @return The duration for the animation
2094         */
2095        public long getAnimationDuration(RecyclerView recyclerView, int animationType,
2096                float animateDx, float animateDy) {
2097            final RecyclerView.ItemAnimator itemAnimator = recyclerView.getItemAnimator();
2098            if (itemAnimator == null) {
2099                return animationType == ANIMATION_TYPE_DRAG ? DEFAULT_DRAG_ANIMATION_DURATION
2100                        : DEFAULT_SWIPE_ANIMATION_DURATION;
2101            } else {
2102                return animationType == ANIMATION_TYPE_DRAG ? itemAnimator.getMoveDuration()
2103                        : itemAnimator.getRemoveDuration();
2104            }
2105        }
2106
2107        /**
2108         * Called by the ItemTouchHelper when user is dragging a view out of bounds.
2109         * <p>
2110         * You can override this method to decide how much RecyclerView should scroll in response
2111         * to this action. Default implementation calculates a value based on the amount of View
2112         * out of bounds and the time it spent there. The longer user keeps the View out of bounds,
2113         * the faster the list will scroll. Similarly, the larger portion of the View is out of
2114         * bounds, the faster the RecyclerView will scroll.
2115         *
2116         * @param recyclerView        The RecyclerView instance to which ItemTouchHelper is
2117         *                            attached to.
2118         * @param viewSize            The total size of the View in scroll direction, excluding
2119         *                            item decorations.
2120         * @param viewSizeOutOfBounds The total size of the View that is out of bounds. This value
2121         *                            is negative if the View is dragged towards left or top edge.
2122         * @param totalSize           The total size of RecyclerView in the scroll direction.
2123         * @param msSinceStartScroll  The time passed since View is kept out of bounds.
2124         * @return The amount that RecyclerView should scroll. Keep in mind that this value will
2125         * be passed to {@link RecyclerView#scrollBy(int, int)} method.
2126         */
2127        public int interpolateOutOfBoundsScroll(RecyclerView recyclerView,
2128                int viewSize, int viewSizeOutOfBounds,
2129                int totalSize, long msSinceStartScroll) {
2130            final int maxScroll = getMaxDragScroll(recyclerView);
2131            final int absOutOfBounds = Math.abs(viewSizeOutOfBounds);
2132            final int direction = (int) Math.signum(viewSizeOutOfBounds);
2133            // might be negative if other direction
2134            float outOfBoundsRatio = Math.min(1f, 1f * absOutOfBounds / viewSize);
2135            final int cappedScroll = (int) (direction * maxScroll *
2136                    sDragViewScrollCapInterpolator.getInterpolation(outOfBoundsRatio));
2137            final float timeRatio;
2138            if (msSinceStartScroll > DRAG_SCROLL_ACCELERATION_LIMIT_TIME_MS) {
2139                timeRatio = 1f;
2140            } else {
2141                timeRatio = (float) msSinceStartScroll / DRAG_SCROLL_ACCELERATION_LIMIT_TIME_MS;
2142            }
2143            final int value = (int) (cappedScroll * sDragScrollInterpolator
2144                    .getInterpolation(timeRatio));
2145            if (value == 0) {
2146                return viewSizeOutOfBounds > 0 ? 1 : -1;
2147            }
2148            return value;
2149        }
2150    }
2151
2152    /**
2153     * A simple wrapper to the default Callback which you can construct with drag and swipe
2154     * directions and this class will handle the flag callbacks. You should still override onMove
2155     * or
2156     * onSwiped depending on your use case.
2157     *
2158     * <pre>
2159     * ItemTouchHelper mIth = new ItemTouchHelper(
2160     *     new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN,
2161     *         ItemTouchHelper.LEFT) {
2162     *         public abstract boolean onMove(RecyclerView recyclerView,
2163     *             ViewHolder viewHolder, ViewHolder target) {
2164     *             final int fromPos = viewHolder.getAdapterPosition();
2165     *             final int toPos = target.getAdapterPosition();
2166     *             // move item in `fromPos` to `toPos` in adapter.
2167     *             return true;// true if moved, false otherwise
2168     *         }
2169     *         public void onSwiped(ViewHolder viewHolder, int direction) {
2170     *             // remove from adapter
2171     *         }
2172     * });
2173     * </pre>
2174     */
2175    public abstract static class SimpleCallback extends Callback {
2176
2177        private int mDefaultSwipeDirs;
2178
2179        private int mDefaultDragDirs;
2180
2181        /**
2182         * Creates a Callback for the given drag and swipe allowance. These values serve as
2183         * defaults
2184         * and if you want to customize behavior per ViewHolder, you can override
2185         * {@link #getSwipeDirs(RecyclerView, ViewHolder)}
2186         * and / or {@link #getDragDirs(RecyclerView, ViewHolder)}.
2187         *
2188         * @param dragDirs  Binary OR of direction flags in which the Views can be dragged. Must be
2189         *                  composed of {@link #LEFT}, {@link #RIGHT}, {@link #START}, {@link
2190         *                  #END},
2191         *                  {@link #UP} and {@link #DOWN}.
2192         * @param swipeDirs Binary OR of direction flags in which the Views can be swiped. Must be
2193         *                  composed of {@link #LEFT}, {@link #RIGHT}, {@link #START}, {@link
2194         *                  #END},
2195         *                  {@link #UP} and {@link #DOWN}.
2196         */
2197        public SimpleCallback(int dragDirs, int swipeDirs) {
2198            mDefaultSwipeDirs = swipeDirs;
2199            mDefaultDragDirs = dragDirs;
2200        }
2201
2202        /**
2203         * Updates the default swipe directions. For example, you can use this method to toggle
2204         * certain directions depending on your use case.
2205         *
2206         * @param defaultSwipeDirs Binary OR of directions in which the ViewHolders can be swiped.
2207         */
2208        public void setDefaultSwipeDirs(int defaultSwipeDirs) {
2209            mDefaultSwipeDirs = defaultSwipeDirs;
2210        }
2211
2212        /**
2213         * Updates the default drag directions. For example, you can use this method to toggle
2214         * certain directions depending on your use case.
2215         *
2216         * @param defaultDragDirs Binary OR of directions in which the ViewHolders can be dragged.
2217         */
2218        public void setDefaultDragDirs(int defaultDragDirs) {
2219            mDefaultDragDirs = defaultDragDirs;
2220        }
2221
2222        /**
2223         * Returns the swipe directions for the provided ViewHolder.
2224         * Default implementation returns the swipe directions that was set via constructor or
2225         * {@link #setDefaultSwipeDirs(int)}.
2226         *
2227         * @param recyclerView The RecyclerView to which the ItemTouchHelper is attached to.
2228         * @param viewHolder   The RecyclerView for which the swipe direction is queried.
2229         * @return A binary OR of direction flags.
2230         */
2231        public int getSwipeDirs(RecyclerView recyclerView, ViewHolder viewHolder) {
2232            return mDefaultSwipeDirs;
2233        }
2234
2235        /**
2236         * Returns the drag directions for the provided ViewHolder.
2237         * Default implementation returns the drag directions that was set via constructor or
2238         * {@link #setDefaultDragDirs(int)}.
2239         *
2240         * @param recyclerView The RecyclerView to which the ItemTouchHelper is attached to.
2241         * @param viewHolder   The RecyclerView for which the swipe direction is queried.
2242         * @return A binary OR of direction flags.
2243         */
2244        public int getDragDirs(RecyclerView recyclerView, ViewHolder viewHolder) {
2245            return mDefaultDragDirs;
2246        }
2247
2248        @Override
2249        public int getMovementFlags(RecyclerView recyclerView, ViewHolder viewHolder) {
2250            return makeMovementFlags(getDragDirs(recyclerView, viewHolder),
2251                    getSwipeDirs(recyclerView, viewHolder));
2252        }
2253    }
2254
2255    private class ItemTouchHelperGestureListener extends GestureDetector.SimpleOnGestureListener {
2256
2257        ItemTouchHelperGestureListener() {
2258        }
2259
2260        @Override
2261        public boolean onDown(MotionEvent e) {
2262            return true;
2263        }
2264
2265        @Override
2266        public void onLongPress(MotionEvent e) {
2267            View child = findChildView(e);
2268            if (child != null) {
2269                ViewHolder vh = mRecyclerView.getChildViewHolder(child);
2270                if (vh != null) {
2271                    if (!mCallback.hasDragFlag(mRecyclerView, vh)) {
2272                        return;
2273                    }
2274                    int pointerId = e.getPointerId(0);
2275                    // Long press is deferred.
2276                    // Check w/ active pointer id to avoid selecting after motion
2277                    // event is canceled.
2278                    if (pointerId == mActivePointerId) {
2279                        final int index = e.findPointerIndex(mActivePointerId);
2280                        final float x = e.getX(index);
2281                        final float y = e.getY(index);
2282                        mInitialTouchX = x;
2283                        mInitialTouchY = y;
2284                        mDx = mDy = 0f;
2285                        if (DEBUG) {
2286                            Log.d(TAG,
2287                                    "onlong press: x:" + mInitialTouchX + ",y:" + mInitialTouchY);
2288                        }
2289                        if (mCallback.isLongPressDragEnabled()) {
2290                            select(vh, ACTION_STATE_DRAG);
2291                        }
2292                    }
2293                }
2294            }
2295        }
2296    }
2297
2298    private class RecoverAnimation implements Animator.AnimatorListener {
2299
2300        final float mStartDx;
2301
2302        final float mStartDy;
2303
2304        final float mTargetX;
2305
2306        final float mTargetY;
2307
2308        final ViewHolder mViewHolder;
2309
2310        final int mActionState;
2311
2312        private final ValueAnimator mValueAnimator;
2313
2314        final int mAnimationType;
2315
2316        public boolean mIsPendingCleanup;
2317
2318        float mX;
2319
2320        float mY;
2321
2322        // if user starts touching a recovering view, we put it into interaction mode again,
2323        // instantly.
2324        boolean mOverridden = false;
2325
2326        boolean mEnded = false;
2327
2328        private float mFraction;
2329
2330        public RecoverAnimation(ViewHolder viewHolder, int animationType,
2331                int actionState, float startDx, float startDy, float targetX, float targetY) {
2332            mActionState = actionState;
2333            mAnimationType = animationType;
2334            mViewHolder = viewHolder;
2335            mStartDx = startDx;
2336            mStartDy = startDy;
2337            mTargetX = targetX;
2338            mTargetY = targetY;
2339            mValueAnimator = ValueAnimator.ofFloat(0f, 1f);
2340            mValueAnimator.addUpdateListener(
2341                    new ValueAnimator.AnimatorUpdateListener() {
2342                        @Override
2343                        public void onAnimationUpdate(ValueAnimator animation) {
2344                            setFraction(animation.getAnimatedFraction());
2345                        }
2346                    });
2347            mValueAnimator.setTarget(viewHolder.itemView);
2348            mValueAnimator.addListener(this);
2349            setFraction(0f);
2350        }
2351
2352        public void setDuration(long duration) {
2353            mValueAnimator.setDuration(duration);
2354        }
2355
2356        public void start() {
2357            mViewHolder.setIsRecyclable(false);
2358            mValueAnimator.start();
2359        }
2360
2361        public void cancel() {
2362            mValueAnimator.cancel();
2363        }
2364
2365        public void setFraction(float fraction) {
2366            mFraction = fraction;
2367        }
2368
2369        /**
2370         * We run updates on onDraw method but use the fraction from animator callback.
2371         * This way, we can sync translate x/y values w/ the animators to avoid one-off frames.
2372         */
2373        public void update() {
2374            if (mStartDx == mTargetX) {
2375                mX = ViewCompat.getTranslationX(mViewHolder.itemView);
2376            } else {
2377                mX = mStartDx + mFraction * (mTargetX - mStartDx);
2378            }
2379            if (mStartDy == mTargetY) {
2380                mY = ViewCompat.getTranslationY(mViewHolder.itemView);
2381            } else {
2382                mY = mStartDy + mFraction * (mTargetY - mStartDy);
2383            }
2384        }
2385
2386        @Override
2387        public void onAnimationStart(Animator animation) {
2388
2389        }
2390
2391        @Override
2392        public void onAnimationEnd(Animator animation) {
2393            if (!mEnded) {
2394                mViewHolder.setIsRecyclable(true);
2395            }
2396            mEnded = true;
2397        }
2398
2399        @Override
2400        public void onAnimationCancel(Animator animation) {
2401            setFraction(1f); //make sure we recover the view's state.
2402        }
2403
2404        @Override
2405        public void onAnimationRepeat(Animator animation) {
2406
2407        }
2408    }
2409}