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