ItemTouchHelper.java revision e71a1df9b3c0e1bd3c21a1b3dd20a41790d4a950
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            final int s = scrollX;
735            scrollX = mCallback.interpolateOutOfBoundsScroll(mRecyclerView,
736                    mSelected.itemView.getWidth(), scrollX,
737                    mRecyclerView.getWidth(), scrollDuration);
738        }
739        if (scrollY != 0) {
740            final int s = scrollY;
741            scrollY = mCallback.interpolateOutOfBoundsScroll(mRecyclerView,
742                    mSelected.itemView.getHeight(), scrollY,
743                    mRecyclerView.getHeight(), scrollDuration);
744        }
745        if (scrollX != 0 || scrollY != 0) {
746            if (mDragScrollStartTimeInMs == Long.MIN_VALUE) {
747                mDragScrollStartTimeInMs = now;
748            }
749            mRecyclerView.scrollBy(scrollX, scrollY);
750            return true;
751        }
752        mDragScrollStartTimeInMs = Long.MIN_VALUE;
753        return false;
754    }
755
756    private List<ViewHolder> findSwapTargets(ViewHolder viewHolder) {
757        if (mSwapTargets == null) {
758            mSwapTargets = new ArrayList<ViewHolder>();
759            mDistances = new ArrayList<Integer>();
760        } else {
761            mSwapTargets.clear();
762            mDistances.clear();
763        }
764        final int margin = mCallback.getBoundingBoxMargin();
765        final int left = Math.round(mSelectedStartX + mDx) - margin;
766        final int top = Math.round(mSelectedStartY + mDy) - margin;
767        final int right = left + viewHolder.itemView.getWidth() + 2 * margin;
768        final int bottom = top + viewHolder.itemView.getHeight() + 2 * margin;
769        final int centerX = (left + right) / 2;
770        final int centerY = (top + bottom) / 2;
771        final RecyclerView.LayoutManager lm = mRecyclerView.getLayoutManager();
772        final int childCount = lm.getChildCount();
773        for (int i = 0; i < childCount; i++) {
774            View other = lm.getChildAt(i);
775            if (other == viewHolder.itemView) {
776                continue;//myself!
777            }
778            if (other.getBottom() < top || other.getTop() > bottom
779                    || other.getRight() < left || other.getLeft() > right) {
780                continue;
781            }
782            final ViewHolder otherVh = mRecyclerView.getChildViewHolder(other);
783            if (mCallback.canDropOver(mRecyclerView, mSelected, otherVh)) {
784                // find the index to add
785                final int dx = Math.abs(centerX - (other.getLeft() + other.getRight()) / 2);
786                final int dy = Math.abs(centerY - (other.getTop() + other.getBottom()) / 2);
787                final int dist = dx * dx + dy * dy;
788
789                int pos = 0;
790                final int cnt = mSwapTargets.size();
791                for (int j = 0; j < cnt; j++) {
792                    if (dist > mDistances.get(j)) {
793                        pos++;
794                    } else {
795                        break;
796                    }
797                }
798                mSwapTargets.add(pos, otherVh);
799                mDistances.add(pos, dist);
800            }
801        }
802        return mSwapTargets;
803    }
804
805    /**
806     * Checks if we should swap w/ another view holder.
807     */
808    private void moveIfNecessary(ViewHolder viewHolder) {
809        if (mRecyclerView.isLayoutRequested()) {
810            return;
811        }
812        if (mActionState != ACTION_STATE_DRAG) {
813            return;
814        }
815
816        final float threshold = mCallback.getMoveThreshold(viewHolder);
817        final int x = (int) (mSelectedStartX + mDx);
818        final int y = (int) (mSelectedStartY + mDy);
819        if (Math.abs(y - viewHolder.itemView.getTop()) < viewHolder.itemView.getHeight() * threshold
820                && Math.abs(x - viewHolder.itemView.getLeft())
821                < viewHolder.itemView.getWidth() * threshold) {
822            return;
823        }
824        List<ViewHolder> swapTargets = findSwapTargets(viewHolder);
825        if (swapTargets.size() == 0) {
826            return;
827        }
828        // may swap.
829        ViewHolder target = mCallback.chooseDropTarget(viewHolder, swapTargets, x, y);
830        if (target == null) {
831            mSwapTargets.clear();
832            mDistances.clear();
833            return;
834        }
835        final int toPosition = target.getAdapterPosition();
836        final int fromPosition = viewHolder.getAdapterPosition();
837        if (mCallback.onMove(mRecyclerView, viewHolder, target)) {
838            // keep target visible
839            mCallback.onMoved(mRecyclerView, viewHolder, fromPosition,
840                    target, toPosition, x, y);
841            return;
842        }
843    }
844
845    @Override
846    public void onChildViewAttachedToWindow(View view) {
847    }
848
849    @Override
850    public void onChildViewDetachedFromWindow(View view) {
851        removeChildDrawingOrderCallbackIfNecessary(view);
852        final ViewHolder holder = mRecyclerView.getChildViewHolder(view);
853        if (holder == null) {
854            return;
855        }
856        if (mSelected != null && holder == mSelected) {
857            select(null, ACTION_STATE_IDLE);
858        } else {
859            endRecoverAnimation(holder, false); // this may push it into pending cleanup list.
860            if (mPendingCleanup.remove(holder.itemView)) {
861                mCallback.clearView(mRecyclerView, holder);
862            }
863        }
864    }
865
866    /**
867     * Returns the animation type or 0 if cannot be found.
868     */
869    private int endRecoverAnimation(ViewHolder viewHolder, boolean override) {
870        final int recoverAnimSize = mRecoverAnimations.size();
871        for (int i = recoverAnimSize - 1; i >= 0; i--) {
872            final RecoverAnimation anim = mRecoverAnimations.get(i);
873            if (anim.mViewHolder == viewHolder) {
874                anim.mOverridden |= override;
875                if (!anim.mEnded) {
876                    anim.cancel();
877                }
878                mRecoverAnimations.remove(i);
879                anim.mViewHolder.setIsRecyclable(true);
880                return anim.mAnimationType;
881            }
882        }
883        return 0;
884    }
885
886    @Override
887    public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
888            RecyclerView.State state) {
889        outRect.setEmpty();
890    }
891
892    private void obtainVelocityTracker() {
893        if (mVelocityTracker != null) {
894            mVelocityTracker.recycle();
895        }
896        mVelocityTracker = VelocityTracker.obtain();
897    }
898
899    private void releaseVelocityTracker() {
900        if (mVelocityTracker != null) {
901            mVelocityTracker.recycle();
902            mVelocityTracker = null;
903        }
904    }
905
906    private ViewHolder findSwipedView(MotionEvent motionEvent) {
907        final RecyclerView.LayoutManager lm = mRecyclerView.getLayoutManager();
908        if (mActivePointerId == ACTIVE_POINTER_ID_NONE) {
909            return null;
910        }
911        final int pointerIndex = MotionEventCompat.findPointerIndex(motionEvent, mActivePointerId);
912        final float dx = MotionEventCompat.getX(motionEvent, pointerIndex) - mInitialTouchX;
913        final float dy = MotionEventCompat.getY(motionEvent, pointerIndex) - mInitialTouchY;
914        final float absDx = Math.abs(dx);
915        final float absDy = Math.abs(dy);
916
917        if (absDx < mSlop && absDy < mSlop) {
918            return null;
919        }
920        if (absDx > absDy && lm.canScrollHorizontally()) {
921            return null;
922        } else if (absDy > absDx && lm.canScrollVertically()) {
923            return null;
924        }
925        View child = findChildView(motionEvent);
926        if (child == null) {
927            return null;
928        }
929        return mRecyclerView.getChildViewHolder(child);
930    }
931
932    /**
933     * Checks whether we should select a View for swiping.
934     */
935    private boolean checkSelectForSwipe(int action, MotionEvent motionEvent, int pointerIndex) {
936        if (mSelected != null || action != MotionEvent.ACTION_MOVE
937                || mActionState == ACTION_STATE_DRAG || !mCallback.isItemViewSwipeEnabled()) {
938            return false;
939        }
940        if (mRecyclerView.getScrollState() == RecyclerView.SCROLL_STATE_DRAGGING) {
941            return false;
942        }
943        final ViewHolder vh = findSwipedView(motionEvent);
944        if (vh == null) {
945            return false;
946        }
947        final int movementFlags = mCallback.getAbsoluteMovementFlags(mRecyclerView, vh);
948
949        final int swipeFlags = (movementFlags & ACTION_MODE_SWIPE_MASK)
950                >> (DIRECTION_FLAG_COUNT * ACTION_STATE_SWIPE);
951
952        if (swipeFlags == 0) {
953            return false;
954        }
955
956        // mDx and mDy are only set in allowed directions. We use custom x/y here instead of
957        // updateDxDy to avoid swiping if user moves more in the other direction
958        final float x = MotionEventCompat.getX(motionEvent, pointerIndex);
959        final float y = MotionEventCompat.getY(motionEvent, pointerIndex);
960
961        // Calculate the distance moved
962        final float dx = x - mInitialTouchX;
963        final float dy = y - mInitialTouchY;
964        // swipe target is chose w/o applying flags so it does not really check if swiping in that
965        // direction is allowed. This why here, we use mDx mDy to check slope value again.
966        final float absDx = Math.abs(dx);
967        final float absDy = Math.abs(dy);
968
969        if (absDx < mSlop && absDy < mSlop) {
970            return false;
971        }
972        if (absDx > absDy) {
973            if (dx < 0 && (swipeFlags & LEFT) == 0) {
974                return false;
975            }
976            if (dx > 0 && (swipeFlags & RIGHT) == 0) {
977                return false;
978            }
979        } else {
980            if (dy < 0 && (swipeFlags & UP) == 0) {
981                return false;
982            }
983            if (dy > 0 && (swipeFlags & DOWN) == 0) {
984                return false;
985            }
986        }
987        mDx = mDy = 0f;
988        mActivePointerId = MotionEventCompat.getPointerId(motionEvent, 0);
989        select(vh, ACTION_STATE_SWIPE);
990        return true;
991    }
992
993    private View findChildView(MotionEvent event) {
994        // first check elevated views, if none, then call RV
995        final float x = event.getX();
996        final float y = event.getY();
997        if (mSelected != null) {
998            final View selectedView = mSelected.itemView;
999            if (hitTest(selectedView, x, y, mSelectedStartX + mDx, mSelectedStartY + mDy)) {
1000                return selectedView;
1001            }
1002        }
1003        for (int i = mRecoverAnimations.size() - 1; i >= 0; i--) {
1004            final RecoverAnimation anim = mRecoverAnimations.get(i);
1005            final View view = anim.mViewHolder.itemView;
1006            if (hitTest(view, x, y, anim.mX, anim.mY)) {
1007                return view;
1008            }
1009        }
1010        return mRecyclerView.findChildViewUnder(x, y);
1011    }
1012
1013    /**
1014     * Starts dragging the provided ViewHolder. By default, ItemTouchHelper starts a drag when a
1015     * View is long pressed. You can disable that behavior via
1016     * {@link ItemTouchHelper.Callback#isLongPressDragEnabled()}.
1017     * <p>
1018     * For this method to work:
1019     * <ul>
1020     * <li>The provided ViewHolder must be a child of the RecyclerView to which this
1021     * ItemTouchHelper
1022     * is attached.</li>
1023     * <li>{@link ItemTouchHelper.Callback} must have dragging enabled.</li>
1024     * <li>There must be a previous touch event that was reported to the ItemTouchHelper
1025     * through RecyclerView's ItemTouchListener mechanism. As long as no other ItemTouchListener
1026     * grabs previous events, this should work as expected.</li>
1027     * </ul>
1028     *
1029     * For example, if you would like to let your user to be able to drag an Item by touching one
1030     * of its descendants, you may implement it as follows:
1031     * <pre>
1032     *     viewHolder.dragButton.setOnTouchListener(new View.OnTouchListener() {
1033     *         public boolean onTouch(View v, MotionEvent event) {
1034     *             if (MotionEventCompat.getActionMasked(event) == MotionEvent.ACTION_DOWN) {
1035     *                 mItemTouchHelper.startDrag(viewHolder);
1036     *             }
1037     *             return false;
1038     *         }
1039     *     });
1040     * </pre>
1041     * <p>
1042     *
1043     * @param viewHolder The ViewHolder to start dragging. It must be a direct child of
1044     *                   RecyclerView.
1045     * @see ItemTouchHelper.Callback#isItemViewSwipeEnabled()
1046     */
1047    public void startDrag(ViewHolder viewHolder) {
1048        if (!mCallback.hasDragFlag(mRecyclerView, viewHolder)) {
1049            Log.e(TAG, "Start drag has been called but swiping is not enabled");
1050            return;
1051        }
1052        if (viewHolder.itemView.getParent() != mRecyclerView) {
1053            Log.e(TAG, "Start drag has been called with a view holder which is not a child of "
1054                    + "the RecyclerView which is controlled by this ItemTouchHelper.");
1055            return;
1056        }
1057        obtainVelocityTracker();
1058        mDx = mDy = 0f;
1059        select(viewHolder, ACTION_STATE_DRAG);
1060    }
1061
1062    /**
1063     * Starts swiping the provided ViewHolder. By default, ItemTouchHelper starts swiping a View
1064     * when user swipes their finger (or mouse pointer) over the View. You can disable this
1065     * behavior
1066     * by overriding {@link ItemTouchHelper.Callback}
1067     * <p>
1068     * For this method to work:
1069     * <ul>
1070     * <li>The provided ViewHolder must be a child of the RecyclerView to which this
1071     * ItemTouchHelper is attached.</li>
1072     * <li>{@link ItemTouchHelper.Callback} must have swiping enabled.</li>
1073     * <li>There must be a previous touch event that was reported to the ItemTouchHelper
1074     * through RecyclerView's ItemTouchListener mechanism. As long as no other ItemTouchListener
1075     * grabs previous events, this should work as expected.</li>
1076     * </ul>
1077     *
1078     * For example, if you would like to let your user to be able to swipe an Item by touching one
1079     * of its descendants, you may implement it as follows:
1080     * <pre>
1081     *     viewHolder.dragButton.setOnTouchListener(new View.OnTouchListener() {
1082     *         public boolean onTouch(View v, MotionEvent event) {
1083     *             if (MotionEventCompat.getActionMasked(event) == MotionEvent.ACTION_DOWN) {
1084     *                 mItemTouchHelper.startSwipe(viewHolder);
1085     *             }
1086     *             return false;
1087     *         }
1088     *     });
1089     * </pre>
1090     *
1091     * @param viewHolder The ViewHolder to start swiping. It must be a direct child of
1092     *                   RecyclerView.
1093     */
1094    public void startSwipe(ViewHolder viewHolder) {
1095        if (!mCallback.hasSwipeFlag(mRecyclerView, viewHolder)) {
1096            Log.e(TAG, "Start swipe has been called but dragging is not enabled");
1097            return;
1098        }
1099        if (viewHolder.itemView.getParent() != mRecyclerView) {
1100            Log.e(TAG, "Start swipe has been called with a view holder which is not a child of "
1101                    + "the RecyclerView controlled by this ItemTouchHelper.");
1102            return;
1103        }
1104        obtainVelocityTracker();
1105        mDx = mDy = 0f;
1106        select(viewHolder, ACTION_STATE_SWIPE);
1107    }
1108
1109    private RecoverAnimation findAnimation(MotionEvent event) {
1110        if (mRecoverAnimations.isEmpty()) {
1111            return null;
1112        }
1113        View target = findChildView(event);
1114        for (int i = mRecoverAnimations.size() - 1; i >= 0; i--) {
1115            final RecoverAnimation anim = mRecoverAnimations.get(i);
1116            if (anim.mViewHolder.itemView == target) {
1117                return anim;
1118            }
1119        }
1120        return null;
1121    }
1122
1123    private void updateDxDy(MotionEvent ev, int directionFlags, int pointerIndex) {
1124        final float x = MotionEventCompat.getX(ev, pointerIndex);
1125        final float y = MotionEventCompat.getY(ev, pointerIndex);
1126
1127        // Calculate the distance moved
1128        mDx = x - mInitialTouchX;
1129        mDy = y - mInitialTouchY;
1130        if ((directionFlags & LEFT) == 0) {
1131            mDx = Math.max(0, mDx);
1132        }
1133        if ((directionFlags & RIGHT) == 0) {
1134            mDx = Math.min(0, mDx);
1135        }
1136        if ((directionFlags & UP) == 0) {
1137            mDy = Math.max(0, mDy);
1138        }
1139        if ((directionFlags & DOWN) == 0) {
1140            mDy = Math.min(0, mDy);
1141        }
1142    }
1143
1144    private int swipeIfNecessary(ViewHolder viewHolder) {
1145        if (mActionState == ACTION_STATE_DRAG) {
1146            return 0;
1147        }
1148        final int originalMovementFlags = mCallback.getMovementFlags(mRecyclerView, viewHolder);
1149        final int absoluteMovementFlags = mCallback.convertToAbsoluteDirection(
1150                originalMovementFlags,
1151                ViewCompat.getLayoutDirection(mRecyclerView));
1152        final int flags = (absoluteMovementFlags
1153                & ACTION_MODE_SWIPE_MASK) >> (ACTION_STATE_SWIPE * DIRECTION_FLAG_COUNT);
1154        if (flags == 0) {
1155            return 0;
1156        }
1157        final int originalFlags = (originalMovementFlags
1158                & ACTION_MODE_SWIPE_MASK) >> (ACTION_STATE_SWIPE * DIRECTION_FLAG_COUNT);
1159        int swipeDir;
1160        if (Math.abs(mDx) > Math.abs(mDy)) {
1161            if ((swipeDir = checkHorizontalSwipe(viewHolder, flags)) > 0) {
1162                // if swipe dir is not in original flags, it should be the relative direction
1163                if ((originalFlags & swipeDir) == 0) {
1164                    // convert to relative
1165                    return Callback.convertToRelativeDirection(swipeDir,
1166                            ViewCompat.getLayoutDirection(mRecyclerView));
1167                }
1168                return swipeDir;
1169            }
1170            if ((swipeDir = checkVerticalSwipe(viewHolder, flags)) > 0) {
1171                return swipeDir;
1172            }
1173        } else {
1174            if ((swipeDir = checkVerticalSwipe(viewHolder, flags)) > 0) {
1175                return swipeDir;
1176            }
1177            if ((swipeDir = checkHorizontalSwipe(viewHolder, flags)) > 0) {
1178                // if swipe dir is not in original flags, it should be the relative direction
1179                if ((originalFlags & swipeDir) == 0) {
1180                    // convert to relative
1181                    return Callback.convertToRelativeDirection(swipeDir,
1182                            ViewCompat.getLayoutDirection(mRecyclerView));
1183                }
1184                return swipeDir;
1185            }
1186        }
1187        return 0;
1188    }
1189
1190    private int checkHorizontalSwipe(ViewHolder viewHolder, int flags) {
1191        if ((flags & (LEFT | RIGHT)) != 0) {
1192            final int dirFlag = mDx > 0 ? RIGHT : LEFT;
1193            if (mVelocityTracker != null && mActivePointerId > -1) {
1194                final float xVelocity = VelocityTrackerCompat
1195                        .getXVelocity(mVelocityTracker, mActivePointerId);
1196                final int velDirFlag = xVelocity > 0f ? RIGHT : LEFT;
1197                if ((velDirFlag & flags) != 0 && dirFlag == velDirFlag &&
1198                        Math.abs(xVelocity) >= mRecyclerView.getMinFlingVelocity()) {
1199                    return velDirFlag;
1200                }
1201            }
1202
1203            final float threshold = mRecyclerView.getWidth() * mCallback
1204                    .getSwipeThreshold(viewHolder);
1205
1206            if ((flags & dirFlag) != 0 && Math.abs(mDx) > threshold) {
1207                return dirFlag;
1208            }
1209        }
1210        return 0;
1211    }
1212
1213    private int checkVerticalSwipe(ViewHolder viewHolder, int flags) {
1214        if ((flags & (UP | DOWN)) != 0) {
1215            final int dirFlag = mDy > 0 ? DOWN : UP;
1216            if (mVelocityTracker != null && mActivePointerId > -1) {
1217                final float yVelocity = VelocityTrackerCompat
1218                        .getYVelocity(mVelocityTracker, mActivePointerId);
1219                final int velDirFlag = yVelocity > 0f ? DOWN : UP;
1220                if ((velDirFlag & flags) != 0 && velDirFlag == dirFlag &&
1221                        Math.abs(yVelocity) >= mRecyclerView.getMinFlingVelocity()) {
1222                    return velDirFlag;
1223                }
1224            }
1225
1226            final float threshold = mRecyclerView.getHeight() * mCallback
1227                    .getSwipeThreshold(viewHolder);
1228            if ((flags & dirFlag) != 0 && Math.abs(mDy) > threshold) {
1229                return dirFlag;
1230            }
1231        }
1232        return 0;
1233    }
1234
1235    private void addChildDrawingOrderCallback() {
1236        if (Build.VERSION.SDK_INT >= 21) {
1237            return;// we use elevation on Lollipop
1238        }
1239        if (mChildDrawingOrderCallback == null) {
1240            mChildDrawingOrderCallback = new RecyclerView.ChildDrawingOrderCallback() {
1241                @Override
1242                public int onGetChildDrawingOrder(int childCount, int i) {
1243                    if (mOverdrawChild == null) {
1244                        return i;
1245                    }
1246                    int childPosition = mOverdrawChildPosition;
1247                    if (childPosition == -1) {
1248                        childPosition = mRecyclerView.indexOfChild(mOverdrawChild);
1249                        mOverdrawChildPosition = childPosition;
1250                    }
1251                    if (i == childCount - 1) {
1252                        return childPosition;
1253                    }
1254                    return i < childPosition ? i : i + 1;
1255                }
1256            };
1257        }
1258        mRecyclerView.setChildDrawingOrderCallback(mChildDrawingOrderCallback);
1259    }
1260
1261    private void removeChildDrawingOrderCallbackIfNecessary(View view) {
1262        if (view == mOverdrawChild) {
1263            mOverdrawChild = null;
1264            // only remove if we've added
1265            if (mChildDrawingOrderCallback != null) {
1266                mRecyclerView.setChildDrawingOrderCallback(null);
1267            }
1268        }
1269    }
1270
1271    /**
1272     * An interface which can be implemented by LayoutManager for better integration with
1273     * {@link ItemTouchHelper}.
1274     */
1275    public static interface ViewDropHandler {
1276
1277        /**
1278         * Called by the {@link ItemTouchHelper} after a View is dropped over another View.
1279         * <p>
1280         * A LayoutManager should implement this interface to get ready for the upcoming move
1281         * operation.
1282         * <p>
1283         * For example, LinearLayoutManager sets up a "scrollToPositionWithOffset" calls so that
1284         * the View under drag will be used as an anchor View while calculating the next layout,
1285         * making layout stay consistent.
1286         *
1287         * @param view   The View which is being dragged. It is very likely that user is still
1288         *               dragging this View so there might be other
1289         *               {@link #prepareForDrop(View, View, int, int)} after this one.
1290         * @param target The target view which is being dropped on.
1291         * @param x      The <code>left</code> offset of the View that is being dragged. This value
1292         *               includes the movement caused by the user.
1293         * @param y      The <code>top</code> offset of the View that is being dragged. This value
1294         *               includes the movement caused by the user.
1295         */
1296        public void prepareForDrop(View view, View target, int x, int y);
1297    }
1298
1299    /**
1300     * This class is the contract between ItemTouchHelper and your application. It lets you control
1301     * which touch behaviors are enabled per each ViewHolder and also receive callbacks when user
1302     * performs these actions.
1303     * <p>
1304     * To control which actions user can take on each view, you should override
1305     * {@link #getMovementFlags(RecyclerView, ViewHolder)} and return appropriate set
1306     * of direction flags. ({@link #LEFT}, {@link #RIGHT}, {@link #START}, {@link #END},
1307     * {@link #UP}, {@link #DOWN}). You can use
1308     * {@link #makeMovementFlags(int, int)} to easily construct it. Alternatively, you can use
1309     * {@link SimpleCallback}.
1310     * <p>
1311     * If user drags an item, ItemTouchHelper will call
1312     * {@link Callback#onMove(RecyclerView, ViewHolder, ViewHolder)
1313     * onMove(recyclerView, dragged, target)}.
1314     * Upon receiving this callback, you should move the item from the old position
1315     * ({@code dragged.getAdapterPosition()}) to new position ({@code target.getAdapterPosition()})
1316     * in your adapter and also call {@link RecyclerView.Adapter#notifyItemMoved(int, int)}.
1317     * To control where a View can be dropped, you can override
1318     * {@link #canDropOver(RecyclerView, ViewHolder, ViewHolder)}. When a
1319     * dragging View overlaps multiple other views, Callback chooses the closest View with which
1320     * dragged View might have changed positions. Although this approach works for many use cases,
1321     * if you have a custom LayoutManager, you can override
1322     * {@link #chooseDropTarget(ViewHolder, java.util.List, int, int)} to select a
1323     * custom drop target.
1324     * <p>
1325     * When a View is swiped, ItemTouchHelper animates it until it goes out of bounds, then calls
1326     * {@link #onSwiped(ViewHolder, int)}. At this point, you should update your
1327     * adapter (e.g. remove the item) and call related Adapter#notify event.
1328     */
1329    @SuppressWarnings("UnusedParameters")
1330    public abstract static class Callback {
1331
1332        public static final int DEFAULT_DRAG_ANIMATION_DURATION = 200;
1333
1334        public static final int DEFAULT_SWIPE_ANIMATION_DURATION = 250;
1335
1336        static final int RELATIVE_DIR_FLAGS = START | END |
1337                ((START | END) << DIRECTION_FLAG_COUNT) |
1338                ((START | END) << (2 * DIRECTION_FLAG_COUNT));
1339
1340        private static final ItemTouchUICompat mUICallback;
1341
1342        private static final int ABS_HORIZONTAL_DIR_FLAGS = LEFT | RIGHT |
1343                ((LEFT | RIGHT) << DIRECTION_FLAG_COUNT) |
1344                ((LEFT | RIGHT) << (2 * DIRECTION_FLAG_COUNT));
1345
1346        private static final Interpolator sDragScrollInterpolator = new Interpolator() {
1347            public float getInterpolation(float t) {
1348                return t * t * t * t * t;
1349            }
1350        };
1351
1352        private static final Interpolator sDragViewScrollCapInterpolator = new Interpolator() {
1353            public float getInterpolation(float t) {
1354                t -= 1.0f;
1355                return t * t * t * t * t + 1.0f;
1356            }
1357        };
1358
1359        /**
1360         * Drag scroll speed keeps accelerating until this many milliseconds before being capped.
1361         */
1362        private static final long DRAG_SCROLL_ACCELERATION_LIMIT_TIME_MS = 2000;
1363
1364        private int mCachedMaxScrollSpeed = -1;
1365
1366        static {
1367            if (Build.VERSION.SDK_INT >= 21) {
1368                mUICallback = new ItemTouchUICompat.LollipopImpl();
1369            } else if (Build.VERSION.SDK_INT >= 11) {
1370                mUICallback = new ItemTouchUICompat.HoneycombImpl();
1371            } else {
1372                mUICallback = new ItemTouchUICompat.GingerbreadImpl();
1373            }
1374        }
1375
1376        /**
1377         * Replaces a movement direction with its relative version by taking layout direction into
1378         * account.
1379         *
1380         * @param flags           The flag value that include any number of movement flags.
1381         * @param layoutDirection The layout direction of the View. Can be obtained from
1382         *                        {@link ViewCompat#getLayoutDirection(android.view.View)}.
1383         * @return Updated flags which uses relative flags ({@link #START}, {@link #END}) instead
1384         * of {@link #LEFT}, {@link #RIGHT}.
1385         * @see #convertToAbsoluteDirection(int, int)
1386         */
1387        public static int convertToRelativeDirection(int flags, int layoutDirection) {
1388            int masked = flags & ABS_HORIZONTAL_DIR_FLAGS;
1389            if (masked == 0) {
1390                return flags;// does not have any abs flags, good.
1391            }
1392            flags &= ~masked; //remove left / right.
1393            if (layoutDirection == ViewCompat.LAYOUT_DIRECTION_LTR) {
1394                // no change. just OR with 2 bits shifted mask and return
1395                flags |= masked << 2; // START is 2 bits after LEFT, END is 2 bits after RIGHT.
1396                return flags;
1397            } else {
1398                // add RIGHT flag as START
1399                flags |= ((masked << 1) & ~ABS_HORIZONTAL_DIR_FLAGS);
1400                // first clean RIGHT bit then add LEFT flag as END
1401                flags |= ((masked << 1) & ABS_HORIZONTAL_DIR_FLAGS) << 2;
1402            }
1403            return flags;
1404        }
1405
1406        /**
1407         * Convenience method to create movement flags.
1408         * <p>
1409         * For instance, if you want to let your items be drag & dropped vertically and swiped
1410         * left to be dismissed, you can call this method with:
1411         * <code>makeMovementFlags(UP | DOWN, LEFT);</code>
1412         *
1413         * @param dragFlags  The directions in which the item can be dragged.
1414         * @param swipeFlags The directions in which the item can be swiped.
1415         * @return Returns an integer composed of the given drag and swipe flags.
1416         */
1417        public static int makeMovementFlags(int dragFlags, int swipeFlags) {
1418            return makeFlag(ACTION_STATE_IDLE, swipeFlags | dragFlags) |
1419                    makeFlag(ACTION_STATE_SWIPE, swipeFlags) | makeFlag(ACTION_STATE_DRAG,
1420                    dragFlags);
1421        }
1422
1423        /**
1424         * Shifts the given direction flags to the offset of the given action state.
1425         *
1426         * @param actionState The action state you want to get flags in. Should be one of
1427         *                    {@link #ACTION_STATE_IDLE}, {@link #ACTION_STATE_SWIPE} or
1428         *                    {@link #ACTION_STATE_DRAG}.
1429         * @param directions  The direction flags. Can be composed from {@link #UP}, {@link #DOWN},
1430         *                    {@link #RIGHT}, {@link #LEFT} {@link #START} and {@link #END}.
1431         * @return And integer that represents the given directions in the provided actionState.
1432         */
1433        public static int makeFlag(int actionState, int directions) {
1434            return directions << (actionState * DIRECTION_FLAG_COUNT);
1435        }
1436
1437        /**
1438         * Should return a composite flag which defines the enabled move directions in each state
1439         * (idle, swiping, dragging).
1440         * <p>
1441         * Instead of composing this flag manually, you can use {@link #makeMovementFlags(int,
1442         * int)}
1443         * or {@link #makeFlag(int, int)}.
1444         * <p>
1445         * This flag is composed of 3 sets of 8 bits, where first 8 bits are for IDLE state, next
1446         * 8 bits are for SWIPE state and third 8 bits are for DRAG state.
1447         * Each 8 bit sections can be constructed by simply OR'ing direction flags defined in
1448         * {@link ItemTouchHelper}.
1449         * <p>
1450         * For example, if you want it to allow swiping LEFT and RIGHT but only allow starting to
1451         * swipe by swiping RIGHT, you can return:
1452         * <pre>
1453         *      makeFlag(ACTION_STATE_IDLE, RIGHT) | makeFlag(ACTION_STATE_SWIPE, LEFT | RIGHT);
1454         * </pre>
1455         * This means, allow right movement while IDLE and allow right and left movement while
1456         * swiping.
1457         *
1458         * @param recyclerView The RecyclerView to which ItemTouchHelper is attached.
1459         * @param viewHolder   The ViewHolder for which the movement information is necessary.
1460         * @return flags specifying which movements are allowed on this ViewHolder.
1461         * @see #makeMovementFlags(int, int)
1462         * @see #makeFlag(int, int)
1463         */
1464        public abstract int getMovementFlags(RecyclerView recyclerView,
1465                ViewHolder viewHolder);
1466
1467        /**
1468         * Converts a given set of flags to absolution direction which means {@link #START} and
1469         * {@link #END} are replaced with {@link #LEFT} and {@link #RIGHT} depending on the layout
1470         * direction.
1471         *
1472         * @param flags           The flag value that include any number of movement flags.
1473         * @param layoutDirection The layout direction of the RecyclerView.
1474         * @return Updated flags which includes only absolute direction values.
1475         */
1476        public int convertToAbsoluteDirection(int flags, int layoutDirection) {
1477            int masked = flags & RELATIVE_DIR_FLAGS;
1478            if (masked == 0) {
1479                return flags;// does not have any relative flags, good.
1480            }
1481            flags &= ~masked; //remove start / end
1482            if (layoutDirection == ViewCompat.LAYOUT_DIRECTION_LTR) {
1483                // no change. just OR with 2 bits shifted mask and return
1484                flags |= masked >> 2; // START is 2 bits after LEFT, END is 2 bits after RIGHT.
1485                return flags;
1486            } else {
1487                // add START flag as RIGHT
1488                flags |= ((masked >> 1) & ~RELATIVE_DIR_FLAGS);
1489                // first clean start bit then add END flag as LEFT
1490                flags |= ((masked >> 1) & RELATIVE_DIR_FLAGS) >> 2;
1491            }
1492            return flags;
1493        }
1494
1495        final int getAbsoluteMovementFlags(RecyclerView recyclerView,
1496                ViewHolder viewHolder) {
1497            final int flags = getMovementFlags(recyclerView, viewHolder);
1498            return convertToAbsoluteDirection(flags, ViewCompat.getLayoutDirection(recyclerView));
1499        }
1500
1501        private boolean hasDragFlag(RecyclerView recyclerView, ViewHolder viewHolder) {
1502            final int flags = getAbsoluteMovementFlags(recyclerView, viewHolder);
1503            return (flags & ACTION_MODE_DRAG_MASK) != 0;
1504        }
1505
1506        private boolean hasSwipeFlag(RecyclerView recyclerView,
1507                ViewHolder viewHolder) {
1508            final int flags = getAbsoluteMovementFlags(recyclerView, viewHolder);
1509            return (flags & ACTION_MODE_SWIPE_MASK) != 0;
1510        }
1511
1512        /**
1513         * Return true if the current ViewHolder can be dropped over the the target ViewHolder.
1514         * <p>
1515         * This method is used when selecting drop target for the dragged View. After Views are
1516         * eliminated either via bounds check or via this method, resulting set of views will be
1517         * passed to {@link #chooseDropTarget(ViewHolder, java.util.List, int, int)}.
1518         * <p>
1519         * Default implementation returns true.
1520         *
1521         * @param recyclerView The RecyclerView to which ItemTouchHelper is attached to.
1522         * @param current      The ViewHolder that user is dragging.
1523         * @param target       The ViewHolder which is below the dragged ViewHolder.
1524         * @return True if the dragged ViewHolder can be replaced with the target ViewHolder, false
1525         * otherwise.
1526         */
1527        public boolean canDropOver(RecyclerView recyclerView, ViewHolder current,
1528                ViewHolder target) {
1529            return true;
1530        }
1531
1532        /**
1533         * Called when ItemTouchHelper wants to move the dragged item from its old position to
1534         * the new position.
1535         * <p>
1536         * If this method returns true, ItemTouchHelper assumes {@code viewHolder} has been moved
1537         * to the adapter position of {@code target} ViewHolder
1538         * ({@link ViewHolder#getAdapterPosition()
1539         * ViewHolder#getAdapterPosition()}).
1540         * <p>
1541         * If you don't support drag & drop, this method will never be called.
1542         *
1543         * @param recyclerView The RecyclerView to which ItemTouchHelper is attached to.
1544         * @param viewHolder   The ViewHolder which is being dragged by the user.
1545         * @param target       The ViewHolder over which the currently active item is being
1546         *                     dragged.
1547         * @return True if the {@code viewHolder} has been moved to the adapter position of
1548         * {@code target}.
1549         * @see #onMoved(RecyclerView, ViewHolder, int, ViewHolder, int, int, int)
1550         */
1551        public abstract boolean onMove(RecyclerView recyclerView,
1552                ViewHolder viewHolder, ViewHolder target);
1553
1554        /**
1555         * Returns whether ItemTouchHelper should start a drag and drop operation if an item is
1556         * long pressed.
1557         * <p>
1558         * Default value returns true but you may want to disable this if you want to start
1559         * dragging on a custom view touch using {@link #startDrag(ViewHolder)}.
1560         *
1561         * @return True if ItemTouchHelper should start dragging an item when it is long pressed,
1562         * false otherwise. Default value is <code>true</code>.
1563         * @see #startDrag(ViewHolder)
1564         */
1565        public boolean isLongPressDragEnabled() {
1566            return true;
1567        }
1568
1569        /**
1570         * Returns whether ItemTouchHelper should start a swipe operation if a pointer is swiped
1571         * over the View.
1572         * <p>
1573         * Default value returns true but you may want to disable this if you want to start
1574         * swiping on a custom view touch using {@link #startSwipe(ViewHolder)}.
1575         *
1576         * @return True if ItemTouchHelper should start swiping an item when user swipes a pointer
1577         * over the View, false otherwise. Default value is <code>true</code>.
1578         * @see #startSwipe(ViewHolder)
1579         */
1580        public boolean isItemViewSwipeEnabled() {
1581            return true;
1582        }
1583
1584        /**
1585         * When finding views under a dragged view, by default, ItemTouchHelper searches for views
1586         * that overlap with the dragged View. By overriding this method, you can extend or shrink
1587         * the search box.
1588         *
1589         * @return The extra margin to be added to the hit box of the dragged View.
1590         */
1591        public int getBoundingBoxMargin() {
1592            return 0;
1593        }
1594
1595        /**
1596         * Returns the fraction that the user should move the View to be considered as swiped.
1597         * The fraction is calculated with respect to RecyclerView's bounds.
1598         * <p>
1599         * Default value is .5f, which means, to swipe a View, user must move the View at least
1600         * half of RecyclerView's width or height, depending on the swipe direction.
1601         *
1602         * @param viewHolder The ViewHolder that is being dragged.
1603         * @return A float value that denotes the fraction of the View size. Default value
1604         * is .5f .
1605         */
1606        public float getSwipeThreshold(ViewHolder viewHolder) {
1607            return .5f;
1608        }
1609
1610        /**
1611         * Returns the fraction that the user should move the View to be considered as it is
1612         * dragged. After a view is moved this amount, ItemTouchHelper starts checking for Views
1613         * below it for a possible drop.
1614         *
1615         * @param viewHolder The ViewHolder that is being dragged.
1616         * @return A float value that denotes the fraction of the View size. Default value is
1617         * .5f .
1618         */
1619        public float getMoveThreshold(ViewHolder viewHolder) {
1620            return .5f;
1621        }
1622
1623        /**
1624         * Called by ItemTouchHelper to select a drop target from the list of ViewHolders that
1625         * are under the dragged View.
1626         * <p>
1627         * Default implementation filters the View with which dragged item have changed position
1628         * in the drag direction. For instance, if the view is dragged UP, it compares the
1629         * <code>view.getTop()</code> of the two views before and after drag started. If that value
1630         * is different, the target view passes the filter.
1631         * <p>
1632         * Among these Views which pass the test, the one closest to the dragged view is chosen.
1633         * <p>
1634         * This method is called on the main thread every time user moves the View. If you want to
1635         * override it, make sure it does not do any expensive operations.
1636         *
1637         * @param selected    The ViewHolder being dragged by the user.
1638         * @param dropTargets The list of ViewHolder that are under the dragged View and
1639         *                    candidate as a drop.
1640         * @param curX        The updated left value of the dragged View after drag translations
1641         *                    are applied. This value does not include margins added by
1642         *                    {@link RecyclerView.ItemDecoration}s.
1643         * @param curY        The updated top value of the dragged View after drag translations
1644         *                    are applied. This value does not include margins added by
1645         *                    {@link RecyclerView.ItemDecoration}s.
1646         * @return A ViewHolder to whose position the dragged ViewHolder should be
1647         * moved to.
1648         */
1649        public ViewHolder chooseDropTarget(ViewHolder selected,
1650                List<ViewHolder> dropTargets, int curX, int curY) {
1651            int right = curX + selected.itemView.getWidth();
1652            int bottom = curY + selected.itemView.getHeight();
1653            ViewHolder winner = null;
1654            int winnerScore = -1;
1655            final int dx = curX - selected.itemView.getLeft();
1656            final int dy = curY - selected.itemView.getTop();
1657            final int targetsSize = dropTargets.size();
1658            for (int i = 0; i < targetsSize; i++) {
1659                final ViewHolder target = dropTargets.get(i);
1660                if (dx > 0) {
1661                    int diff = target.itemView.getRight() - right;
1662                    if (diff < 0 && target.itemView.getRight() > selected.itemView.getRight()) {
1663                        final int score = Math.abs(diff);
1664                        if (score > winnerScore) {
1665                            winnerScore = score;
1666                            winner = target;
1667                        }
1668                    }
1669                }
1670                if (dx < 0) {
1671                    int diff = target.itemView.getLeft() - curX;
1672                    if (diff > 0 && target.itemView.getLeft() < selected.itemView.getLeft()) {
1673                        final int score = Math.abs(diff);
1674                        if (score > winnerScore) {
1675                            winnerScore = score;
1676                            winner = target;
1677                        }
1678                    }
1679                }
1680                if (dy < 0) {
1681                    int diff = target.itemView.getTop() - curY;
1682                    if (diff > 0 && target.itemView.getTop() < selected.itemView.getTop()) {
1683                        final int score = Math.abs(diff);
1684                        if (score > winnerScore) {
1685                            winnerScore = score;
1686                            winner = target;
1687                        }
1688                    }
1689                }
1690
1691                if (dy > 0) {
1692                    int diff = target.itemView.getBottom() - bottom;
1693                    if (diff < 0 && target.itemView.getBottom() > selected.itemView.getBottom()) {
1694                        final int score = Math.abs(diff);
1695                        if (score > winnerScore) {
1696                            winnerScore = score;
1697                            winner = target;
1698                        }
1699                    }
1700                }
1701            }
1702            return winner;
1703        }
1704
1705        /**
1706         * Called when a ViewHolder is swiped by the user.
1707         * <p>
1708         * If you are returning relative directions ({@link #START} , {@link #END}) from the
1709         * {@link #getMovementFlags(RecyclerView, ViewHolder)} method, this method
1710         * will also use relative directions. Otherwise, it will use absolute directions.
1711         * <p>
1712         * If you don't support swiping, this method will never be called.
1713         * <p>
1714         * ItemTouchHelper will keep a reference to the View until it is detached from
1715         * RecyclerView.
1716         * As soon as it is detached, ItemTouchHelper will call
1717         * {@link #clearView(RecyclerView, ViewHolder)}.
1718         *
1719         * @param viewHolder The ViewHolder which has been swiped by the user.
1720         * @param direction  The direction to which the ViewHolder is swiped. It is one of
1721         *                   {@link #UP}, {@link #DOWN},
1722         *                   {@link #LEFT} or {@link #RIGHT}. If your
1723         *                   {@link #getMovementFlags(RecyclerView, ViewHolder)}
1724         *                   method
1725         *                   returned relative flags instead of {@link #LEFT} / {@link #RIGHT};
1726         *                   `direction` will be relative as well. ({@link #START} or {@link
1727         *                   #END}).
1728         */
1729        public abstract void onSwiped(ViewHolder viewHolder, int direction);
1730
1731        /**
1732         * Called when the ViewHolder swiped or dragged by the ItemTouchHelper is changed.
1733         * <p/>
1734         * If you override this method, you should call super.
1735         *
1736         * @param viewHolder  The new ViewHolder that is being swiped or dragged. Might be null if
1737         *                    it is cleared.
1738         * @param actionState One of {@link ItemTouchHelper#ACTION_STATE_IDLE},
1739         *                    {@link ItemTouchHelper#ACTION_STATE_SWIPE} or
1740         *                    {@link ItemTouchHelper#ACTION_STATE_DRAG}.
1741         *
1742         * @see #clearView(RecyclerView, RecyclerView.ViewHolder)
1743         */
1744        public void onSelectedChanged(ViewHolder viewHolder, int actionState) {
1745            if (viewHolder != null) {
1746                mUICallback.onSelected(viewHolder);
1747            }
1748        }
1749
1750        private int getMaxDragScroll(RecyclerView recyclerView) {
1751            if (mCachedMaxScrollSpeed == -1) {
1752                mCachedMaxScrollSpeed = recyclerView.getResources().getDimensionPixelSize(
1753                        R.dimen.item_touch_helper_max_drag_scroll_per_frame);
1754            }
1755            return mCachedMaxScrollSpeed;
1756        }
1757
1758        /**
1759         * Called when {@link #onMove(RecyclerView, ViewHolder, ViewHolder)} returns true.
1760         * <p>
1761         * ItemTouchHelper does not create an extra Bitmap or View while dragging, instead, it
1762         * modifies the existing View. Because of this reason, it is important that the View is
1763         * still part of the layout after it is moved. This may not work as intended when swapped
1764         * Views are close to RecyclerView bounds or there are gaps between them (e.g. other Views
1765         * which were not eligible for dropping over).
1766         * <p>
1767         * This method is responsible to give necessary hint to the LayoutManager so that it will
1768         * keep the View in visible area. For example, for LinearLayoutManager, this is as simple
1769         * as calling {@link LinearLayoutManager#scrollToPositionWithOffset(int, int)}.
1770         *
1771         * Default implementation calls {@link RecyclerView#scrollToPosition(int)} if the View's
1772         * new position is likely to be out of bounds.
1773         * <p>
1774         * It is important to ensure the ViewHolder will stay visible as otherwise, it might be
1775         * removed by the LayoutManager if the move causes the View to go out of bounds. In that
1776         * case, drag will end prematurely.
1777         *
1778         * @param recyclerView The RecyclerView controlled by the ItemTouchHelper.
1779         * @param viewHolder   The ViewHolder under user's control.
1780         * @param fromPos      The previous adapter position of the dragged item (before it was
1781         *                     moved).
1782         * @param target       The ViewHolder on which the currently active item has been dropped.
1783         * @param toPos        The new adapter position of the dragged item.
1784         * @param x            The updated left value of the dragged View after drag translations
1785         *                     are applied. This value does not include margins added by
1786         *                     {@link RecyclerView.ItemDecoration}s.
1787         * @param y            The updated top value of the dragged View after drag translations
1788         *                     are applied. This value does not include margins added by
1789         *                     {@link RecyclerView.ItemDecoration}s.
1790         */
1791        public void onMoved(final RecyclerView recyclerView,
1792                final ViewHolder viewHolder, int fromPos, final ViewHolder target, int toPos, int x,
1793                int y) {
1794            final RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
1795            if (layoutManager instanceof ViewDropHandler) {
1796                ((ViewDropHandler) layoutManager).prepareForDrop(viewHolder.itemView,
1797                        target.itemView, x, y);
1798                return;
1799            }
1800
1801            // if layout manager cannot handle it, do some guesswork
1802            if (layoutManager.canScrollHorizontally()) {
1803                final int minLeft = layoutManager.getDecoratedLeft(target.itemView);
1804                if (minLeft <= recyclerView.getPaddingLeft()) {
1805                    recyclerView.scrollToPosition(toPos);
1806                }
1807                final int maxRight = layoutManager.getDecoratedRight(target.itemView);
1808                if (maxRight >= recyclerView.getWidth() - recyclerView.getPaddingRight()) {
1809                    recyclerView.scrollToPosition(toPos);
1810                }
1811            }
1812
1813            if (layoutManager.canScrollVertically()) {
1814                final int minTop = layoutManager.getDecoratedTop(target.itemView);
1815                if (minTop <= recyclerView.getPaddingTop()) {
1816                    recyclerView.scrollToPosition(toPos);
1817                }
1818                final int maxBottom = layoutManager.getDecoratedBottom(target.itemView);
1819                if (maxBottom >= recyclerView.getHeight() - recyclerView.getPaddingBottom()) {
1820                    recyclerView.scrollToPosition(toPos);
1821                }
1822            }
1823        }
1824
1825        private void onDraw(Canvas c, RecyclerView parent, ViewHolder selected,
1826                List<ItemTouchHelper.RecoverAnimation> recoverAnimationList,
1827                int actionState, float dX, float dY) {
1828            final int recoverAnimSize = recoverAnimationList.size();
1829            for (int i = 0; i < recoverAnimSize; i++) {
1830                final ItemTouchHelper.RecoverAnimation anim = recoverAnimationList.get(i);
1831                anim.update();
1832                final int count = c.save();
1833                onChildDraw(c, parent, anim.mViewHolder, anim.mX, anim.mY, anim.mActionState,
1834                        false);
1835                c.restoreToCount(count);
1836            }
1837            if (selected != null) {
1838                final int count = c.save();
1839                onChildDraw(c, parent, selected, dX, dY, actionState, true);
1840                c.restoreToCount(count);
1841            }
1842        }
1843
1844        private void onDrawOver(Canvas c, RecyclerView parent, ViewHolder selected,
1845                List<ItemTouchHelper.RecoverAnimation> recoverAnimationList,
1846                int actionState, float dX, float dY) {
1847            final int recoverAnimSize = recoverAnimationList.size();
1848            for (int i = 0; i < recoverAnimSize; i++) {
1849                final ItemTouchHelper.RecoverAnimation anim = recoverAnimationList.get(i);
1850                final int count = c.save();
1851                onChildDrawOver(c, parent, anim.mViewHolder, anim.mX, anim.mY, anim.mActionState,
1852                        false);
1853                c.restoreToCount(count);
1854            }
1855            if (selected != null) {
1856                final int count = c.save();
1857                onChildDrawOver(c, parent, selected, dX, dY, actionState, true);
1858                c.restoreToCount(count);
1859            }
1860            boolean hasRunningAnimation = false;
1861            for (int i = recoverAnimSize - 1; i >= 0; i--) {
1862                final RecoverAnimation anim = recoverAnimationList.get(i);
1863                if (anim.mEnded && !anim.mIsPendingCleanup) {
1864                    recoverAnimationList.remove(i);
1865                    anim.mViewHolder.setIsRecyclable(true);
1866                } else if (!anim.mEnded) {
1867                    hasRunningAnimation = true;
1868                }
1869            }
1870            if (hasRunningAnimation) {
1871                parent.invalidate();
1872            }
1873        }
1874
1875        /**
1876         * Called by the ItemTouchHelper when the user interaction with an element is over and it
1877         * also completed its animation.
1878         * <p>
1879         * This is a good place to clear all changes on the View that was done in
1880         * {@link #onSelectedChanged(RecyclerView.ViewHolder, int)},
1881         * {@link #onChildDraw(Canvas, RecyclerView, ViewHolder, float, float, int,
1882         * boolean)} or
1883         * {@link #onChildDrawOver(Canvas, RecyclerView, ViewHolder, float, float, int, boolean)}.
1884         *
1885         * @param recyclerView The RecyclerView which is controlled by the ItemTouchHelper.
1886         * @param viewHolder   The View that was interacted by the user.
1887         */
1888        public void clearView(RecyclerView recyclerView, ViewHolder viewHolder) {
1889            mUICallback.clearView(viewHolder);
1890        }
1891
1892        /**
1893         * Called by ItemTouchHelper on RecyclerView's onDraw callback.
1894         * <p>
1895         * If you would like to customize how your View's respond to user interactions, this is
1896         * a good place to override.
1897         * <p>
1898         * Default implementation translates the child by the given <code>dX</code>,
1899         * <code>dY</code>.
1900         * ItemTouchHelper also takes care of drawing the child after other children if it is being
1901         * dragged. This is done using child re-ordering mechanism. On platforms prior to L, this
1902         * is
1903         * achieved via {@link android.view.ViewGroup#getChildDrawingOrder(int, int)} and on L
1904         * and after, it changes View's elevation value to be greater than all other children.)
1905         *
1906         * @param c                 The canvas which RecyclerView is drawing its children
1907         * @param recyclerView      The RecyclerView to which ItemTouchHelper is attached to
1908         * @param viewHolder        The ViewHolder which is being interacted by the User or it was
1909         *                          interacted and simply animating to its original position
1910         * @param dX                The amount of horizontal displacement caused by user's action
1911         * @param dY                The amount of vertical displacement caused by user's action
1912         * @param actionState       The type of interaction on the View. Is either {@link
1913         *                          #ACTION_STATE_DRAG} or {@link #ACTION_STATE_SWIPE}.
1914         * @param isCurrentlyActive True if this view is currently being controlled by the user or
1915         *                          false it is simply animating back to its original state.
1916         * @see #onChildDrawOver(Canvas, RecyclerView, ViewHolder, float, float, int,
1917         * boolean)
1918         */
1919        public void onChildDraw(Canvas c, RecyclerView recyclerView,
1920                ViewHolder viewHolder,
1921                float dX, float dY, int actionState, boolean isCurrentlyActive) {
1922            mUICallback.onDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
1923        }
1924
1925        /**
1926         * Called by ItemTouchHelper on RecyclerView's onDraw callback.
1927         * <p>
1928         * If you would like to customize how your View's respond to user interactions, this is
1929         * a good place to override.
1930         * <p>
1931         * Default implementation translates the child by the given <code>dX</code>,
1932         * <code>dY</code>.
1933         * ItemTouchHelper also takes care of drawing the child after other children if it is being
1934         * dragged. This is done using child re-ordering mechanism. On platforms prior to L, this
1935         * is
1936         * achieved via {@link android.view.ViewGroup#getChildDrawingOrder(int, int)} and on L
1937         * and after, it changes View's elevation value to be greater than all other children.)
1938         *
1939         * @param c                 The canvas which RecyclerView is drawing its children
1940         * @param recyclerView      The RecyclerView to which ItemTouchHelper is attached to
1941         * @param viewHolder        The ViewHolder which is being interacted by the User or it was
1942         *                          interacted and simply animating to its original position
1943         * @param dX                The amount of horizontal displacement caused by user's action
1944         * @param dY                The amount of vertical displacement caused by user's action
1945         * @param actionState       The type of interaction on the View. Is either {@link
1946         *                          #ACTION_STATE_DRAG} or {@link #ACTION_STATE_SWIPE}.
1947         * @param isCurrentlyActive True if this view is currently being controlled by the user or
1948         *                          false it is simply animating back to its original state.
1949         * @see #onChildDrawOver(Canvas, RecyclerView, ViewHolder, float, float, int,
1950         * boolean)
1951         */
1952        public void onChildDrawOver(Canvas c, RecyclerView recyclerView,
1953                ViewHolder viewHolder,
1954                float dX, float dY, int actionState, boolean isCurrentlyActive) {
1955            mUICallback.onDrawOver(c, recyclerView, viewHolder, dX, dY, actionState,
1956                    isCurrentlyActive);
1957        }
1958
1959        /**
1960         * Called by the ItemTouchHelper when user action finished on a ViewHolder and now the View
1961         * will be animated to its final position.
1962         * <p>
1963         * Default implementation uses ItemAnimator's duration values. If
1964         * <code>animationType</code> is {@link #ANIMATION_TYPE_DRAG}, it returns
1965         * {@link RecyclerView.ItemAnimator#getMoveDuration()}, otherwise, it returns
1966         * {@link RecyclerView.ItemAnimator#getRemoveDuration()}. If RecyclerView does not have
1967         * any {@link RecyclerView.ItemAnimator} attached, this method returns
1968         * {@code DEFAULT_DRAG_ANIMATION_DURATION} or {@code DEFAULT_SWIPE_ANIMATION_DURATION}
1969         * depending on the animation type.
1970         *
1971         * @param recyclerView  The RecyclerView to which the ItemTouchHelper is attached to.
1972         * @param animationType The type of animation. Is one of {@link #ANIMATION_TYPE_DRAG},
1973         *                      {@link #ANIMATION_TYPE_SWIPE_CANCEL} or
1974         *                      {@link #ANIMATION_TYPE_SWIPE_SUCCESS}.
1975         * @param animateDx     The horizontal distance that the animation will offset
1976         * @param animateDy     The vertical distance that the animation will offset
1977         * @return The duration for the animation
1978         */
1979        public long getAnimationDuration(RecyclerView recyclerView, int animationType,
1980                float animateDx, float animateDy) {
1981            final RecyclerView.ItemAnimator itemAnimator = recyclerView.getItemAnimator();
1982            if (itemAnimator == null) {
1983                return animationType == ANIMATION_TYPE_DRAG ? DEFAULT_DRAG_ANIMATION_DURATION
1984                        : DEFAULT_SWIPE_ANIMATION_DURATION;
1985            } else {
1986                return animationType == ANIMATION_TYPE_DRAG ? itemAnimator.getMoveDuration()
1987                        : itemAnimator.getRemoveDuration();
1988            }
1989        }
1990
1991        /**
1992         * Called by the ItemTouchHelper when user is dragging a view out of bounds.
1993         * <p>
1994         * You can override this method to decide how much RecyclerView should scroll in response
1995         * to this action. Default implementation calculates a value based on the amount of View
1996         * out of bounds and the time it spent there. The longer user keeps the View out of bounds,
1997         * the faster the list will scroll. Similarly, the larger portion of the View is out of
1998         * bounds, the faster the RecyclerView will scroll.
1999         *
2000         * @param recyclerView        The RecyclerView instance to which ItemTouchHelper is attached
2001         *                            to.
2002         * @param viewSize            The total size of the View in scroll direction, excluding
2003         *                            item decorations.
2004         * @param viewSizeOutOfBounds The total size of the View that is out of bounds. This value
2005         *                            is negative if the View is dragged towards left or top edge.
2006         * @param totalSize           The total size of RecyclerView in the scroll direction.
2007         * @param msSinceStartScroll  The time passed since View is kept out of bounds.
2008         *
2009         * @return The amount that RecyclerView should scroll. Keep in mind that this value will
2010         * be passed to {@link RecyclerView#scrollBy(int, int)} method.
2011         */
2012        public int interpolateOutOfBoundsScroll(RecyclerView recyclerView,
2013                int viewSize, int viewSizeOutOfBounds,
2014                int totalSize, long msSinceStartScroll) {
2015            final int maxScroll = getMaxDragScroll(recyclerView);
2016            final int absOutOfBounds = Math.abs(viewSizeOutOfBounds);
2017            final int direction = (int) Math.signum(viewSizeOutOfBounds);
2018            // might be negative if other direction
2019            float outOfBoundsRatio = Math.min(1f, 1f * absOutOfBounds / viewSize);
2020            final int cappedScroll = (int) (direction * maxScroll *
2021                    sDragViewScrollCapInterpolator.getInterpolation(outOfBoundsRatio));
2022            final float timeRatio;
2023            if (msSinceStartScroll > DRAG_SCROLL_ACCELERATION_LIMIT_TIME_MS) {
2024                timeRatio = 1f;
2025            } else {
2026                timeRatio = (float) msSinceStartScroll / DRAG_SCROLL_ACCELERATION_LIMIT_TIME_MS;
2027            }
2028            final int value = (int) (cappedScroll * sDragScrollInterpolator
2029                    .getInterpolation(timeRatio));
2030            if (value == 0) {
2031                return viewSizeOutOfBounds > 0 ? 1 : -1;
2032            }
2033            return value;
2034        }
2035    }
2036
2037    /**
2038     * A simple wrapper to the default Callback which you can construct with drag and swipe
2039     * directions and this class will handle the flag callbacks. You should still override onMove
2040     * or
2041     * onSwiped depending on your use case.
2042     *
2043     * <pre>
2044     * ItemTouchHelper mIth = new ItemTouchHelper(
2045     *     new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN,
2046     *         ItemTouchHelper.LEFT) {
2047     *         public abstract boolean onMove(RecyclerView recyclerView,
2048     *             ViewHolder viewHolder, ViewHolder target) {
2049     *             final int fromPos = viewHolder.getAdapterPosition();
2050     *             final int toPos = viewHolder.getAdapterPosition();
2051     *             // move item in `fromPos` to `toPos` in adapter.
2052     *             return true;// true if moved, false otherwise
2053     *         }
2054     *         public void onSwiped(ViewHolder viewHolder, int direction) {
2055     *             // remove from adapter
2056     *         }
2057     * });
2058     * </pre>
2059     */
2060    public abstract static class SimpleCallback extends Callback {
2061
2062        private int mDefaultSwipeDirs;
2063
2064        private int mDefaultDragDirs;
2065
2066        /**
2067         * Creates a Callback for the given drag and swipe allowance. These values serve as
2068         * defaults
2069         * and if you want to customize behavior per ViewHolder, you can override
2070         * {@link #getSwipeDirs(RecyclerView, ViewHolder)}
2071         * and / or {@link #getDragDirs(RecyclerView, ViewHolder)}.
2072         *
2073         * @param dragDirs  Binary OR of direction flags in which the Views can be dragged. Must be
2074         *                  composed of {@link #LEFT}, {@link #RIGHT}, {@link #START}, {@link
2075         *                  #END},
2076         *                  {@link #UP} and {@link #DOWN}.
2077         * @param swipeDirs Binary OR of direction flags in which the Views can be swiped. Must be
2078         *                  composed of {@link #LEFT}, {@link #RIGHT}, {@link #START}, {@link
2079         *                  #END},
2080         *                  {@link #UP} and {@link #DOWN}.
2081         */
2082        public SimpleCallback(int dragDirs, int swipeDirs) {
2083            mDefaultSwipeDirs = swipeDirs;
2084            mDefaultDragDirs = dragDirs;
2085        }
2086
2087        /**
2088         * Updates the default swipe directions. For example, you can use this method to toggle
2089         * certain directions depending on your use case.
2090         *
2091         * @param defaultSwipeDirs Binary OR of directions in which the ViewHolders can be swiped.
2092         */
2093        public void setDefaultSwipeDirs(int defaultSwipeDirs) {
2094            mDefaultSwipeDirs = defaultSwipeDirs;
2095        }
2096
2097        /**
2098         * Updates the default drag directions. For example, you can use this method to toggle
2099         * certain directions depending on your use case.
2100         *
2101         * @param defaultDragDirs Binary OR of directions in which the ViewHolders can be dragged.
2102         */
2103        public void setDefaultDragDirs(int defaultDragDirs) {
2104            mDefaultDragDirs = defaultDragDirs;
2105        }
2106
2107        /**
2108         * Returns the swipe directions for the provided ViewHolder.
2109         * Default implementation returns the swipe directions that was set via constructor or
2110         * {@link #setDefaultSwipeDirs(int)}.
2111         *
2112         * @param recyclerView The RecyclerView to which the ItemTouchHelper is attached to.
2113         * @param viewHolder   The RecyclerView for which the swipe drection is queried.
2114         * @return A binary OR of direction flags.
2115         */
2116        public int getSwipeDirs(RecyclerView recyclerView, ViewHolder viewHolder) {
2117            return mDefaultSwipeDirs;
2118        }
2119
2120        /**
2121         * Returns the drag directions for the provided ViewHolder.
2122         * Default implementation returns the drag directions that was set via constructor or
2123         * {@link #setDefaultDragDirs(int)}.
2124         *
2125         * @param recyclerView The RecyclerView to which the ItemTouchHelper is attached to.
2126         * @param viewHolder   The RecyclerView for which the swipe drection is queried.
2127         * @return A binary OR of direction flags.
2128         */
2129        public int getDragDirs(RecyclerView recyclerView, ViewHolder viewHolder) {
2130            return mDefaultDragDirs;
2131        }
2132
2133        @Override
2134        public int getMovementFlags(RecyclerView recyclerView, ViewHolder viewHolder) {
2135            return makeMovementFlags(getDragDirs(recyclerView, viewHolder),
2136                    getSwipeDirs(recyclerView, viewHolder));
2137        }
2138    }
2139
2140    private class ItemTouchHelperGestureListener extends GestureDetector.SimpleOnGestureListener {
2141
2142        @Override
2143        public boolean onDown(MotionEvent e) {
2144            return true;
2145        }
2146
2147        @Override
2148        public void onLongPress(MotionEvent e) {
2149            View child = findChildView(e);
2150            if (child != null) {
2151                ViewHolder vh = mRecyclerView.getChildViewHolder(child);
2152                if (vh != null) {
2153                    if (!mCallback.hasDragFlag(mRecyclerView, vh)) {
2154                        return;
2155                    }
2156                    int pointerId = MotionEventCompat.getPointerId(e, 0);
2157                    // Long press is deferred.
2158                    // Check w/ active pointer id to avoid selecting after motion
2159                    // event is canceled.
2160                    if (pointerId == mActivePointerId) {
2161                        final int index = MotionEventCompat
2162                                .findPointerIndex(e, mActivePointerId);
2163                        final float x = MotionEventCompat.getX(e, index);
2164                        final float y = MotionEventCompat.getY(e, index);
2165                        mInitialTouchX = x;
2166                        mInitialTouchY = y;
2167                        mDx = mDy = 0f;
2168                        if (DEBUG) {
2169                            Log.d(TAG,
2170                                    "onlong press: x:" + mInitialTouchX + ",y:" + mInitialTouchY);
2171                        }
2172                        if (mCallback.isLongPressDragEnabled()) {
2173                            select(vh, ACTION_STATE_DRAG);
2174                        }
2175                    }
2176                }
2177            }
2178        }
2179    }
2180
2181    private class RecoverAnimation implements AnimatorListenerCompat {
2182
2183        final float mStartDx;
2184
2185        final float mStartDy;
2186
2187        final float mTargetX;
2188
2189        final float mTargetY;
2190
2191        final ViewHolder mViewHolder;
2192
2193        final int mActionState;
2194
2195        private final ValueAnimatorCompat mValueAnimator;
2196
2197        private final int mAnimationType;
2198
2199        public boolean mIsPendingCleanup;
2200
2201        float mX;
2202
2203        float mY;
2204
2205        // if user starts touching a recovering view, we put it into interaction mode again,
2206        // instantly.
2207        boolean mOverridden = false;
2208
2209        private boolean mEnded = false;
2210
2211        private float mFraction;
2212
2213        public RecoverAnimation(ViewHolder viewHolder, int animationType,
2214                int actionState, float startDx, float startDy, float targetX, float targetY) {
2215            mActionState = actionState;
2216            mAnimationType = animationType;
2217            mViewHolder = viewHolder;
2218            mStartDx = startDx;
2219            mStartDy = startDy;
2220            mTargetX = targetX;
2221            mTargetY = targetY;
2222            mValueAnimator = AnimatorCompatHelper.emptyValueAnimator();
2223            mValueAnimator.addUpdateListener(
2224                    new AnimatorUpdateListenerCompat() {
2225                        @Override
2226                        public void onAnimationUpdate(ValueAnimatorCompat animation) {
2227                            setFraction(animation.getAnimatedFraction());
2228                        }
2229                    });
2230            mValueAnimator.setTarget(viewHolder.itemView);
2231            mValueAnimator.addListener(this);
2232            setFraction(0f);
2233        }
2234
2235        public void setDuration(long duration) {
2236            mValueAnimator.setDuration(duration);
2237        }
2238
2239        public void start() {
2240            mViewHolder.setIsRecyclable(false);
2241            mValueAnimator.start();
2242        }
2243
2244        public void cancel() {
2245            mValueAnimator.cancel();
2246        }
2247
2248        public void setFraction(float fraction) {
2249            mFraction = fraction;
2250        }
2251
2252        /**
2253         * We run updates on onDraw method but use the fraction from animator callback.
2254         * This way, we can sync translate x/y values w/ the animators to avoid one-off frames.
2255         */
2256        public void update() {
2257            if (mStartDx == mTargetX) {
2258                mX = ViewCompat.getTranslationX(mViewHolder.itemView);
2259            } else {
2260                mX = mStartDx + mFraction * (mTargetX - mStartDx);
2261            }
2262            if (mStartDy == mTargetY) {
2263                mY = ViewCompat.getTranslationY(mViewHolder.itemView);
2264            } else {
2265                mY = mStartDy + mFraction * (mTargetY - mStartDy);
2266            }
2267        }
2268
2269        @Override
2270        public void onAnimationStart(ValueAnimatorCompat animation) {
2271
2272        }
2273
2274        @Override
2275        public void onAnimationEnd(ValueAnimatorCompat animation) {
2276            mEnded = true;
2277        }
2278
2279        @Override
2280        public void onAnimationCancel(ValueAnimatorCompat animation) {
2281            setFraction(1f); //make sure we recover the view's state.
2282        }
2283
2284        @Override
2285        public void onAnimationRepeat(ValueAnimatorCompat animation) {
2286
2287        }
2288    }
2289}