import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.animation.AnimatorCompatHelper;
import android.support.v4.animation.AnimatorListenerCompat;
import android.support.v4.animation.AnimatorUpdateListenerCompat;
import android.support.v4.animation.ValueAnimatorCompat;
import android.support.v4.view.GestureDetectorCompat;
import android.support.v4.view.MotionEventCompat;
import android.support.v4.view.VelocityTrackerCompat;
import android.support.v4.view.ViewCompat;
import android.support.v7.recyclerview.R;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.OnItemTouchListener;
import android.support.v7.widget.RecyclerView.ViewHolder;
import android.util.Log;
import android.view.GestureDetector;
import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.animation.Interpolator;
import java.util.ArrayList;
import java.util.List;
/**
* This is a utility class to add swipe to dismiss and drag & drop support to RecyclerView.
*
* It works with a RecyclerView and a Callback class, which configures what type of interactions
* are enabled and also receives events when user performs these actions.
*
* Depending on which functionality you support, you should override
* {@link Callback#onMove(RecyclerView, ViewHolder, ViewHolder)} and / or
* {@link Callback#onSwiped(ViewHolder, int)}.
*
* This class is designed to work with any LayoutManager but for certain situations, it can be
* optimized for your custom LayoutManager by extending methods in the
* {@link ItemTouchHelper.Callback} class or implementing {@link ItemTouchHelper.ViewDropHandler}
* interface in your LayoutManager.
*
* By default, ItemTouchHelper moves the items' translateX/Y properties to reposition them. On
* platforms older than Honeycomb, ItemTouchHelper uses canvas translations and View's visibility
* property to move items in response to touch events. You can customize these behaviors by
* overriding {@link Callback#onChildDraw(Canvas, RecyclerView, ViewHolder, float, float, int,
* boolean)}
* or {@link Callback#onChildDrawOver(Canvas, RecyclerView, ViewHolder, float, float, int,
* boolean)}.
*
* Most of the time, you only need to override onChildDraw
but due to limitations of
* platform prior to Honeycomb, you may need to implement onChildDrawOver
as well.
*/
public class ItemTouchHelper extends RecyclerView.ItemDecoration
implements RecyclerView.OnChildAttachStateChangeListener {
/**
* Up direction, used for swipe & drag control.
*/
public static final int UP = 1;
/**
* Down direction, used for swipe & drag control.
*/
public static final int DOWN = 1 << 1;
/**
* Left direction, used for swipe & drag control.
*/
public static final int LEFT = 1 << 2;
/**
* Right direction, used for swipe & drag control.
*/
public static final int RIGHT = 1 << 3;
// If you change these relative direction values, update Callback#convertToAbsoluteDirection,
// Callback#convertToRelativeDirection.
/**
* Horizontal start direction. Resolved to LEFT or RIGHT depending on RecyclerView's layout
* direction. Used for swipe & drag control.
*/
public static final int START = LEFT << 2;
/**
* Horizontal end direction. Resolved to LEFT or RIGHT depending on RecyclerView's layout
* direction. Used for swipe & drag control.
*/
public static final int END = RIGHT << 2;
/**
* ItemTouchHelper is in idle state. At this state, either there is no related motion event by
* the user or latest motion events have not yet triggered a swipe or drag.
*/
public static final int ACTION_STATE_IDLE = 0;
/**
* A View is currently being swiped.
*/
public static final int ACTION_STATE_SWIPE = 1;
/**
* A View is currently being dragged.
*/
public static final int ACTION_STATE_DRAG = 2;
/**
* Animation type for views which are swiped successfully.
*/
public static final int ANIMATION_TYPE_SWIPE_SUCCESS = 1 << 1;
/**
* Animation type for views which are not completely swiped thus will animate back to their
* original position.
*/
public static final int ANIMATION_TYPE_SWIPE_CANCEL = 1 << 2;
/**
* Animation type for views that were dragged and now will animate to their final position.
*/
public static final int ANIMATION_TYPE_DRAG = 1 << 3;
static final String TAG = "ItemTouchHelper";
static final boolean DEBUG = true;
static final int ACTIVE_POINTER_ID_NONE = -1;
static final int DIRECTION_FLAG_COUNT = 8;
private static final int ACTION_MODE_IDLE_MASK = (1 << DIRECTION_FLAG_COUNT) - 1;
static final int ACTION_MODE_SWIPE_MASK = ACTION_MODE_IDLE_MASK << DIRECTION_FLAG_COUNT;
static final int ACTION_MODE_DRAG_MASK = ACTION_MODE_SWIPE_MASK << DIRECTION_FLAG_COUNT;
/**
* The unit we are using to track velocity
*/
private static final int PIXELS_PER_SECOND = 1000;
/**
* Views, whose state should be cleared after they are detached from RecyclerView.
* This is necessary after swipe dismissing an item. We wait until animator finishes its job
* to clean these views.
*/
final List mPendingCleanup = new ArrayList();
/**
* Re-use array to calculate dx dy for a ViewHolder
*/
private final float[] mTmpPosition = new float[2];
/**
* Currently selected view holder
*/
ViewHolder mSelected = null;
/**
* The reference coordinates for the action start. For drag & drop, this is the time long
* press is completed vs for swipe, this is the initial touch point.
*/
float mInitialTouchX;
float mInitialTouchY;
/**
* Set when ItemTouchHelper is assigned to a RecyclerView.
*/
float mSwipeEscapeVelocity;
/**
* Set when ItemTouchHelper is assigned to a RecyclerView.
*/
float mMaxSwipeVelocity;
/**
* The diff between the last event and initial touch.
*/
float mDx;
float mDy;
/**
* The coordinates of the selected view at the time it is selected. We record these values
* when action starts so that we can consistently position it even if LayoutManager moves the
* View.
*/
float mSelectedStartX;
float mSelectedStartY;
/**
* The pointer we are tracking.
*/
int mActivePointerId = ACTIVE_POINTER_ID_NONE;
/**
* Developer callback which controls the behavior of ItemTouchHelper.
*/
Callback mCallback;
/**
* Current mode.
*/
int mActionState = ACTION_STATE_IDLE;
/**
* The direction flags obtained from unmasking
* {@link Callback#getAbsoluteMovementFlags(RecyclerView, ViewHolder)} for the current
* action state.
*/
int mSelectedFlags;
/**
* When a View is dragged or swiped and needs to go back to where it was, we create a Recover
* Animation and animate it to its location using this custom Animator, instead of using
* framework Animators.
* Using framework animators has the side effect of clashing with ItemAnimator, creating
* jumpy UIs.
*/
List mRecoverAnimations = new ArrayList();
private int mSlop;
RecyclerView mRecyclerView;
/**
* When user drags a view to the edge, we start scrolling the LayoutManager as long as View
* is partially out of bounds.
*/
final Runnable mScrollRunnable = new Runnable() {
@Override
public void run() {
if (mSelected != null && scrollIfNecessary()) {
if (mSelected != null) { //it might be lost during scrolling
moveIfNecessary(mSelected);
}
mRecyclerView.removeCallbacks(mScrollRunnable);
ViewCompat.postOnAnimation(mRecyclerView, this);
}
}
};
/**
* Used for detecting fling swipe
*/
VelocityTracker mVelocityTracker;
//re-used list for selecting a swap target
private List mSwapTargets;
//re used for for sorting swap targets
private List mDistances;
/**
* If drag & drop is supported, we use child drawing order to bring them to front.
*/
private RecyclerView.ChildDrawingOrderCallback mChildDrawingOrderCallback = null;
/**
* This keeps a reference to the child dragged by the user. Even after user stops dragging,
* until view reaches its final position (end of recover animation), we keep a reference so
* that it can be drawn above other children.
*/
View mOverdrawChild = null;
/**
* We cache the position of the overdraw child to avoid recalculating it each time child
* position callback is called. This value is invalidated whenever a child is attached or
* detached.
*/
int mOverdrawChildPosition = -1;
/**
* Used to detect long press.
*/
GestureDetectorCompat mGestureDetector;
private final OnItemTouchListener mOnItemTouchListener
= new OnItemTouchListener() {
@Override
public boolean onInterceptTouchEvent(RecyclerView recyclerView, MotionEvent event) {
mGestureDetector.onTouchEvent(event);
if (DEBUG) {
Log.d(TAG, "intercept: x:" + event.getX() + ",y:" + event.getY() + ", " + event);
}
final int action = MotionEventCompat.getActionMasked(event);
if (action == MotionEvent.ACTION_DOWN) {
mActivePointerId = event.getPointerId(0);
mInitialTouchX = event.getX();
mInitialTouchY = event.getY();
obtainVelocityTracker();
if (mSelected == null) {
final RecoverAnimation animation = findAnimation(event);
if (animation != null) {
mInitialTouchX -= animation.mX;
mInitialTouchY -= animation.mY;
endRecoverAnimation(animation.mViewHolder, true);
if (mPendingCleanup.remove(animation.mViewHolder.itemView)) {
mCallback.clearView(mRecyclerView, animation.mViewHolder);
}
select(animation.mViewHolder, animation.mActionState);
updateDxDy(event, mSelectedFlags, 0);
}
}
} else if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
mActivePointerId = ACTIVE_POINTER_ID_NONE;
select(null, ACTION_STATE_IDLE);
} else if (mActivePointerId != ACTIVE_POINTER_ID_NONE) {
// in a non scroll orientation, if distance change is above threshold, we
// can select the item
final int index = event.findPointerIndex(mActivePointerId);
if (DEBUG) {
Log.d(TAG, "pointer index " + index);
}
if (index >= 0) {
checkSelectForSwipe(action, event, index);
}
}
if (mVelocityTracker != null) {
mVelocityTracker.addMovement(event);
}
return mSelected != null;
}
@Override
public void onTouchEvent<