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