From e0e754045a974675134657a0d79edcf213c42867 Mon Sep 17 00:00:00 2001
From: mrs <863597281@qq.com>
Date: Thu, 12 Oct 2017 18:10:36 +0800
Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=BB=84=E7=BB=87=E5=90=8D?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
libitemtouchhelper/.gitignore | 1 +
libitemtouchhelper/proguard-rules.pro | 25 +
.../ExampleInstrumentedTest.java | 26 +
.../src/main/AndroidManifest.xml | 2 +
.../itemtouchhelper/ItemTouchHelper.java | 2542 +++++++++++++++++
.../itemtouchhelper/ItemTouchUIUtilImpl.java | 128 +
.../src/main/res/values/strings.xml | 3 +
.../itemtouchhelper/ExampleUnitTest.java | 17 +
8 files changed, 2744 insertions(+)
create mode 100644 libitemtouchhelper/.gitignore
create mode 100644 libitemtouchhelper/proguard-rules.pro
create mode 100644 libitemtouchhelper/src/androidTest/java/com/qiaomu/itemtouchhelper/ExampleInstrumentedTest.java
create mode 100644 libitemtouchhelper/src/main/AndroidManifest.xml
create mode 100644 libitemtouchhelper/src/main/java/com/qiaomu/itemtouchhelper/itemtouchhelper/ItemTouchHelper.java
create mode 100644 libitemtouchhelper/src/main/java/com/qiaomu/itemtouchhelper/itemtouchhelper/ItemTouchUIUtilImpl.java
create mode 100644 libitemtouchhelper/src/main/res/values/strings.xml
create mode 100644 libitemtouchhelper/src/test/java/com/qiaomu/itemtouchhelper/ExampleUnitTest.java
diff --git a/libitemtouchhelper/.gitignore b/libitemtouchhelper/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/libitemtouchhelper/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/libitemtouchhelper/proguard-rules.pro b/libitemtouchhelper/proguard-rules.pro
new file mode 100644
index 0000000..b641eb4
--- /dev/null
+++ b/libitemtouchhelper/proguard-rules.pro
@@ -0,0 +1,25 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in C:\Users\mrs\AppData\Local\Android\Sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/libitemtouchhelper/src/androidTest/java/com/qiaomu/itemtouchhelper/ExampleInstrumentedTest.java b/libitemtouchhelper/src/androidTest/java/com/qiaomu/itemtouchhelper/ExampleInstrumentedTest.java
new file mode 100644
index 0000000..48d001b
--- /dev/null
+++ b/libitemtouchhelper/src/androidTest/java/com/qiaomu/itemtouchhelper/ExampleInstrumentedTest.java
@@ -0,0 +1,26 @@
+package com.qiaomu.itemtouchhelper;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumentation test, which will execute on an Android device.
+ *
+ * @see Testing documentation
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+ @Test
+ public void useAppContext() throws Exception {
+ // Context of the app under test.
+ Context appContext = InstrumentationRegistry.getTargetContext();
+
+ assertEquals("com.qiaomu.itemtouchhelper.test", appContext.getPackageName());
+ }
+}
diff --git a/libitemtouchhelper/src/main/AndroidManifest.xml b/libitemtouchhelper/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..cc32e70
--- /dev/null
+++ b/libitemtouchhelper/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+ * 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 Callback} class or implementing {@link 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 overrideonChildDraw
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 = false;
+
+ 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
+ * You can attach ItemTouchHelper to a RecyclerView via
+ * {@link #attachToRecyclerView(RecyclerView)}. Upon attaching, it will add an item decoration,
+ * an onItemTouchListener and a Child attach / detach listener to the RecyclerView.
+ *
+ * @param callback The Callback which controls the behavior of this touch helper.
+ */
+ public ItemTouchHelper(Callback callback) {
+ mCallback = callback;
+ }
+
+ private static boolean hitTest(View child, float x, float y, float left, float top) {
+ return x >= left &&
+ x <= left + child.getWidth() &&
+ y >= top &&
+ y <= top + child.getHeight();
+ }
+
+ /**
+ * Attaches the ItemTouchHelper to the provided RecyclerView. If TouchHelper is already
+ * attached to a RecyclerView, it will first detach from the previous one. You can call this
+ * method with {@code null} to detach it from the current RecyclerView.
+ *
+ * @param recyclerView The RecyclerView instance to which you want to add this helper or
+ * {@code null} if you want to remove ItemTouchHelper from the current
+ * RecyclerView.
+ */
+ public void attachToRecyclerView(@Nullable RecyclerView recyclerView) {
+ if (mRecyclerView == recyclerView) {
+ return; // nothing to do
+ }
+ if (mRecyclerView != null) {
+ destroyCallbacks();
+ }
+ mRecyclerView = recyclerView;
+ if (mRecyclerView != null) {
+ final Resources resources = recyclerView.getResources();
+ mSwipeEscapeVelocity = resources
+ .getDimension(R.dimen.item_touch_helper_swipe_escape_velocity);
+ mMaxSwipeVelocity = resources
+ .getDimension(R.dimen.item_touch_helper_swipe_escape_max_velocity);
+ setupCallbacks();
+ }
+ }
+
+ private void setupCallbacks() {
+ ViewConfiguration vc = ViewConfiguration.get(mRecyclerView.getContext());
+ mSlop = vc.getScaledTouchSlop();
+ mRecyclerView.addItemDecoration(this);
+ mRecyclerView.addOnItemTouchListener(mOnItemTouchListener);
+ mRecyclerView.addOnChildAttachStateChangeListener(this);
+ initGestureDetector();
+ }
+
+ private void destroyCallbacks() {
+ mRecyclerView.removeItemDecoration(this);
+ mRecyclerView.removeOnItemTouchListener(mOnItemTouchListener);
+ mRecyclerView.removeOnChildAttachStateChangeListener(this);
+ // clean all attached
+ final int recoverAnimSize = mRecoverAnimations.size();
+ for (int i = recoverAnimSize - 1; i >= 0; i--) {
+ final RecoverAnimation recoverAnimation = mRecoverAnimations.get(0);
+ mCallback.clearView(mRecyclerView, recoverAnimation.mViewHolder);
+ }
+ mRecoverAnimations.clear();
+ mOverdrawChild = null;
+ mOverdrawChildPosition = -1;
+ releaseVelocityTracker();
+ }
+
+ private void initGestureDetector() {
+ if (mGestureDetector != null) {
+ return;
+ }
+ mGestureDetector = new GestureDetectorCompat(mRecyclerView.getContext(),
+ new ItemTouchHelperGestureListener());
+ }
+
+ private void getSelectedDxDy(float[] outPosition) {
+ if ((mSelectedFlags & (LEFT | RIGHT)) != 0) {
+ outPosition[0] = mSelectedStartX + mDx - mSelected.itemView.getLeft();
+ } else {
+ outPosition[0] = ViewCompat.getTranslationX(mSelected.itemView);
+ }
+ if ((mSelectedFlags & (UP | DOWN)) != 0) {
+ outPosition[1] = mSelectedStartY + mDy - mSelected.itemView.getTop();
+ } else {
+ outPosition[1] = ViewCompat.getTranslationY(mSelected.itemView);
+ }
+ }
+
+ @Override
+ public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
+ float dx = 0, dy = 0;
+ if (mSelected != null) {
+ getSelectedDxDy(mTmpPosition);
+ dx = mTmpPosition[0];
+ dy = mTmpPosition[1];
+ }
+ mCallback.onDrawOver(c, parent, mSelected,
+ mRecoverAnimations, mActionState, dx, dy);
+ }
+
+ @Override
+ public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
+ // we don't know if RV changed something so we should invalidate this index.
+ mOverdrawChildPosition = -1;
+ float dx = 0, dy = 0;
+ if (mSelected != null) {
+ getSelectedDxDy(mTmpPosition);
+ dx = mTmpPosition[0];
+ dy = mTmpPosition[1];
+ }
+ mCallback.onDraw(c, parent, mSelected,
+ mRecoverAnimations, mActionState, dx, dy);
+ }
+
+ /**
+ * Starts dragging or swiping the given View. Call with null if you want to clear it.
+ *
+ * @param selected The ViewHolder to drag or swipe. Can be null if you want to cancel the
+ * current action
+ * @param actionState The type of action
+ */
+ void select(ViewHolder selected, int actionState) {
+ if (selected == mSelected && actionState == mActionState) {
+ return;
+ }
+ mDragScrollStartTimeInMs = Long.MIN_VALUE;
+ final int prevActionState = mActionState;
+ // prevent duplicate animations
+ endRecoverAnimation(selected, true);
+ mActionState = actionState;
+ if (actionState == ACTION_STATE_DRAG) {
+ // we remove after animation is complete. this means we only elevate the last drag
+ // child but that should perform good enough as it is very hard to start dragging a
+ // new child before the previous one settles.
+ mOverdrawChild = selected.itemView;
+ addChildDrawingOrderCallback();
+ }
+ int actionStateMask = (1 << (DIRECTION_FLAG_COUNT + DIRECTION_FLAG_COUNT * actionState))
+ - 1;
+ boolean preventLayout = false;
+
+ if (mSelected != null) {
+ final ViewHolder prevSelected = mSelected;
+ if (prevSelected.itemView.getParent() != null) {
+ final int swipeDir = prevActionState == ACTION_STATE_DRAG ? 0
+ : swipeIfNecessary(prevSelected);
+ releaseVelocityTracker();
+ // find where we should animate to
+ final float targetTranslateX, targetTranslateY;
+ int animationType;
+ switch (swipeDir) {
+ case LEFT:
+ case RIGHT:
+ case START:
+ case END:
+ targetTranslateY = 0;
+ targetTranslateX = Math.signum(mDx) * mRecyclerView.getWidth();
+ break;
+ case UP:
+ case DOWN:
+ targetTranslateX = 0;
+ targetTranslateY = Math.signum(mDy) * mRecyclerView.getHeight();
+ break;
+ default:
+ targetTranslateX = 0;
+ targetTranslateY = 0;
+ }
+ if (prevActionState == ACTION_STATE_DRAG) {
+ animationType = ANIMATION_TYPE_DRAG;
+ } else if (swipeDir > 0) {
+ animationType = ANIMATION_TYPE_SWIPE_SUCCESS;
+ } else {
+ animationType = ANIMATION_TYPE_SWIPE_CANCEL;
+ }
+ getSelectedDxDy(mTmpPosition);
+ final float currentTranslateX = mTmpPosition[0];
+ final float currentTranslateY = mTmpPosition[1];
+ final RecoverAnimation rv = new RecoverAnimation(prevSelected, animationType,
+ prevActionState, currentTranslateX, currentTranslateY,
+ targetTranslateX, targetTranslateY) {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ if (this.mOverridden) {
+ return;
+ }
+ if (swipeDir <= 0) {
+ // this is a drag or failed swipe. recover immediately
+ mCallback.clearView(mRecyclerView, prevSelected);
+ // full cleanup will happen on onDrawOver
+ } else {
+ // wait until remove animation is complete.
+ mPendingCleanup.add(prevSelected.itemView);
+ mIsPendingCleanup = true;
+ mPreOpened = prevSelected;
+ if (swipeDir > 0) {
+ // Animation might be ended by other animators during a layout.
+ // We defer callback to avoid editing adapter during a layout.
+ postDispatchSwipe(this, swipeDir);
+ }
+ }
+ // removed from the list after it is drawn for the last time
+ if (mOverdrawChild == prevSelected.itemView) {
+ removeChildDrawingOrderCallbackIfNecessary(prevSelected.itemView);
+ }
+ }
+ };
+ final long duration = mCallback.getAnimationDuration(mRecyclerView, animationType,
+ targetTranslateX - currentTranslateX, targetTranslateY - currentTranslateY);
+ rv.setDuration(duration);
+ mRecoverAnimations.add(rv);
+ rv.start();
+ preventLayout = true;
+ } else {
+ removeChildDrawingOrderCallbackIfNecessary(prevSelected.itemView);
+ mCallback.clearView(mRecyclerView, prevSelected);
+ }
+ //mPreSelected = mSelected;
+ mSelected = null;
+ }
+ if (selected != null) {
+ mSelectedFlags =
+ (mCallback.getAbsoluteMovementFlags(mRecyclerView, selected) & actionStateMask)
+ >> (mActionState * DIRECTION_FLAG_COUNT);
+ mSelectedStartX = selected.itemView.getLeft();
+ mSelectedStartY = selected.itemView.getTop();
+ mSelected = selected;
+
+ if (actionState == ACTION_STATE_DRAG) {
+ mSelected.itemView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+ }
+ }
+ final ViewParent rvParent = mRecyclerView.getParent();
+ if (rvParent != null) {
+ rvParent.requestDisallowInterceptTouchEvent(mSelected != null);
+ }
+ if (!preventLayout) {
+ mRecyclerView.getLayoutManager().requestSimpleAnimationsInNextLayout();
+ }
+ mCallback.onSelectedChanged(mSelected, mActionState);
+ mRecyclerView.invalidate();
+ }
+
+ void postDispatchSwipe(final RecoverAnimation anim, final int swipeDir) {
+ // wait until animations are complete.
+ mRecyclerView.post(new Runnable() {
+ @Override
+ public void run() {
+ if (mRecyclerView != null && mRecyclerView.isAttachedToWindow() &&
+ !anim.mOverridden &&
+ anim.mViewHolder.getAdapterPosition() != RecyclerView.NO_POSITION) {
+ final RecyclerView.ItemAnimator animator = mRecyclerView.getItemAnimator();
+ // if animator is running or we have other active recover animations, we try
+ // not to call onSwiped because DefaultItemAnimator is not good at merging
+ // animations. Instead, we wait and batch.
+ if ((animator == null || !animator.isRunning(null))
+ && !hasRunningRecoverAnim()) {
+ mCallback.onSwiped(anim.mViewHolder, swipeDir);
+ } else {
+ mRecyclerView.post(this);
+ }
+ }
+ }
+ });
+ }
+
+ boolean hasRunningRecoverAnim() {
+ final int size = mRecoverAnimations.size();
+ for (int i = 0; i < size; i++) {
+ if (!mRecoverAnimations.get(i).mEnded) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * If user drags the view to the edge, trigger a scroll if necessary.
+ */
+ boolean scrollIfNecessary() {
+ if (mSelected == null) {
+ mDragScrollStartTimeInMs = Long.MIN_VALUE;
+ return false;
+ }
+ final long now = System.currentTimeMillis();
+ final long scrollDuration = mDragScrollStartTimeInMs
+ == Long.MIN_VALUE ? 0 : now - mDragScrollStartTimeInMs;
+ RecyclerView.LayoutManager lm = mRecyclerView.getLayoutManager();
+ if (mTmpRect == null) {
+ mTmpRect = new Rect();
+ }
+ int scrollX = 0;
+ int scrollY = 0;
+ lm.calculateItemDecorationsForChild(mSelected.itemView, mTmpRect);
+ if (lm.canScrollHorizontally()) {
+ int curX = (int) (mSelectedStartX + mDx);
+ final int leftDiff = curX - mTmpRect.left - mRecyclerView.getPaddingLeft();
+ if (mDx < 0 && leftDiff < 0) {
+ scrollX = leftDiff;
+ } else if (mDx > 0) {
+ final int rightDiff =
+ curX + mSelected.itemView.getWidth() + mTmpRect.right
+ - (mRecyclerView.getWidth() - mRecyclerView.getPaddingRight());
+ if (rightDiff > 0) {
+ scrollX = rightDiff;
+ }
+ }
+ }
+ if (lm.canScrollVertically()) {
+ int curY = (int) (mSelectedStartY + mDy);
+ final int topDiff = curY - mTmpRect.top - mRecyclerView.getPaddingTop();
+ if (mDy < 0 && topDiff < 0) {
+ scrollY = topDiff;
+ } else if (mDy > 0) {
+ final int bottomDiff = curY + mSelected.itemView.getHeight() + mTmpRect.bottom -
+ (mRecyclerView.getHeight() - mRecyclerView.getPaddingBottom());
+ if (bottomDiff > 0) {
+ scrollY = bottomDiff;
+ }
+ }
+ }
+ if (scrollX != 0) {
+ scrollX = mCallback.interpolateOutOfBoundsScroll(mRecyclerView,
+ mSelected.itemView.getWidth(), scrollX,
+ mRecyclerView.getWidth(), scrollDuration);
+ }
+ if (scrollY != 0) {
+ scrollY = mCallback.interpolateOutOfBoundsScroll(mRecyclerView,
+ mSelected.itemView.getHeight(), scrollY,
+ mRecyclerView.getHeight(), scrollDuration);
+ }
+ if (scrollX != 0 || scrollY != 0) {
+ if (mDragScrollStartTimeInMs == Long.MIN_VALUE) {
+ mDragScrollStartTimeInMs = now;
+ }
+ mRecyclerView.scrollBy(scrollX, scrollY);
+ return true;
+ }
+ mDragScrollStartTimeInMs = Long.MIN_VALUE;
+ return false;
+ }
+
+ private List
+ * For this method to work:
+ *
+ * For example, if you would like to let your user to be able to drag an Item by touching one
+ * of its descendants, you may implement it as follows:
+ *
+ *
+ * @param viewHolder The ViewHolder to start dragging. It must be a direct child of
+ * RecyclerView.
+ * @see Callback#isItemViewSwipeEnabled()
+ */
+ public void startDrag(ViewHolder viewHolder) {
+ if (!mCallback.hasDragFlag(mRecyclerView, viewHolder)) {
+ Log.e(TAG, "Start drag has been called but swiping is not enabled");
+ return;
+ }
+ if (viewHolder.itemView.getParent() != mRecyclerView) {
+ Log.e(TAG, "Start drag has been called with a view holder which is not a child of "
+ + "the RecyclerView which is controlled by this ItemTouchHelper.");
+ return;
+ }
+ obtainVelocityTracker();
+ mDx = mDy = 0f;
+ select(viewHolder, ACTION_STATE_DRAG);
+ }
+
+ /**
+ * Starts swiping the provided ViewHolder. By default, ItemTouchHelper starts swiping a View
+ * when user swipes their finger (or mouse pointer) over the View. You can disable this
+ * behavior
+ * by overriding {@link Callback}
+ *
+ * For this method to work:
+ *
+ * For example, if you would like to let your user to be able to swipe an Item by touching one
+ * of its descendants, you may implement it as follows:
+ *
+ * A LayoutManager should implement this interface to get ready for the upcoming move
+ * operation.
+ *
+ * For example, LinearLayoutManager sets up a "scrollToPositionWithOffset" calls so that
+ * the View under drag will be used as an anchor View while calculating the next layout,
+ * making layout stay consistent.
+ *
+ * @param view The View which is being dragged. It is very likely that user is still
+ * dragging this View so there might be other
+ * {@link #prepareForDrop(View, View, int, int)} after this one.
+ * @param target The target view which is being dropped on.
+ * @param x The
+ * To control which actions user can take on each view, you should override
+ * {@link #getMovementFlags(RecyclerView, ViewHolder)} and return appropriate set
+ * of direction flags. ({@link #LEFT}, {@link #RIGHT}, {@link #START}, {@link #END},
+ * {@link #UP}, {@link #DOWN}). You can use
+ * {@link #makeMovementFlags(int, int)} to easily construct it. Alternatively, you can use
+ * {@link SimpleCallback}.
+ *
+ * If user drags an item, ItemTouchHelper will call
+ * {@link Callback#onMove(RecyclerView, ViewHolder, ViewHolder)
+ * onMove(recyclerView, dragged, target)}.
+ * Upon receiving this callback, you should move the item from the old position
+ * ({@code dragged.getAdapterPosition()}) to new position ({@code target.getAdapterPosition()})
+ * in your adapter and also call {@link RecyclerView.Adapter#notifyItemMoved(int, int)}.
+ * To control where a View can be dropped, you can override
+ * {@link #canDropOver(RecyclerView, ViewHolder, ViewHolder)}. When a
+ * dragging View overlaps multiple other views, Callback chooses the closest View with which
+ * dragged View might have changed positions. Although this approach works for many use cases,
+ * if you have a custom LayoutManager, you can override
+ * {@link #chooseDropTarget(ViewHolder, List, int, int)} to select a
+ * custom drop target.
+ *
+ * When a View is swiped, ItemTouchHelper animates it until it goes out of bounds, then calls
+ * {@link #onSwiped(ViewHolder, int)}. At this point, you should update your
+ * adapter (e.g. remove the item) and call related Adapter#notify event.
+ */
+ @SuppressWarnings("UnusedParameters")
+ public abstract static class Callback {
+
+ public static final int DEFAULT_DRAG_ANIMATION_DURATION = 200;
+
+ public static final int DEFAULT_SWIPE_ANIMATION_DURATION = 250;
+
+ static final int RELATIVE_DIR_FLAGS = START | END |
+ ((START | END) << DIRECTION_FLAG_COUNT) |
+ ((START | END) << (2 * DIRECTION_FLAG_COUNT));
+
+ private static final ItemTouchUIUtil sUICallback;
+
+ private static final int ABS_HORIZONTAL_DIR_FLAGS = LEFT | RIGHT |
+ ((LEFT | RIGHT) << DIRECTION_FLAG_COUNT) |
+ ((LEFT | RIGHT) << (2 * DIRECTION_FLAG_COUNT));
+
+ private static final Interpolator sDragScrollInterpolator = new Interpolator() {
+ @Override
+ public float getInterpolation(float t) {
+ return t * t * t * t * t;
+ }
+ };
+
+ private static final Interpolator sDragViewScrollCapInterpolator = new Interpolator() {
+ @Override
+ public float getInterpolation(float t) {
+ t -= 1.0f;
+ return t * t * t * t * t + 1.0f;
+ }
+ };
+
+ /**
+ * Drag scroll speed keeps accelerating until this many milliseconds before being capped.
+ */
+ private static final long DRAG_SCROLL_ACCELERATION_LIMIT_TIME_MS = 2000;
+
+ private int mCachedMaxScrollSpeed = -1;
+
+ static {
+ if (Build.VERSION.SDK_INT >= 21) {
+ sUICallback = new ItemTouchUIUtilImpl.Lollipop();
+ } else if (Build.VERSION.SDK_INT >= 11) {
+ sUICallback = new ItemTouchUIUtilImpl.Honeycomb();
+ } else {
+ sUICallback = new ItemTouchUIUtilImpl.Gingerbread();
+ }
+ }
+
+ /**
+ * Returns the {@link ItemTouchUIUtil} that is used by the {@link Callback} class for
+ * visual
+ * changes on Views in response to user interactions. {@link ItemTouchUIUtil} has different
+ * implementations for different platform versions.
+ *
+ * By default, {@link Callback} applies these changes on
+ * {@link ViewHolder#itemView}.
+ *
+ * For example, if you have a use case where you only want the text to move when user
+ * swipes over the view, you can do the following:
+ *
+ * For instance, if you want to let your items be drag & dropped vertically and swiped
+ * left to be dismissed, you can call this method with:
+ *
+ * Instead of composing this flag manually, you can use {@link #makeMovementFlags(int,
+ * int)}
+ * or {@link #makeFlag(int, int)}.
+ *
+ * This flag is composed of 3 sets of 8 bits, where first 8 bits are for IDLE state, next
+ * 8 bits are for SWIPE state and third 8 bits are for DRAG state.
+ * Each 8 bit sections can be constructed by simply OR'ing direction flags defined in
+ * {@link ItemTouchHelper}.
+ *
+ * For example, if you want it to allow swiping LEFT and RIGHT but only allow starting to
+ * swipe by swiping RIGHT, you can return:
+ *
+ * This method is used when selecting drop target for the dragged View. After Views are
+ * eliminated either via bounds check or via this method, resulting set of views will be
+ * passed to {@link #chooseDropTarget(ViewHolder, List, int, int)}.
+ *
+ * Default implementation returns true.
+ *
+ * @param recyclerView The RecyclerView to which ItemTouchHelper is attached to.
+ * @param current The ViewHolder that user is dragging.
+ * @param target The ViewHolder which is below the dragged ViewHolder.
+ * @return True if the dragged ViewHolder can be replaced with the target ViewHolder, false
+ * otherwise.
+ */
+ public boolean canDropOver(RecyclerView recyclerView, ViewHolder current,
+ ViewHolder target) {
+ return true;
+ }
+
+ /**
+ * Called when ItemTouchHelper wants to move the dragged item from its old position to
+ * the new position.
+ *
+ * If this method returns true, ItemTouchHelper assumes {@code viewHolder} has been moved
+ * to the adapter position of {@code target} ViewHolder
+ * ({@link ViewHolder#getAdapterPosition()
+ * ViewHolder#getAdapterPosition()}).
+ *
+ * If you don't support drag & drop, this method will never be called.
+ *
+ * @param recyclerView The RecyclerView to which ItemTouchHelper is attached to.
+ * @param viewHolder The ViewHolder which is being dragged by the user.
+ * @param target The ViewHolder over which the currently active item is being
+ * dragged.
+ * @return True if the {@code viewHolder} has been moved to the adapter position of
+ * {@code target}.
+ * @see #onMoved(RecyclerView, ViewHolder, int, ViewHolder, int, int, int)
+ */
+ public abstract boolean onMove(RecyclerView recyclerView,
+ ViewHolder viewHolder, ViewHolder target);
+
+ /**
+ * Returns whether ItemTouchHelper should start a drag and drop operation if an item is
+ * long pressed.
+ *
+ * Default value returns true but you may want to disable this if you want to start
+ * dragging on a custom view touch using {@link #startDrag(ViewHolder)}.
+ *
+ * @return True if ItemTouchHelper should start dragging an item when it is long pressed,
+ * false otherwise. Default value is
+ * Default value returns true but you may want to disable this if you want to start
+ * swiping on a custom view touch using {@link #startSwipe(ViewHolder)}.
+ *
+ * @return True if ItemTouchHelper should start swiping an item when user swipes a pointer
+ * over the View, false otherwise. Default value is
+ * Default value is .5f, which means, to swipe a View, user must move the View at least
+ * half of RecyclerView's width or height, depending on the swipe direction.
+ *
+ * @param viewHolder The ViewHolder that is being dragged.
+ * @return A float value that denotes the fraction of the View size. Default value
+ * is .5f .
+ */
+ public float getSwipeThreshold(ViewHolder viewHolder) {
+ return .5f;
+ }
+
+ /**
+ * Returns the fraction that the user should move the View to be considered as it is
+ * dragged. After a view is moved this amount, ItemTouchHelper starts checking for Views
+ * below it for a possible drop.
+ *
+ * @param viewHolder The ViewHolder that is being dragged.
+ * @return A float value that denotes the fraction of the View size. Default value is
+ * .5f .
+ */
+ public float getMoveThreshold(ViewHolder viewHolder) {
+ return .5f;
+ }
+
+ /**
+ * Defines the minimum velocity which will be considered as a swipe action by the user.
+ *
+ * You can increase this value to make it harder to swipe or decrease it to make it easier.
+ * Keep in mind that ItemTouchHelper also checks the perpendicular velocity and makes sure
+ * current direction velocity is larger then the perpendicular one. Otherwise, user's
+ * movement is ambiguous. You can change the threshold by overriding
+ * {@link #getSwipeVelocityThreshold(float)}.
+ *
+ * The velocity is calculated in pixels per second.
+ *
+ * The default framework value is passed as a parameter so that you can modify it with a
+ * multiplier.
+ *
+ * @param defaultValue The default value (in pixels per second) used by the
+ * ItemTouchHelper.
+ * @return The minimum swipe velocity. The default implementation returns the
+ *
+ * To consider a movement as swipe, ItemTouchHelper requires it to be larger than the
+ * perpendicular movement. If both directions reach to the max threshold, none of them will
+ * be considered as a swipe because it is usually an indication that user rather tried to
+ * scroll then swipe.
+ *
+ * The velocity is calculated in pixels per second.
+ *
+ * You can customize this behavior by changing this method. If you increase the value, it
+ * will be easier for the user to swipe diagonally and if you decrease the value, user will
+ * need to make a rather straight finger movement to trigger a swipe.
+ *
+ * @param defaultValue The default value(in pixels per second) used by the ItemTouchHelper.
+ * @return The velocity cap for pointer movements. The default implementation returns the
+ *
+ * Default implementation filters the View with which dragged item have changed position
+ * in the drag direction. For instance, if the view is dragged UP, it compares the
+ *
+ * Among these Views which pass the test, the one closest to the dragged view is chosen.
+ *
+ * This method is called on the main thread every time user moves the View. If you want to
+ * override it, make sure it does not do any expensive operations.
+ *
+ * @param selected The ViewHolder being dragged by the user.
+ * @param dropTargets The list of ViewHolder that are under the dragged View and
+ * candidate as a drop.
+ * @param curX The updated left value of the dragged View after drag translations
+ * are applied. This value does not include margins added by
+ * {@link RecyclerView.ItemDecoration}s.
+ * @param curY The updated top value of the dragged View after drag translations
+ * are applied. This value does not include margins added by
+ * {@link RecyclerView.ItemDecoration}s.
+ * @return A ViewHolder to whose position the dragged ViewHolder should be
+ * moved to.
+ */
+ public ViewHolder chooseDropTarget(ViewHolder selected,
+ List
+ * If you are returning relative directions ({@link #START} , {@link #END}) from the
+ * {@link #getMovementFlags(RecyclerView, ViewHolder)} method, this method
+ * will also use relative directions. Otherwise, it will use absolute directions.
+ *
+ * If you don't support swiping, this method will never be called.
+ *
+ * ItemTouchHelper will keep a reference to the View until it is detached from
+ * RecyclerView.
+ * As soon as it is detached, ItemTouchHelper will call
+ * {@link #clearView(RecyclerView, ViewHolder)}.
+ *
+ * @param viewHolder The ViewHolder which has been swiped by the user.
+ * @param direction The direction to which the ViewHolder is swiped. It is one of
+ * {@link #UP}, {@link #DOWN},
+ * {@link #LEFT} or {@link #RIGHT}. If your
+ * {@link #getMovementFlags(RecyclerView, ViewHolder)}
+ * method
+ * returned relative flags instead of {@link #LEFT} / {@link #RIGHT};
+ * `direction` will be relative as well. ({@link #START} or {@link
+ * #END}).
+ */
+ public abstract void onSwiped(ViewHolder viewHolder, int direction);
+
+
+ /**
+ * @param viewHolder this is pre action viewHolder, there we think view has two child
+ * first one is back action view.Front is show view.
+ * @return
+ */
+ public View getItemFrontView(ViewHolder viewHolder) {
+ if (viewHolder == null) return null;
+ if (viewHolder.itemView instanceof ViewGroup && ((ViewGroup) viewHolder.itemView).getChildCount() > 1) {
+ ViewGroup viewGroup = (ViewGroup) viewHolder.itemView;
+ return viewGroup.getChildAt(viewGroup.getChildCount() - 1);
+ } else {
+ return viewHolder.itemView;
+ }
+ }
+
+
+ /**
+ * Called when the ViewHolder swiped or dragged by the ItemTouchHelper is changed.
+ *
+ * ItemTouchHelper does not create an extra Bitmap or View while dragging, instead, it
+ * modifies the existing View. Because of this reason, it is important that the View is
+ * still part of the layout after it is moved. This may not work as intended when swapped
+ * Views are close to RecyclerView bounds or there are gaps between them (e.g. other Views
+ * which were not eligible for dropping over).
+ *
+ * This method is responsible to give necessary hint to the LayoutManager so that it will
+ * keep the View in visible area. For example, for LinearLayoutManager, this is as simple
+ * as calling {@link LinearLayoutManager#scrollToPositionWithOffset(int, int)}.
+ *
+ * Default implementation calls {@link RecyclerView#scrollToPosition(int)} if the View's
+ * new position is likely to be out of bounds.
+ *
+ * It is important to ensure the ViewHolder will stay visible as otherwise, it might be
+ * removed by the LayoutManager if the move causes the View to go out of bounds. In that
+ * case, drag will end prematurely.
+ *
+ * @param recyclerView The RecyclerView controlled by the ItemTouchHelper.
+ * @param viewHolder The ViewHolder under user's control.
+ * @param fromPos The previous adapter position of the dragged item (before it was
+ * moved).
+ * @param target The ViewHolder on which the currently active item has been dropped.
+ * @param toPos The new adapter position of the dragged item.
+ * @param x The updated left value of the dragged View after drag translations
+ * are applied. This value does not include margins added by
+ * {@link RecyclerView.ItemDecoration}s.
+ * @param y The updated top value of the dragged View after drag translations
+ * are applied. This value does not include margins added by
+ * {@link RecyclerView.ItemDecoration}s.
+ */
+ public void onMoved(final RecyclerView recyclerView,
+ final ViewHolder viewHolder, int fromPos, final ViewHolder target, int toPos, int x,
+ int y) {
+ final RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
+ if (layoutManager instanceof ViewDropHandler) {
+ ((ViewDropHandler) layoutManager).prepareForDrop(viewHolder.itemView,
+ target.itemView, x, y);
+ return;
+ }
+
+ // if layout manager cannot handle it, do some guesswork
+ if (layoutManager.canScrollHorizontally()) {
+ final int minLeft = layoutManager.getDecoratedLeft(target.itemView);
+ if (minLeft <= recyclerView.getPaddingLeft()) {
+ recyclerView.scrollToPosition(toPos);
+ }
+ final int maxRight = layoutManager.getDecoratedRight(target.itemView);
+ if (maxRight >= recyclerView.getWidth() - recyclerView.getPaddingRight()) {
+ recyclerView.scrollToPosition(toPos);
+ }
+ }
+
+ if (layoutManager.canScrollVertically()) {
+ final int minTop = layoutManager.getDecoratedTop(target.itemView);
+ if (minTop <= recyclerView.getPaddingTop()) {
+ recyclerView.scrollToPosition(toPos);
+ }
+ final int maxBottom = layoutManager.getDecoratedBottom(target.itemView);
+ if (maxBottom >= recyclerView.getHeight() - recyclerView.getPaddingBottom()) {
+ recyclerView.scrollToPosition(toPos);
+ }
+ }
+ }
+
+ void onDraw(Canvas c, RecyclerView parent, ViewHolder selected,
+ List
+ * This is a good place to clear all changes on the View that was done in
+ * {@link #onSelectedChanged(ViewHolder, int)},
+ * {@link #onChildDraw(Canvas, RecyclerView, ViewHolder, float, float, int,
+ * boolean)} or
+ * {@link #onChildDrawOver(Canvas, RecyclerView, ViewHolder, float, float, int, boolean)}.
+ *
+ * @param recyclerView The RecyclerView which is controlled by the ItemTouchHelper.
+ * @param viewHolder The View that was interacted by the user.
+ */
+ public void clearView(RecyclerView recyclerView, ViewHolder viewHolder) {
+ sUICallback.clearView(viewHolder.itemView);
+ }
+
+ /**
+ * Called by ItemTouchHelper on RecyclerView's onDraw callback.
+ *
+ * If you would like to customize how your View's respond to user interactions, this is
+ * a good place to override.
+ *
+ * Default implementation translates the child by the given
+ * If you would like to customize how your View's respond to user interactions, this is
+ * a good place to override.
+ *
+ * Default implementation translates the child by the given
+ * Default implementation uses ItemAnimator's duration values. If
+ *
+ * You can override this method to decide how much RecyclerView should scroll in response
+ * to this action. Default implementation calculates a value based on the amount of View
+ * out of bounds and the time it spent there. The longer user keeps the View out of bounds,
+ * the faster the list will scroll. Similarly, the larger portion of the View is out of
+ * bounds, the faster the RecyclerView will scroll.
+ *
+ * @param recyclerView The RecyclerView instance to which ItemTouchHelper is
+ * attached to.
+ * @param viewSize The total size of the View in scroll direction, excluding
+ * item decorations.
+ * @param viewSizeOutOfBounds The total size of the View that is out of bounds. This value
+ * is negative if the View is dragged towards left or top edge.
+ * @param totalSize The total size of RecyclerView in the scroll direction.
+ * @param msSinceStartScroll The time passed since View is kept out of bounds.
+ * @return The amount that RecyclerView should scroll. Keep in mind that this value will
+ * be passed to {@link RecyclerView#scrollBy(int, int)} method.
+ */
+ public int interpolateOutOfBoundsScroll(RecyclerView recyclerView,
+ int viewSize, int viewSizeOutOfBounds,
+ int totalSize, long msSinceStartScroll) {
+ final int maxScroll = getMaxDragScroll(recyclerView);
+ final int absOutOfBounds = Math.abs(viewSizeOutOfBounds);
+ final int direction = (int) Math.signum(viewSizeOutOfBounds);
+ // might be negative if other direction
+ float outOfBoundsRatio = Math.min(1f, 1f * absOutOfBounds / viewSize);
+ final int cappedScroll = (int) (direction * maxScroll *
+ sDragViewScrollCapInterpolator.getInterpolation(outOfBoundsRatio));
+ final float timeRatio;
+ if (msSinceStartScroll > DRAG_SCROLL_ACCELERATION_LIMIT_TIME_MS) {
+ timeRatio = 1f;
+ } else {
+ timeRatio = (float) msSinceStartScroll / DRAG_SCROLL_ACCELERATION_LIMIT_TIME_MS;
+ }
+ final int value = (int) (cappedScroll * sDragScrollInterpolator
+ .getInterpolation(timeRatio));
+ if (value == 0) {
+ return viewSizeOutOfBounds > 0 ? 1 : -1;
+ }
+ return value;
+ }
+ }
+
+ /**
+ * A simple wrapper to the default Callback which you can construct with drag and swipe
+ * directions and this class will handle the flag callbacks. You should still override onMove
+ * or
+ * onSwiped depending on your use case.
+ *
+ *
+ *
+ *
+ * viewHolder.dragButton.setOnTouchListener(new View.OnTouchListener() {
+ * public boolean onTouch(View v, MotionEvent event) {
+ * if (MotionEventCompat.getActionMasked(event) == MotionEvent.ACTION_DOWN) {
+ * mItemTouchHelper.startDrag(viewHolder);
+ * }
+ * return false;
+ * }
+ * });
+ *
+ *
+ *
+ *
+ * viewHolder.dragButton.setOnTouchListener(new View.OnTouchListener() {
+ * public boolean onTouch(View v, MotionEvent event) {
+ * if (MotionEventCompat.getActionMasked(event) == MotionEvent.ACTION_DOWN) {
+ * mItemTouchHelper.startSwipe(viewHolder);
+ * }
+ * return false;
+ * }
+ * });
+ *
+ *
+ * @param viewHolder The ViewHolder to start swiping. It must be a direct child of
+ * RecyclerView.
+ */
+ public void startSwipe(ViewHolder viewHolder) {
+ if (!mCallback.hasSwipeFlag(mRecyclerView, viewHolder)) {
+ Log.e(TAG, "Start swipe has been called but dragging is not enabled");
+ return;
+ }
+ if (viewHolder.itemView.getParent() != mRecyclerView) {
+ Log.e(TAG, "Start swipe has been called with a view holder which is not a child of "
+ + "the RecyclerView controlled by this ItemTouchHelper.");
+ return;
+ }
+ obtainVelocityTracker();
+ mDx = mDy = 0f;
+ select(viewHolder, ACTION_STATE_SWIPE);
+ }
+
+
+ RecoverAnimation findAnimation(MotionEvent event) {
+ if (mRecoverAnimations.isEmpty()) {
+ return null;
+ }
+ View target = findChildView(event);
+ for (int i = mRecoverAnimations.size() - 1; i >= 0; i--) {
+ final RecoverAnimation anim = mRecoverAnimations.get(i);
+ if (anim.mViewHolder.itemView == target) {
+ return anim;
+ }
+ }
+ return null;
+ }
+
+ void updateDxDy(MotionEvent ev, int directionFlags, int pointerIndex) {
+ final float x = ev.getX(pointerIndex);
+ final float y = ev.getY(pointerIndex);
+
+ // Calculate the distance moved
+ mDx = x - mInitialTouchX;
+ mDy = y - mInitialTouchY;
+ if ((directionFlags & LEFT) == 0) {
+ mDx = Math.max(0, mDx);
+ }
+ if ((directionFlags & RIGHT) == 0) {
+ mDx = Math.min(0, mDx);
+ }
+ if ((directionFlags & UP) == 0) {
+ mDy = Math.max(0, mDy);
+ }
+ if ((directionFlags & DOWN) == 0) {
+ mDy = Math.min(0, mDy);
+ }
+ }
+
+ private int swipeIfNecessary(ViewHolder viewHolder) {
+ if (mActionState == ACTION_STATE_DRAG) {
+ return 0;
+ }
+ final int originalMovementFlags = mCallback.getMovementFlags(mRecyclerView, viewHolder);
+ final int absoluteMovementFlags = mCallback.convertToAbsoluteDirection(
+ originalMovementFlags,
+ ViewCompat.getLayoutDirection(mRecyclerView));
+ final int flags = (absoluteMovementFlags
+ & ACTION_MODE_SWIPE_MASK) >> (ACTION_STATE_SWIPE * DIRECTION_FLAG_COUNT);
+ if (flags == 0) {
+ return 0;
+ }
+ final int originalFlags = (originalMovementFlags
+ & ACTION_MODE_SWIPE_MASK) >> (ACTION_STATE_SWIPE * DIRECTION_FLAG_COUNT);
+ int swipeDir;
+ if (Math.abs(mDx) > Math.abs(mDy)) {
+ if ((swipeDir = checkHorizontalSwipe(viewHolder, flags)) > 0) {
+ // if swipe dir is not in original flags, it should be the relative direction
+ if ((originalFlags & swipeDir) == 0) {
+ // convert to relative
+ return Callback.convertToRelativeDirection(swipeDir,
+ ViewCompat.getLayoutDirection(mRecyclerView));
+ }
+ return swipeDir;
+ }
+ if ((swipeDir = checkVerticalSwipe(viewHolder, flags)) > 0) {
+ return swipeDir;
+ }
+ } else {
+ if ((swipeDir = checkVerticalSwipe(viewHolder, flags)) > 0) {
+ return swipeDir;
+ }
+ if ((swipeDir = checkHorizontalSwipe(viewHolder, flags)) > 0) {
+ // if swipe dir is not in original flags, it should be the relative direction
+ if ((originalFlags & swipeDir) == 0) {
+ // convert to relative
+ return Callback.convertToRelativeDirection(swipeDir,
+ ViewCompat.getLayoutDirection(mRecyclerView));
+ }
+ return swipeDir;
+ }
+ }
+ return 0;
+ }
+
+ private int checkHorizontalSwipe(ViewHolder viewHolder, int flags) {
+ if ((flags & (LEFT | RIGHT)) != 0) {
+ final int dirFlag = mDx > 0 ? RIGHT : LEFT;
+ if (mVelocityTracker != null && mActivePointerId > -1) {
+ mVelocityTracker.computeCurrentVelocity(PIXELS_PER_SECOND,
+ mCallback.getSwipeVelocityThreshold(mMaxSwipeVelocity));
+ final float xVelocity = VelocityTrackerCompat
+ .getXVelocity(mVelocityTracker, mActivePointerId);
+ final float yVelocity = VelocityTrackerCompat
+ .getYVelocity(mVelocityTracker, mActivePointerId);
+ final int velDirFlag = xVelocity > 0f ? RIGHT : LEFT;
+ final float absXVelocity = Math.abs(xVelocity);
+ if ((velDirFlag & flags) != 0 && dirFlag == velDirFlag &&
+ absXVelocity >= mCallback.getSwipeEscapeVelocity(mSwipeEscapeVelocity) &&
+ absXVelocity > Math.abs(yVelocity)) {
+ return velDirFlag;
+ }
+ }
+
+ final float threshold = mRecyclerView.getWidth() * mCallback
+ .getSwipeThreshold(viewHolder);
+
+ if ((flags & dirFlag) != 0 && Math.abs(mDx) > threshold) {
+ return dirFlag;
+ }
+ }
+ return 0;
+ }
+
+ private int checkVerticalSwipe(ViewHolder viewHolder, int flags) {
+ if ((flags & (UP | DOWN)) != 0) {
+ final int dirFlag = mDy > 0 ? DOWN : UP;
+ if (mVelocityTracker != null && mActivePointerId > -1) {
+ mVelocityTracker.computeCurrentVelocity(PIXELS_PER_SECOND,
+ mCallback.getSwipeVelocityThreshold(mMaxSwipeVelocity));
+ final float xVelocity = VelocityTrackerCompat
+ .getXVelocity(mVelocityTracker, mActivePointerId);
+ final float yVelocity = VelocityTrackerCompat
+ .getYVelocity(mVelocityTracker, mActivePointerId);
+ final int velDirFlag = yVelocity > 0f ? DOWN : UP;
+ final float absYVelocity = Math.abs(yVelocity);
+ if ((velDirFlag & flags) != 0 && velDirFlag == dirFlag &&
+ absYVelocity >= mCallback.getSwipeEscapeVelocity(mSwipeEscapeVelocity) &&
+ absYVelocity > Math.abs(xVelocity)) {
+ return velDirFlag;
+ }
+ }
+
+ final float threshold = mRecyclerView.getHeight() * mCallback
+ .getSwipeThreshold(viewHolder);
+ if ((flags & dirFlag) != 0 && Math.abs(mDy) > threshold) {
+ return dirFlag;
+ }
+ }
+ return 0;
+ }
+
+ private void addChildDrawingOrderCallback() {
+ if (Build.VERSION.SDK_INT >= 21) {
+ return;// we use elevation on Lollipop
+ }
+ if (mChildDrawingOrderCallback == null) {
+ mChildDrawingOrderCallback = new RecyclerView.ChildDrawingOrderCallback() {
+ @Override
+ public int onGetChildDrawingOrder(int childCount, int i) {
+ if (mOverdrawChild == null) {
+ return i;
+ }
+ int childPosition = mOverdrawChildPosition;
+ if (childPosition == -1) {
+ childPosition = mRecyclerView.indexOfChild(mOverdrawChild);
+ mOverdrawChildPosition = childPosition;
+ }
+ if (i == childCount - 1) {
+ return childPosition;
+ }
+ return i < childPosition ? i : i + 1;
+ }
+ };
+ }
+ mRecyclerView.setChildDrawingOrderCallback(mChildDrawingOrderCallback);
+ }
+
+ void removeChildDrawingOrderCallbackIfNecessary(View view) {
+ if (view == mOverdrawChild) {
+ mOverdrawChild = null;
+ // only remove if we've added
+ if (mChildDrawingOrderCallback != null) {
+ mRecyclerView.setChildDrawingOrderCallback(null);
+ }
+ }
+ }
+
+ /**
+ * An interface which can be implemented by LayoutManager for better integration with
+ * {@link ItemTouchHelper}.
+ */
+ public static interface ViewDropHandler {
+
+ /**
+ * Called by the {@link ItemTouchHelper} after a View is dropped over another View.
+ * left
offset of the View that is being dragged. This value
+ * includes the movement caused by the user.
+ * @param y The top
offset of the View that is being dragged. This value
+ * includes the movement caused by the user.
+ */
+ public void prepareForDrop(View view, View target, int x, int y);
+ }
+
+ /**
+ * This class is the contract between ItemTouchHelper and your application. It lets you control
+ * which touch behaviors are enabled per each ViewHolder and also receive callbacks when user
+ * performs these actions.
+ *
+ * public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder){
+ * getDefaultUIUtil().clearView(((ItemTouchViewHolder) viewHolder).textView);
+ * }
+ * public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
+ * if (viewHolder != null){
+ * getDefaultUIUtil().onSelected(((ItemTouchViewHolder) viewHolder).textView);
+ * }
+ * }
+ * public void onChildDraw(Canvas c, RecyclerView recyclerView,
+ * RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState,
+ * boolean isCurrentlyActive) {
+ * getDefaultUIUtil().onDraw(c, recyclerView,
+ * ((ItemTouchViewHolder) viewHolder).textView, dX, dY,
+ * actionState, isCurrentlyActive);
+ * return true;
+ * }
+ * public void onChildDrawOver(Canvas c, RecyclerView recyclerView,
+ * RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState,
+ * boolean isCurrentlyActive) {
+ * getDefaultUIUtil().onDrawOver(c, recyclerView,
+ * ((ItemTouchViewHolder) viewHolder).textView, dX, dY,
+ * actionState, isCurrentlyActive);
+ * return true;
+ * }
+ *
+ *
+ * @return The {@link ItemTouchUIUtil} instance that is used by the {@link Callback}
+ */
+ public static ItemTouchUIUtil getDefaultUIUtil() {
+ return sUICallback;
+ }
+
+ /**
+ * Replaces a movement direction with its relative version by taking layout direction into
+ * account.
+ *
+ * @param flags The flag value that include any number of movement flags.
+ * @param layoutDirection The layout direction of the View. Can be obtained from
+ * {@link ViewCompat#getLayoutDirection(View)}.
+ * @return Updated flags which uses relative flags ({@link #START}, {@link #END}) instead
+ * of {@link #LEFT}, {@link #RIGHT}.
+ * @see #convertToAbsoluteDirection(int, int)
+ */
+ public static int convertToRelativeDirection(int flags, int layoutDirection) {
+ int masked = flags & ABS_HORIZONTAL_DIR_FLAGS;
+ if (masked == 0) {
+ return flags;// does not have any abs flags, good.
+ }
+ flags &= ~masked; //remove left / right.
+ if (layoutDirection == ViewCompat.LAYOUT_DIRECTION_LTR) {
+ // no change. just OR with 2 bits shifted mask and return
+ flags |= masked << 2; // START is 2 bits after LEFT, END is 2 bits after RIGHT.
+ return flags;
+ } else {
+ // add RIGHT flag as START
+ flags |= ((masked << 1) & ~ABS_HORIZONTAL_DIR_FLAGS);
+ // first clean RIGHT bit then add LEFT flag as END
+ flags |= ((masked << 1) & ABS_HORIZONTAL_DIR_FLAGS) << 2;
+ }
+ return flags;
+ }
+
+ /**
+ * Convenience method to create movement flags.
+ * makeMovementFlags(UP | DOWN, LEFT);
+ *
+ * @param dragFlags The directions in which the item can be dragged.
+ * @param swipeFlags The directions in which the item can be swiped.
+ * @return Returns an integer composed of the given drag and swipe flags.
+ */
+ public static int makeMovementFlags(int dragFlags, int swipeFlags) {
+ return makeFlag(ACTION_STATE_IDLE, swipeFlags | dragFlags) |
+ makeFlag(ACTION_STATE_SWIPE, swipeFlags) | makeFlag(ACTION_STATE_DRAG,
+ dragFlags);
+ }
+
+ /**
+ * Shifts the given direction flags to the offset of the given action state.
+ *
+ * @param actionState The action state you want to get flags in. Should be one of
+ * {@link #ACTION_STATE_IDLE}, {@link #ACTION_STATE_SWIPE} or
+ * {@link #ACTION_STATE_DRAG}.
+ * @param directions The direction flags. Can be composed from {@link #UP}, {@link #DOWN},
+ * {@link #RIGHT}, {@link #LEFT} {@link #START} and {@link #END}.
+ * @return And integer that represents the given directions in the provided actionState.
+ */
+ public static int makeFlag(int actionState, int directions) {
+ return directions << (actionState * DIRECTION_FLAG_COUNT);
+ }
+
+ /**
+ * Should return a composite flag which defines the enabled move directions in each state
+ * (idle, swiping, dragging).
+ *
+ * makeFlag(ACTION_STATE_IDLE, RIGHT) | makeFlag(ACTION_STATE_SWIPE, LEFT | RIGHT);
+ *
+ * This means, allow right movement while IDLE and allow right and left movement while
+ * swiping.
+ *
+ * @param recyclerView The RecyclerView to which ItemTouchHelper is attached.
+ * @param viewHolder The ViewHolder for which the movement information is necessary.
+ * @return flags specifying which movements are allowed on this ViewHolder.
+ * @see #makeMovementFlags(int, int)
+ * @see #makeFlag(int, int)
+ */
+ public abstract int getMovementFlags(RecyclerView recyclerView,
+ ViewHolder viewHolder);
+
+ /**
+ * Converts a given set of flags to absolution direction which means {@link #START} and
+ * {@link #END} are replaced with {@link #LEFT} and {@link #RIGHT} depending on the layout
+ * direction.
+ *
+ * @param flags The flag value that include any number of movement flags.
+ * @param layoutDirection The layout direction of the RecyclerView.
+ * @return Updated flags which includes only absolute direction values.
+ */
+ public int convertToAbsoluteDirection(int flags, int layoutDirection) {
+ int masked = flags & RELATIVE_DIR_FLAGS;
+ if (masked == 0) {
+ return flags;// does not have any relative flags, good.
+ }
+ flags &= ~masked; //remove start / end
+ if (layoutDirection == ViewCompat.LAYOUT_DIRECTION_LTR) {
+ // no change. just OR with 2 bits shifted mask and return
+ flags |= masked >> 2; // START is 2 bits after LEFT, END is 2 bits after RIGHT.
+ return flags;
+ } else {
+ // add START flag as RIGHT
+ flags |= ((masked >> 1) & ~RELATIVE_DIR_FLAGS);
+ // first clean start bit then add END flag as LEFT
+ flags |= ((masked >> 1) & RELATIVE_DIR_FLAGS) >> 2;
+ }
+ return flags;
+ }
+
+ final int getAbsoluteMovementFlags(RecyclerView recyclerView,
+ ViewHolder viewHolder) {
+ final int flags = getMovementFlags(recyclerView, viewHolder);
+ return convertToAbsoluteDirection(flags, ViewCompat.getLayoutDirection(recyclerView));
+ }
+
+ boolean hasDragFlag(RecyclerView recyclerView, ViewHolder viewHolder) {
+ final int flags = getAbsoluteMovementFlags(recyclerView, viewHolder);
+ return (flags & ACTION_MODE_DRAG_MASK) != 0;
+ }
+
+ boolean hasSwipeFlag(RecyclerView recyclerView,
+ ViewHolder viewHolder) {
+ final int flags = getAbsoluteMovementFlags(recyclerView, viewHolder);
+ return (flags & ACTION_MODE_SWIPE_MASK) != 0;
+ }
+
+ /**
+ * Return true if the current ViewHolder can be dropped over the the target ViewHolder.
+ * true
.
+ * @see #startDrag(ViewHolder)
+ */
+ public boolean isLongPressDragEnabled() {
+ return true;
+ }
+
+ /**
+ * Returns whether ItemTouchHelper should start a swipe operation if a pointer is swiped
+ * over the View.
+ * true
.
+ * @see #startSwipe(ViewHolder)
+ */
+ public boolean isItemViewSwipeEnabled() {
+ return true;
+ }
+
+ /**
+ * When finding views under a dragged view, by default, ItemTouchHelper searches for views
+ * that overlap with the dragged View. By overriding this method, you can extend or shrink
+ * the search box.
+ *
+ * @return The extra margin to be added to the hit box of the dragged View.
+ */
+ public int getBoundingBoxMargin() {
+ return 0;
+ }
+
+ /**
+ * Returns the fraction that the user should move the View to be considered as swiped.
+ * The fraction is calculated with respect to RecyclerView's bounds.
+ * defaultValue
parameter.
+ * @see #getSwipeVelocityThreshold(float)
+ * @see #getSwipeThreshold(ViewHolder)
+ */
+ public float getSwipeEscapeVelocity(float defaultValue) {
+ return defaultValue;
+ }
+
+ /**
+ * Defines the maximum velocity ItemTouchHelper will ever calculate for pointer movements.
+ * defaultValue
parameter.
+ * @see #getSwipeEscapeVelocity(float)
+ */
+ public float getSwipeVelocityThreshold(float defaultValue) {
+ return defaultValue;
+ }
+
+ /**
+ * Called by ItemTouchHelper to select a drop target from the list of ViewHolders that
+ * are under the dragged View.
+ * view.getTop()
of the two views before and after drag started. If that value
+ * is different, the target view passes the filter.
+ * dX
,
+ * dY
.
+ * ItemTouchHelper also takes care of drawing the child after other children if it is being
+ * dragged. This is done using child re-ordering mechanism. On platforms prior to L, this
+ * is
+ * achieved via {@link ViewGroup#getChildDrawingOrder(int, int)} and on L
+ * and after, it changes View's elevation value to be greater than all other children.)
+ *
+ * @param c The canvas which RecyclerView is drawing its children
+ * @param recyclerView The RecyclerView to which ItemTouchHelper is attached to
+ * @param viewHolder The ViewHolder which is being interacted by the User or it was
+ * interacted and simply animating to its original position
+ * @param dX The amount of horizontal displacement caused by user's action
+ * @param dY The amount of vertical displacement caused by user's action
+ * @param actionState The type of interaction on the View. Is either {@link
+ * #ACTION_STATE_DRAG} or {@link #ACTION_STATE_SWIPE}.
+ * @param isCurrentlyActive True if this view is currently being controlled by the user or
+ * false it is simply animating back to its original state.
+ * @see #onChildDrawOver(Canvas, RecyclerView, ViewHolder, float, float, int,
+ * boolean)
+ */
+ public void onChildDraw(Canvas c, RecyclerView recyclerView,
+ ViewHolder viewHolder,
+ float dX, float dY, int actionState, boolean isCurrentlyActive) {
+ sUICallback.onDraw(c, recyclerView, viewHolder.itemView, dX, dY, actionState,
+ isCurrentlyActive);
+ }
+
+ /**
+ * Called by ItemTouchHelper on RecyclerView's onDraw callback.
+ * dX
,
+ * dY
.
+ * ItemTouchHelper also takes care of drawing the child after other children if it is being
+ * dragged. This is done using child re-ordering mechanism. On platforms prior to L, this
+ * is
+ * achieved via {@link ViewGroup#getChildDrawingOrder(int, int)} and on L
+ * and after, it changes View's elevation value to be greater than all other children.)
+ *
+ * @param c The canvas which RecyclerView is drawing its children
+ * @param recyclerView The RecyclerView to which ItemTouchHelper is attached to
+ * @param viewHolder The ViewHolder which is being interacted by the User or it was
+ * interacted and simply animating to its original position
+ * @param dX The amount of horizontal displacement caused by user's action
+ * @param dY The amount of vertical displacement caused by user's action
+ * @param actionState The type of interaction on the View. Is either {@link
+ * #ACTION_STATE_DRAG} or {@link #ACTION_STATE_SWIPE}.
+ * @param isCurrentlyActive True if this view is currently being controlled by the user or
+ * false it is simply animating back to its original state.
+ * @see #onChildDrawOver(Canvas, RecyclerView, ViewHolder, float, float, int,
+ * boolean)
+ */
+ public void onChildDrawOver(Canvas c, RecyclerView recyclerView,
+ ViewHolder viewHolder,
+ float dX, float dY, int actionState, boolean isCurrentlyActive) {
+ sUICallback.onDrawOver(c, recyclerView, viewHolder.itemView, dX, dY, actionState,
+ isCurrentlyActive);
+ }
+
+ /**
+ * Called by the ItemTouchHelper when user action finished on a ViewHolder and now the View
+ * will be animated to its final position.
+ * animationType
is {@link #ANIMATION_TYPE_DRAG}, it returns
+ * {@link RecyclerView.ItemAnimator#getMoveDuration()}, otherwise, it returns
+ * {@link RecyclerView.ItemAnimator#getRemoveDuration()}. If RecyclerView does not have
+ * any {@link RecyclerView.ItemAnimator} attached, this method returns
+ * {@code DEFAULT_DRAG_ANIMATION_DURATION} or {@code DEFAULT_SWIPE_ANIMATION_DURATION}
+ * depending on the animation type.
+ *
+ * @param recyclerView The RecyclerView to which the ItemTouchHelper is attached to.
+ * @param animationType The type of animation. Is one of {@link #ANIMATION_TYPE_DRAG},
+ * {@link #ANIMATION_TYPE_SWIPE_CANCEL} or
+ * {@link #ANIMATION_TYPE_SWIPE_SUCCESS}.
+ * @param animateDx The horizontal distance that the animation will offset
+ * @param animateDy The vertical distance that the animation will offset
+ * @return The duration for the animation
+ */
+ public long getAnimationDuration(RecyclerView recyclerView, int animationType,
+ float animateDx, float animateDy) {
+ final RecyclerView.ItemAnimator itemAnimator = recyclerView.getItemAnimator();
+ if (itemAnimator == null) {
+ return animationType == ANIMATION_TYPE_DRAG ? DEFAULT_DRAG_ANIMATION_DURATION
+ : DEFAULT_SWIPE_ANIMATION_DURATION;
+ } else {
+ return animationType == ANIMATION_TYPE_DRAG ? itemAnimator.getMoveDuration()
+ : itemAnimator.getRemoveDuration();
+ }
+ }
+
+ /**
+ * Called by the ItemTouchHelper when user is dragging a view out of bounds.
+ *
+ * ItemTouchHelper mIth = new ItemTouchHelper(
+ * new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN,
+ * ItemTouchHelper.LEFT) {
+ * public abstract boolean onMove(RecyclerView recyclerView,
+ * ViewHolder viewHolder, ViewHolder target) {
+ * final int fromPos = viewHolder.getAdapterPosition();
+ * final int toPos = target.getAdapterPosition();
+ * // move item in `fromPos` to `toPos` in adapter.
+ * return true;// true if moved, false otherwise
+ * }
+ * public void onSwiped(ViewHolder viewHolder, int direction) {
+ * // remove from adapter
+ * }
+ * });
+ *
+ */
+ public abstract static class SimpleCallback extends Callback {
+
+ private int mDefaultSwipeDirs;
+
+ private int mDefaultDragDirs;
+
+ /**
+ * Creates a Callback for the given drag and swipe allowance. These values serve as
+ * defaults
+ * and if you want to customize behavior per ViewHolder, you can override
+ * {@link #getSwipeDirs(RecyclerView, ViewHolder)}
+ * and / or {@link #getDragDirs(RecyclerView, ViewHolder)}.
+ *
+ * @param dragDirs Binary OR of direction flags in which the Views can be dragged. Must be
+ * composed of {@link #LEFT}, {@link #RIGHT}, {@link #START}, {@link
+ * #END},
+ * {@link #UP} and {@link #DOWN}.
+ * @param swipeDirs Binary OR of direction flags in which the Views can be swiped. Must be
+ * composed of {@link #LEFT}, {@link #RIGHT}, {@link #START}, {@link
+ * #END},
+ * {@link #UP} and {@link #DOWN}.
+ */
+ public SimpleCallback(int dragDirs, int swipeDirs) {
+ mDefaultSwipeDirs = swipeDirs;
+ mDefaultDragDirs = dragDirs;
+ }
+
+ /**
+ * Updates the default swipe directions. For example, you can use this method to toggle
+ * certain directions depending on your use case.
+ *
+ * @param defaultSwipeDirs Binary OR of directions in which the ViewHolders can be swiped.
+ */
+ public void setDefaultSwipeDirs(int defaultSwipeDirs) {
+ mDefaultSwipeDirs = defaultSwipeDirs;
+ }
+
+ /**
+ * Updates the default drag directions. For example, you can use this method to toggle
+ * certain directions depending on your use case.
+ *
+ * @param defaultDragDirs Binary OR of directions in which the ViewHolders can be dragged.
+ */
+ public void setDefaultDragDirs(int defaultDragDirs) {
+ mDefaultDragDirs = defaultDragDirs;
+ }
+
+ /**
+ * Returns the swipe directions for the provided ViewHolder.
+ * Default implementation returns the swipe directions that was set via constructor or
+ * {@link #setDefaultSwipeDirs(int)}.
+ *
+ * @param recyclerView The RecyclerView to which the ItemTouchHelper is attached to.
+ * @param viewHolder The RecyclerView for which the swipe direction is queried.
+ * @return A binary OR of direction flags.
+ */
+ public int getSwipeDirs(RecyclerView recyclerView, ViewHolder viewHolder) {
+ return mDefaultSwipeDirs;
+ }
+
+ /**
+ * Returns the drag directions for the provided ViewHolder.
+ * Default implementation returns the drag directions that was set via constructor or
+ * {@link #setDefaultDragDirs(int)}.
+ *
+ * @param recyclerView The RecyclerView to which the ItemTouchHelper is attached to.
+ * @param viewHolder The RecyclerView for which the swipe direction is queried.
+ * @return A binary OR of direction flags.
+ */
+ public int getDragDirs(RecyclerView recyclerView, ViewHolder viewHolder) {
+ return mDefaultDragDirs;
+ }
+
+ @Override
+ public int getMovementFlags(RecyclerView recyclerView, ViewHolder viewHolder) {
+ return makeMovementFlags(getDragDirs(recyclerView, viewHolder),
+ getSwipeDirs(recyclerView, viewHolder));
+ }
+ }
+
+ private class ItemTouchHelperGestureListener extends GestureDetector.SimpleOnGestureListener {
+
+ ItemTouchHelperGestureListener() {
+ }
+
+ @Override
+ public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
+ if (distanceY > distanceX)
+ closeOpenedPreItem();
+ mRecyclerView.stopNestedScroll();
+ return super.onScroll(e1, e2, distanceX, distanceY);
+
+ }
+
+ @Override
+ public boolean onSingleTapUp(MotionEvent e) {
+ return true;
+ }
+
+ @Override
+ public boolean onSingleTapConfirmed(MotionEvent e) {
+ closeOpenedPreItem();
+ return true;
+ }
+
+ @Override
+ public boolean onDown(MotionEvent e) {
+ return true;
+ }
+
+ @Override
+ public void onLongPress(MotionEvent e) {
+ closeOpenedPreItem();
+ View child = findChildView(e);
+ if (child != null) {
+ ViewHolder vh = mRecyclerView.getChildViewHolder(child);
+ if (vh != null) {
+ if (!mCallback.hasDragFlag(mRecyclerView, vh)) {
+ return;
+ }
+ int pointerId = e.getPointerId(0);
+ // Long press is deferred.
+ // Check w/ active pointer id to avoid selecting after motion
+ // event is canceled.
+ if (pointerId == mActivePointerId) {
+ final int index = e.findPointerIndex(mActivePointerId);
+ final float x = e.getX(index);
+ final float y = e.getY(index);
+ mInitialTouchX = x;
+ mInitialTouchY = y;
+ mDx = mDy = 0f;
+ if (DEBUG) {
+ Log.d(TAG,
+ "onlong press: x:" + mInitialTouchX + ",y:" + mInitialTouchY);
+ }
+ if (mCallback.isLongPressDragEnabled()) {
+ select(vh, ACTION_STATE_DRAG);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private class RecoverAnimation implements Animator.AnimatorListener {
+
+ final float mStartDx;
+
+ final float mStartDy;
+
+ final float mTargetX;
+
+ final float mTargetY;
+
+ final ViewHolder mViewHolder;
+
+ final int mActionState;
+
+ private final ValueAnimator mValueAnimator;
+
+ final int mAnimationType;
+
+ public boolean mIsPendingCleanup;
+
+ float mX;
+
+ float mY;
+
+ // if user starts touching a recovering view, we put it into interaction mode again,
+ // instantly.
+ boolean mOverridden = false;
+
+ boolean mEnded = false;
+
+ private float mFraction;
+
+ public RecoverAnimation(ViewHolder viewHolder, int animationType,
+ int actionState, float startDx, float startDy, float targetX, float targetY) {
+ mActionState = actionState;
+ mAnimationType = animationType;
+ mViewHolder = viewHolder;
+ mStartDx = startDx;
+ mStartDy = startDy;
+ mTargetX = targetX;
+ mTargetY = targetY;
+ mValueAnimator = ValueAnimator.ofInt(0, 1);
+ mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ setFraction(animation.getAnimatedFraction());
+ }
+ });
+ mValueAnimator.setTarget(viewHolder.itemView);
+ mValueAnimator.addListener(this);
+ setFraction(0f);
+ }
+
+ public void setDuration(long duration) {
+ mValueAnimator.setDuration(duration);
+ }
+
+ public void start() {
+ mViewHolder.setIsRecyclable(false);
+ mValueAnimator.start();
+ }
+
+ public void cancel() {
+ mValueAnimator.cancel();
+ }
+
+ public void setFraction(float fraction) {
+ mFraction = fraction;
+ }
+
+ /**
+ * We run updates on onDraw method but use the fraction from animator callback.
+ * This way, we can sync translate x/y values w/ the animators to avoid one-off frames.
+ */
+ public void update() {
+ if (mStartDx == mTargetX) {
+ mX = ViewCompat.getTranslationX(mViewHolder.itemView);
+ } else {
+ mX = mStartDx + mFraction * (mTargetX - mStartDx);
+ }
+ if (mStartDy == mTargetY) {
+ mY = ViewCompat.getTranslationY(mViewHolder.itemView);
+ } else {
+ mY = mStartDy + mFraction * (mTargetY - mStartDy);
+ }
+ }
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (!mEnded) {
+ mViewHolder.setIsRecyclable(true);
+ }
+ mEnded = true;
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ setFraction(1f); //make sure we recover the view's state.
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator animation) {
+
+ }
+ }
+
+ private void closeOpenedPreItem() {
+ final View view = mCallback.getItemFrontView(mPreOpened);
+ if (mPreOpened == null || view == null)
+ return;
+
+ ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(view, "translationX", view.getTranslationX(), 0f);
+ objectAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ super.onAnimationStart(animation);
+ if (mPreOpened != null) mCallback.clearView(mRecyclerView, mPreOpened);
+ if (mPreOpened != null) mPendingCleanup.remove(mPreOpened.itemView);
+ endRecoverAnimation(mPreOpened, true);
+ mPreOpened = mSelected;
+ }
+
+// @Override
+// public void onAnimationEnd(Animator animation) {
+// super.onAnimationEnd(animation);
+// mRecoverAnimations.clear();
+// }
+ });
+ view.setTag(objectAnimator);
+ objectAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
+ objectAnimator.start();
+ }
+
+ private void onItemClick(MotionEvent event) {
+ if (mSelected == null)
+ return;
+ View itemView = mSelected.itemView;
+ if (itemView instanceof ViewGroup) {
+ View consumeView = findConsumeView((ViewGroup) itemView, event.getRawX(), event.getRawY());
+ if (consumeView != null) {
+ consumeView.performClick();
+ }
+ }
+ }
+
+ private View findConsumeView(ViewGroup viewGroup, float rawX, float rawY) {
+ for (int i = viewGroup.getChildCount() - 1; i >= 0; i--) {
+ View childAt = viewGroup.getChildAt(i);
+ if (childAt instanceof ViewGroup) {
+ View consumeView = findConsumeView((ViewGroup) childAt, rawX, rawY);
+ if (consumeView != null)
+ return consumeView;
+ } else if (contains(childAt, rawX, rawY))
+ return childAt;
+
+ }
+ if (contains(viewGroup, rawX, rawY))
+ return viewGroup;
+ return null;
+ }
+
+ private boolean contains(View view, float rawX, float rawY) {
+ int[] location = new int[2];
+ view.getLocationOnScreen(location);
+ RectF rectF = new RectF(location[0], location[1], location[0] + view.getWidth(), location[1] + view.getHeight());
+ if (rectF.contains(rawX, rawY))
+ return true;
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/libitemtouchhelper/src/main/java/com/qiaomu/itemtouchhelper/itemtouchhelper/ItemTouchUIUtilImpl.java b/libitemtouchhelper/src/main/java/com/qiaomu/itemtouchhelper/itemtouchhelper/ItemTouchUIUtilImpl.java
new file mode 100644
index 0000000..89b5d24
--- /dev/null
+++ b/libitemtouchhelper/src/main/java/com/qiaomu/itemtouchhelper/itemtouchhelper/ItemTouchUIUtilImpl.java
@@ -0,0 +1,128 @@
+package com.qiaomu.itemtouchhelper.itemtouchhelper;
+
+/**
+ * Created by qiaomu on 2017/10/11.
+ */
+
+
+import android.graphics.Canvas;
+import android.support.v4.view.ViewCompat;
+import android.support.v7.recyclerview.R;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.helper.ItemTouchUIUtil;
+import android.view.View;
+
+
+/**
+ * Package private class to keep implementations. Putting them inside ItemTouchUIUtil makes them
+ * public API, which is not desired in this case.
+ */
+class ItemTouchUIUtilImpl {
+ static class Lollipop extends Honeycomb {
+ @Override
+ public void onDraw(Canvas c, RecyclerView recyclerView, View view,
+ float dX, float dY, int actionState, boolean isCurrentlyActive) {
+ if (isCurrentlyActive) {
+ Object originalElevation = view.getTag(R.id.item_touch_helper_previous_elevation);
+ if (originalElevation == null) {
+ originalElevation = ViewCompat.getElevation(view);
+ float newElevation = 1f + findMaxElevation(recyclerView, view);
+ ViewCompat.setElevation(view, newElevation);
+ view.setTag(R.id.item_touch_helper_previous_elevation, originalElevation);
+ }
+ }
+ super.onDraw(c, recyclerView, view, dX, dY, actionState, isCurrentlyActive);
+ }
+
+ private float findMaxElevation(RecyclerView recyclerView, View itemView) {
+ final int childCount = recyclerView.getChildCount();
+ float max = 0;
+ for (int i = 0; i < childCount; i++) {
+ final View child = recyclerView.getChildAt(i);
+ if (child == itemView) {
+ continue;
+ }
+ final float elevation = ViewCompat.getElevation(child);
+ if (elevation > max) {
+ max = elevation;
+ }
+ }
+ return max;
+ }
+
+ @Override
+ public void clearView(View view) {
+ final Object tag = view.getTag(R.id.item_touch_helper_previous_elevation);
+ if (tag != null && tag instanceof Float) {
+ ViewCompat.setElevation(view, (Float) tag);
+ }
+ view.setTag(R.id.item_touch_helper_previous_elevation, null);
+ super.clearView(view);
+ }
+ }
+
+ static class Honeycomb implements ItemTouchUIUtil {
+
+ @Override
+ public void clearView(View view) {
+ ViewCompat.setTranslationX(view, 0f);
+ ViewCompat.setTranslationY(view, 0f);
+ }
+
+ @Override
+ public void onSelected(View view) {
+
+ }
+
+ @Override
+ public void onDraw(Canvas c, RecyclerView recyclerView, View view,
+ float dX, float dY, int actionState, boolean isCurrentlyActive) {
+ ViewCompat.setTranslationX(view, dX);
+ ViewCompat.setTranslationY(view, dY);
+ }
+
+ @Override
+ public void onDrawOver(Canvas c, RecyclerView recyclerView,
+ View view, float dX, float dY, int actionState, boolean isCurrentlyActive) {
+
+ }
+ }
+
+ static class Gingerbread implements ItemTouchUIUtil {
+
+ private void draw(Canvas c, RecyclerView parent, View view,
+ float dX, float dY) {
+ c.save();
+ c.translate(dX, dY);
+ parent.drawChild(c, view, 0);
+ c.restore();
+ }
+
+ @Override
+ public void clearView(View view) {
+ view.setVisibility(View.VISIBLE);
+ }
+
+ @Override
+ public void onSelected(View view) {
+ view.setVisibility(View.INVISIBLE);
+ }
+
+ @Override
+ public void onDraw(Canvas c, RecyclerView recyclerView, View view,
+ float dX, float dY, int actionState, boolean isCurrentlyActive) {
+ if (actionState != ItemTouchHelper.ACTION_STATE_DRAG) {
+ draw(c, recyclerView, view, dX, dY);
+ }
+ }
+
+ @Override
+ public void onDrawOver(Canvas c, RecyclerView recyclerView,
+ View view, float dX, float dY,
+ int actionState, boolean isCurrentlyActive) {
+ if (actionState == ItemTouchHelper.ACTION_STATE_DRAG) {
+ draw(c, recyclerView, view, dX, dY);
+ }
+ }
+ }
+}
diff --git a/libitemtouchhelper/src/main/res/values/strings.xml b/libitemtouchhelper/src/main/res/values/strings.xml
new file mode 100644
index 0000000..ea6e718
--- /dev/null
+++ b/libitemtouchhelper/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+