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