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