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