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