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