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