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