449 | * You can attach SwipePositionItemTouchHelper to a RecyclerView via
450 | * {@link #attachToRecyclerView(RecyclerView)}. Upon attaching, it will add an item decoration,
451 | * an onItemTouchListener and a Child attach / detach listener to the RecyclerView.
452 | *
453 | * @param callback The Callback which controls the behavior of this touch helper.
454 | */
455 | public SwipePositionItemTouchHelper(Callback callback) {
456 | mCallback = callback;
457 | }
458 |
459 | private static boolean hitTest(View child, float x, float y, float left, float top) {
460 | return x >= left &&
461 | x <= left + child.getWidth() &&
462 | y >= top &&
463 | y <= top + child.getHeight();
464 | }
465 |
466 | /**
467 | * Attaches the SwipePositionItemTouchHelper to the provided RecyclerView. If TouchHelper is already
468 | * attached to a RecyclerView, it will first detach from the previous one. You can call this
469 | * method with {@code null} to detach it from the current RecyclerView.
470 | *
471 | * @param recyclerView The RecyclerView instance to which you want to add this helper or
472 | * {@code null} if you want to remove SwipePositionItemTouchHelper from the current
473 | * RecyclerView.
474 | */
475 | public void attachToRecyclerView(@Nullable RecyclerView recyclerView) {
476 | if (mRecyclerView == recyclerView) {
477 | return; // nothing to do
478 | }
479 | if (mRecyclerView != null) {
480 | destroyCallbacks();
481 | }
482 | mRecyclerView = recyclerView;
483 | if (mRecyclerView != null) {
484 | final Resources resources = recyclerView.getResources();
485 | mSwipeEscapeVelocity = resources
486 | .getDimension(R.dimen.item_touch_helper_swipe_escape_velocity);
487 | mMaxSwipeVelocity = resources
488 | .getDimension(R.dimen.item_touch_helper_swipe_escape_max_velocity);
489 | setupCallbacks();
490 | }
491 | }
492 |
493 | private void setupCallbacks() {
494 | ViewConfiguration vc = ViewConfiguration.get(mRecyclerView.getContext());
495 | mSlop = vc.getScaledTouchSlop();
496 | mRecyclerView.addItemDecoration(this);
497 | mRecyclerView.addOnItemTouchListener(mOnItemTouchListener);
498 | mRecyclerView.addOnChildAttachStateChangeListener(this);
499 | initGestureDetector();
500 | }
501 |
502 | private void destroyCallbacks() {
503 | mRecyclerView.removeItemDecoration(this);
504 | mRecyclerView.removeOnItemTouchListener(mOnItemTouchListener);
505 | mRecyclerView.removeOnChildAttachStateChangeListener(this);
506 | // clean all attached
507 | final int recoverAnimSize = mRecoverAnimations.size();
508 | for (int i = recoverAnimSize - 1; i >= 0; i--) {
509 | final RecoverAnimation recoverAnimation = mRecoverAnimations.get(0);
510 | mCallback.clearView(mRecyclerView, recoverAnimation.mViewHolder);
511 | }
512 | mRecoverAnimations.clear();
513 | mOverdrawChild = null;
514 | mOverdrawChildPosition = -1;
515 | releaseVelocityTracker();
516 | }
517 |
518 | private void initGestureDetector() {
519 | if (mGestureDetector != null) {
520 | return;
521 | }
522 | mGestureDetector = new GestureDetectorCompat(mRecyclerView.getContext(),
523 | new ItemTouchHelperGestureListener());
524 | }
525 |
526 | private void getSelectedDxDy(float[] outPosition) {
527 | if ((mSelectedFlags & (LEFT | RIGHT)) != 0) {
528 | outPosition[0] = mSelectedStartX + mDx - mSelected.itemView.getLeft();
529 | } else {
530 | outPosition[0] = ViewCompat.getTranslationX(mSelected.itemView);
531 | }
532 | if ((mSelectedFlags & (UP | DOWN)) != 0) {
533 | outPosition[1] = mSelectedStartY + mDy - mSelected.itemView.getTop();
534 | } else {
535 | outPosition[1] = ViewCompat.getTranslationY(mSelected.itemView);
536 | }
537 | }
538 |
539 | @Override
540 | public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
541 | float dx = 0, dy = 0;
542 | if (mSelected != null) {
543 | getSelectedDxDy(mTmpPosition);
544 | dx = mTmpPosition[0];
545 | dy = mTmpPosition[1];
546 | }
547 | mCallback.onDrawOver(c, parent, mSelected,
548 | mRecoverAnimations, mActionState, dx, dy);
549 | }
550 |
551 | @Override
552 | public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
553 | // we don't know if RV changed something so we should invalidate this index.
554 | mOverdrawChildPosition = -1;
555 | float dx = 0, dy = 0;
556 | if (mSelected != null) {
557 | getSelectedDxDy(mTmpPosition);
558 | dx = mTmpPosition[0];
559 | dy = mTmpPosition[1];
560 | }
561 | mCallback.onDraw(c, parent, mSelected,
562 | mRecoverAnimations, mActionState, dx, dy);
563 | }
564 |
565 | /**
566 | * Starts dragging or swiping the given View. Call with null if you want to clear it.
567 | *
568 | * @param selected The ViewHolder to drag or swipe. Can be null if you want to cancel the
569 | * current action
570 | * @param actionState The type of action
571 | */
572 | private void select(ViewHolder selected, int actionState) {
573 | if (selected == mSelected && actionState == mActionState) {
574 | return;
575 | }
576 | mDragScrollStartTimeInMs = Long.MIN_VALUE;
577 | final int prevActionState = mActionState;
578 | // prevent duplicate animations
579 | endRecoverAnimation(selected, true);
580 | mActionState = actionState;
581 | if (actionState == ACTION_STATE_DRAG) {
582 | // we remove after animation is complete. this means we only elevate the last drag
583 | // child but that should perform good enough as it is very hard to start dragging a
584 | // new child before the previous one settles.
585 | mOverdrawChild = selected.itemView;
586 | addChildDrawingOrderCallback();
587 | }
588 | int actionStateMask = (1 << (DIRECTION_FLAG_COUNT + DIRECTION_FLAG_COUNT * actionState))
589 | - 1;
590 | boolean preventLayout = false;
591 |
592 | if (mSelected != null) {
593 | final ViewHolder prevSelected = mSelected;
594 | if (prevSelected.itemView.getParent() != null) {
595 | final int swipeDir = prevActionState == ACTION_STATE_DRAG ? 0
596 | : swipeIfNecessary(prevSelected);
597 | releaseVelocityTracker();
598 | // find where we should animate to
599 | final float targetTranslateX, targetTranslateY;
600 | int animationType;
601 | switch (swipeDir) {
602 | case LEFT:
603 | case RIGHT:
604 | case START:
605 | case END:
606 | targetTranslateY = 0;
607 | targetTranslateX = Math.signum(mDx) * getSwipeWidth();
608 | break;
609 | case UP:
610 | case DOWN:
611 | targetTranslateX = 0;
612 | targetTranslateY = Math.signum(mDy) * mRecyclerView.getHeight();
613 | break;
614 | default:
615 | targetTranslateX = 0;
616 | targetTranslateY = 0;
617 | }
618 | if (prevActionState == ACTION_STATE_DRAG) {
619 | animationType = ANIMATION_TYPE_DRAG;
620 | } else if (swipeDir > 0) {
621 | animationType = ANIMATION_TYPE_SWIPE_SUCCESS;
622 | } else {
623 | animationType = ANIMATION_TYPE_SWIPE_CANCEL;
624 | }
625 | getSelectedDxDy(mTmpPosition);
626 | final float currentTranslateX = mTmpPosition[0];
627 | final float currentTranslateY = mTmpPosition[1];
628 | final RecoverAnimation rv = new RecoverAnimation(prevSelected, animationType,
629 | prevActionState, currentTranslateX, currentTranslateY,
630 | targetTranslateX, targetTranslateY) {
631 | @Override
632 | public void onAnimationEnd(Animator animation) {
633 | super.onAnimationEnd(animation);
634 | if (this.mOverridden) {
635 | return;
636 | }
637 | if (swipeDir <= 0) {
638 | // this is a drag or failed swipe. recover immediately
639 | mCallback.clearView(mRecyclerView, prevSelected);
640 | } else {
641 | // wait until remove animation is complete.
642 | mPendingCleanup.add(prevSelected.itemView);
643 | mPreOpened = prevSelected;
644 | mIsPendingCleanup = true;
645 | if (swipeDir > 0) {
646 | // Animation might be ended by other animators during a layout.
647 | // We defer callback to avoid editing adapter during a layout.
648 | postDispatchSwipe(this, swipeDir, currentTranslateX);
649 | }
650 | }
651 | // removed from the list after it is drawn for the last time
652 | if (mOverdrawChild == prevSelected.itemView) {
653 | removeChildDrawingOrderCallbackIfNecessary(prevSelected.itemView);
654 | }
655 | }
656 | };
657 | final long duration = mCallback.getAnimationDuration(mRecyclerView, animationType,
658 | targetTranslateX - currentTranslateX, targetTranslateY - currentTranslateY);
659 | rv.setDuration(duration);
660 | mRecoverAnimations.add(rv);
661 | rv.start();
662 | preventLayout = true;
663 | } else {
664 | removeChildDrawingOrderCallbackIfNecessary(prevSelected.itemView);
665 | mCallback.clearView(mRecyclerView, prevSelected);
666 | }
667 | mSelected = null;
668 | }
669 | if (selected != null) {
670 | mSelectedFlags =
671 | (mCallback.getAbsoluteMovementFlags(mRecyclerView, selected) & actionStateMask)
672 | >> (mActionState * DIRECTION_FLAG_COUNT);
673 | mSelectedStartX = selected.itemView.getLeft();
674 | mSelectedStartY = selected.itemView.getTop();
675 | mSelected = selected;
676 |
677 | if (actionState == ACTION_STATE_DRAG) {
678 | mSelected.itemView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
679 | }
680 | }
681 | final ViewParent rvParent = mRecyclerView.getParent();
682 | if (rvParent != null) {
683 | rvParent.requestDisallowInterceptTouchEvent(mSelected != null);
684 | }
685 | if (!preventLayout) {
686 | mRecyclerView.getLayoutManager().requestSimpleAnimationsInNextLayout();
687 | }
688 | mCallback.onSelectedChanged(mSelected, mActionState);
689 | mRecyclerView.invalidate();
690 | }
691 |
692 | private float getSwipeWidth() {
693 | return mRecyclerView.getWidth();
694 | }
695 |
696 | private void postDispatchSwipe(final RecoverAnimation anim, final int swipeDir,
697 | final float horizontalTouchPosition) {
698 | // wait until animations are complete.
699 | mRecyclerView.post(new Runnable() {
700 | @Override
701 | public void run() {
702 | if (mRecyclerView != null && mRecyclerView.isAttachedToWindow() &&
703 | !anim.mOverridden &&
704 | anim.mViewHolder.getAdapterPosition() != RecyclerView.NO_POSITION) {
705 | final RecyclerView.ItemAnimator animator = mRecyclerView.getItemAnimator();
706 | // if animator is running or we have other active recover animations, we try
707 | // not to call onSwiped because DefaultItemAnimator is not good at merging
708 | // animations. Instead, we wait and batch.
709 | if ((animator == null || !animator.isRunning(null))
710 | && !hasRunningRecoverAnim()) {
711 | mCallback.onSwiped(anim.mViewHolder, swipeDir, horizontalTouchPosition);
712 | } else {
713 | mRecyclerView.post(this);
714 | }
715 | }
716 | }
717 | });
718 | }
719 |
720 | private boolean hasRunningRecoverAnim() {
721 | final int size = mRecoverAnimations.size();
722 | for (int i = 0; i < size; i++) {
723 | if (!mRecoverAnimations.get(i).mEnded) {
724 | return true;
725 | }
726 | }
727 | return false;
728 | }
729 |
730 | /**
731 | * If user drags the view to the edge, trigger a scroll if necessary.
732 | */
733 | private boolean scrollIfNecessary() {
734 | if (mSelected == null) {
735 | mDragScrollStartTimeInMs = Long.MIN_VALUE;
736 | return false;
737 | }
738 | final long now = System.currentTimeMillis();
739 | final long scrollDuration = mDragScrollStartTimeInMs
740 | == Long.MIN_VALUE ? 0 : now - mDragScrollStartTimeInMs;
741 | RecyclerView.LayoutManager lm = mRecyclerView.getLayoutManager();
742 | if (mTmpRect == null) {
743 | mTmpRect = new Rect();
744 | }
745 | int scrollX = 0;
746 | int scrollY = 0;
747 | lm.calculateItemDecorationsForChild(mSelected.itemView, mTmpRect);
748 | if (lm.canScrollHorizontally()) {
749 | int curX = (int) (mSelectedStartX + mDx);
750 | final int leftDiff = curX - mTmpRect.left - mRecyclerView.getPaddingLeft();
751 | if (mDx < 0 && leftDiff < 0) {
752 | scrollX = leftDiff;
753 | } else if (mDx > 0) {
754 | final int rightDiff =
755 | curX + mSelected.itemView.getWidth() + mTmpRect.right
756 | - (mRecyclerView.getWidth() - mRecyclerView.getPaddingRight());
757 | if (rightDiff > 0) {
758 | scrollX = rightDiff;
759 | }
760 | }
761 | }
762 | if (lm.canScrollVertically()) {
763 | int curY = (int) (mSelectedStartY + mDy);
764 | final int topDiff = curY - mTmpRect.top - mRecyclerView.getPaddingTop();
765 | if (mDy < 0 && topDiff < 0) {
766 | scrollY = topDiff;
767 | } else if (mDy > 0) {
768 | final int bottomDiff = curY + mSelected.itemView.getHeight() + mTmpRect.bottom -
769 | (mRecyclerView.getHeight() - mRecyclerView.getPaddingBottom());
770 | if (bottomDiff > 0) {
771 | scrollY = bottomDiff;
772 | }
773 | }
774 | }
775 | if (scrollX != 0) {
776 | scrollX = mCallback.interpolateOutOfBoundsScroll(mRecyclerView,
777 | mSelected.itemView.getWidth(), scrollX,
778 | mRecyclerView.getWidth(), scrollDuration);
779 | }
780 | if (scrollY != 0) {
781 | scrollY = mCallback.interpolateOutOfBoundsScroll(mRecyclerView,
782 | mSelected.itemView.getHeight(), scrollY,
783 | mRecyclerView.getHeight(), scrollDuration);
784 | }
785 | if (scrollX != 0 || scrollY != 0) {
786 | if (mDragScrollStartTimeInMs == Long.MIN_VALUE) {
787 | mDragScrollStartTimeInMs = now;
788 | }
789 | mRecyclerView.scrollBy(scrollX, scrollY);
790 | return true;
791 | }
792 | mDragScrollStartTimeInMs = Long.MIN_VALUE;
793 | return false;
794 | }
795 |
796 | private List
1264 | */
1265 | @SuppressWarnings("UnusedParameters")
1266 | public abstract static class Callback {
1267 |
1268 | public static final int DEFAULT_DRAG_ANIMATION_DURATION = 200;
1269 |
1270 | public static final int DEFAULT_SWIPE_ANIMATION_DURATION = 250;
1271 |
1272 | static final int RELATIVE_DIR_FLAGS = START | END |
1273 | ((START | END) << DIRECTION_FLAG_COUNT) |
1274 | ((START | END) << (2 * DIRECTION_FLAG_COUNT));
1275 |
1276 | private static final ItemTouchUIUtil sUICallback;
1277 |
1278 | private static final int ABS_HORIZONTAL_DIR_FLAGS = LEFT | RIGHT |
1279 | ((LEFT | RIGHT) << DIRECTION_FLAG_COUNT) |
1280 | ((LEFT | RIGHT) << (2 * DIRECTION_FLAG_COUNT));
1281 |
1282 | private static final Interpolator sDragScrollInterpolator = new Interpolator() {
1283 | public float getInterpolation(float t) {
1284 | return t * t * t * t * t;
1285 | }
1286 | };
1287 |
1288 | private static final Interpolator sDragViewScrollCapInterpolator = new Interpolator() {
1289 | public float getInterpolation(float t) {
1290 | t -= 1.0f;
1291 | return t * t * t * t * t + 1.0f;
1292 | }
1293 | };
1294 |
1295 | /**
1296 | * Drag scroll speed keeps accelerating until this many milliseconds before being capped.
1297 | */
1298 | private static final long DRAG_SCROLL_ACCELERATION_LIMIT_TIME_MS = 2000;
1299 |
1300 | private int mCachedMaxScrollSpeed = -1;
1301 |
1302 | static {
1303 | if (Build.VERSION.SDK_INT >= 21) {
1304 | sUICallback = new ItemTouchUIUtilImpl.Lollipop();
1305 | } else if (Build.VERSION.SDK_INT >= 11) {
1306 | sUICallback = new ItemTouchUIUtilImpl.Honeycomb();
1307 | } else {
1308 | sUICallback = new ItemTouchUIUtilImpl.Gingerbread();
1309 | }
1310 | }
1311 |
1312 | /**
1313 | * Replaces a movement direction with its relative version by taking layout direction into
1314 | * account.
1315 | *
1316 | * @param flags The flag value that include any number of movement flags.
1317 | * @param layoutDirection The layout direction of the View. Can be obtained from
1318 | * {@link ViewCompat#getLayoutDirection(View)}.
1319 | * @return Updated flags which uses relative flags ({@link #START}, {@link #END}) instead
1320 | * of {@link #LEFT}, {@link #RIGHT}.
1321 | * @see #convertToAbsoluteDirection(int, int)
1322 | */
1323 | public static int convertToRelativeDirection(int flags, int layoutDirection) {
1324 | int masked = flags & ABS_HORIZONTAL_DIR_FLAGS;
1325 | if (masked == 0) {
1326 | return flags;// does not have any abs flags, good.
1327 | }
1328 | flags &= ~masked; //remove left / right.
1329 | if (layoutDirection == ViewCompat.LAYOUT_DIRECTION_LTR) {
1330 | // no change. just OR with 2 bits shifted mask and return
1331 | flags |= masked << 2; // START is 2 bits after LEFT, END is 2 bits after RIGHT.
1332 | return flags;
1333 | } else {
1334 | // add RIGHT flag as START
1335 | flags |= ((masked << 1) & ~ABS_HORIZONTAL_DIR_FLAGS);
1336 | // first clean RIGHT bit then add LEFT flag as END
1337 | flags |= ((masked << 1) & ABS_HORIZONTAL_DIR_FLAGS) << 2;
1338 | }
1339 | return flags;
1340 | }
1341 |
1342 | /**
1343 | * Convenience method to create movement flags.
1344 | *
1345 | * For instance, if you want to let your items be drag & dropped vertically and swiped
1346 | * left to be dismissed, you can call this method with:
1347 | *
1424 | * This method is used when selecting drop target for the dragged View. After Views are
1425 | * eliminated either via bounds check or via this method, resulting set of views will be
1426 | * passed to {@link #chooseDropTarget(ViewHolder, List, int, int)}.
1427 | *
1428 | * Default implementation returns true.
1429 | *
1430 | * @param recyclerView The RecyclerView to which SwipePositionItemTouchHelper is attached to.
1431 | * @param current The ViewHolder that user is dragging.
1432 | * @param target The ViewHolder which is below the dragged ViewHolder.
1433 | * @return True if the dragged ViewHolder can be replaced with the target ViewHolder, false
1434 | * otherwise.
1435 | */
1436 | public boolean canDropOver(RecyclerView recyclerView, ViewHolder current,
1437 | ViewHolder target) {
1438 | return true;
1439 | }
1440 |
1441 | /**
1442 | * Called when SwipePositionItemTouchHelper wants to move the dragged item from its old position to
1443 | * the new position.
1444 | *
1445 | * If this method returns true, SwipePositionItemTouchHelper assumes {@code viewHolder} has been moved
1446 | * to the adapter position of {@code target} ViewHolder
1447 | * ({@link ViewHolder#getAdapterPosition()
1448 | * ViewHolder#getAdapterPosition()}).
1449 | *
1450 | * If you don't support drag & drop, this method will never be called.
1451 | *
1452 | * @param recyclerView The RecyclerView to which SwipePositionItemTouchHelper is attached to.
1453 | * @param viewHolder The ViewHolder which is being dragged by the user.
1454 | * @param target The ViewHolder over which the currently active item is being
1455 | * dragged.
1456 | * @return True if the {@code viewHolder} has been moved to the adapter position of
1457 | * {@code target}.
1458 | * @see #onMoved(RecyclerView, ViewHolder, int, ViewHolder, int, int, int)
1459 | */
1460 | public abstract boolean onMove(RecyclerView recyclerView,
1461 | ViewHolder viewHolder, ViewHolder target);
1462 |
1463 |
1464 | public boolean isLongPressDragEnabled() {
1465 | return true;
1466 | }
1467 |
1468 | /**
1469 | * Returns whether SwipePositionItemTouchHelper should start a swipe operation if a pointer is swiped
1470 | * over the View.
1471 | *
1472 | * Default value returns true but you may want to disable this if you want to start
1473 | * swiping on a custom view touch using {@link #startSwipe(ViewHolder)}.
1474 | *
1475 | * @return True if SwipePositionItemTouchHelper should start swiping an item when user swipes a pointer
1476 | * over the View, false otherwise. Default value is
1498 | * Default value is .5f, which means, to swipe a View, user must move the View at least
1499 | * half of RecyclerView's width or height, depending on the swipe direction.
1500 | *
1501 | * @param viewHolder The ViewHolder that is being dragged.
1502 | * @return A float value that denotes the fraction of the View size. Default value
1503 | * is .5f .
1504 | */
1505 | public float getSwipeThreshold(ViewHolder viewHolder) {
1506 | return .5f;
1507 | }
1508 |
1509 | /**
1510 | * Returns the fraction that the user should move the View to be considered as it is
1511 | * dragged. After a view is moved this amount, SwipePositionItemTouchHelper starts checking for Views
1512 | * below it for a possible drop.
1513 | *
1514 | * @param viewHolder The ViewHolder that is being dragged.
1515 | * @return A float value that denotes the fraction of the View size. Default value is
1516 | * .5f .
1517 | */
1518 | public float getMoveThreshold(ViewHolder viewHolder) {
1519 | return .5f;
1520 | }
1521 |
1522 | /**
1523 | * Defines the minimum velocity which will be considered as a swipe action by the user.
1524 | *
1525 | * You can increase this value to make it harder to swipe or decrease it to make it easier.
1526 | * Keep in mind that SwipePositionItemTouchHelper also checks the perpendicular velocity and makes sure
1527 | * current direction velocity is larger then the perpendicular one. Otherwise, user's
1528 | * movement is ambiguous. You can change the threshold by overriding
1529 | * {@link #getSwipeVelocityThreshold(float)}.
1530 | *
1531 | * The velocity is calculated in pixels per second.
1532 | *
1533 | * The default framework value is passed as a parameter so that you can modify it with a
1534 | * multiplier.
1535 | *
1536 | * @param defaultValue The default value (in pixels per second) used by the
1537 | * SwipePositionItemTouchHelper.
1538 | * @return The minimum swipe velocity. The default implementation returns the
1539 | *
1550 | * To consider a movement as swipe, SwipePositionItemTouchHelper requires it to be larger than the
1551 | * perpendicular movement. If both directions reach to the max threshold, none of them will
1552 | * be considered as a swipe because it is usually an indication that user rather tried to
1553 | * scroll then swipe.
1554 | *
1555 | * The velocity is calculated in pixels per second.
1556 | *
1557 | * You can customize this behavior by changing this method. If you increase the value, it
1558 | * will be easier for the user to swipe diagonally and if you decrease the value, user will
1559 | * need to make a rather straight finger movement to trigger a swipe.
1560 | *
1561 | * @param defaultValue The default value(in pixels per second) used by the SwipePositionItemTouchHelper.
1562 | * @return The velocity cap for pointer movements. The default implementation returns the
1563 | *
1574 | * Default implementation filters the View with which dragged item have changed position
1575 | * in the drag direction. For instance, if the view is dragged UP, it compares the
1576 | *
1579 | * Among these Views which pass the test, the one closest to the dragged view is chosen.
1580 | *
1581 | * This method is called on the main thread every time user moves the View. If you want to
1582 | * override it, make sure it does not do any expensive operations.
1583 | *
1584 | * @param selected The ViewHolder being dragged by the user.
1585 | * @param dropTargets The list of ViewHolder that are under the dragged View and
1586 | * candidate as a drop.
1587 | * @param curX The updated left value of the dragged View after drag translations
1588 | * are applied. This value does not include margins added by
1589 | * {@link RecyclerView.ItemDecoration}s.
1590 | * @param curY The updated top value of the dragged View after drag translations
1591 | * are applied. This value does not include margins added by
1592 | * {@link RecyclerView.ItemDecoration}s.
1593 | * @return A FolderViewHolder to whose position the dragged FolderViewHolder should be
1594 | * moved to.
1595 | */
1596 | public ViewHolder chooseDropTarget(ViewHolder selected,
1597 | List
1655 | * If you are returning relative directions ({@link #START} , {@link #END}) from the
1656 | * {@link #getMovementFlags(RecyclerView, ViewHolder)} method, this method
1657 | * will also use relative directions. Otherwise, it will use absolute directions.
1658 | *
1659 | * If you don't support swiping, this method will never be called.
1660 | *
1661 | * SwipePositionItemTouchHelper will keep a reference to the View until it is detached from
1662 | * RecyclerView.
1663 | * As soon as it is detached, SwipePositionItemTouchHelper will call
1664 | * {@link #clearView(RecyclerView, ViewHolder)}.
1665 | *
1666 | * @param viewHolder The ViewHolder which has been swiped by the user.
1667 | * @param direction The direction to which the ViewHolder is swiped. It is one of
1668 | * {@link #UP}, {@link #DOWN},
1669 | * {@link #LEFT} or {@link #RIGHT}. If your
1670 | * {@link #getMovementFlags(RecyclerView, ViewHolder)}
1671 | * method
1672 | * returned relative flags instead of {@link #LEFT} / {@link #RIGHT};
1673 | * `direction` will be relative as well. ({@link #START} or {@link
1674 | * #END}).
1675 | */
1676 | public abstract void onSwiped(ViewHolder viewHolder, int direction,
1677 | float horizontalTouchPosition);
1678 |
1679 |
1680 | /**
1681 | * @param viewHolder this is pre action viewHolder, there we think view has two child
1682 | * first one is back action view.Front is show view.
1683 | * @return
1684 | */
1685 | public View getItemFrontView(ViewHolder viewHolder) {
1686 | if (viewHolder == null) return null;
1687 | if (viewHolder.itemView instanceof ViewGroup && ((ViewGroup) viewHolder.itemView).getChildCount() > 1) {
1688 | ViewGroup viewGroup = (ViewGroup) viewHolder.itemView;
1689 | return viewGroup.getChildAt(viewGroup.getChildCount() - 1);
1690 | } else {
1691 | return viewHolder.itemView;
1692 | }
1693 | }
1694 |
1695 | public void onSelectedChanged(ViewHolder viewHolder, int actionState) {
1696 | if (viewHolder != null) {
1697 | sUICallback.onSelected(viewHolder.itemView);
1698 | }
1699 | }
1700 |
1701 | private int getMaxDragScroll(RecyclerView recyclerView) {
1702 | if (mCachedMaxScrollSpeed == -1) {
1703 | mCachedMaxScrollSpeed = recyclerView.getResources().getDimensionPixelSize(
1704 | R.dimen.item_touch_helper_max_drag_scroll_per_frame);
1705 | }
1706 | return mCachedMaxScrollSpeed;
1707 | }
1708 |
1709 | /**
1710 | * Called when {@link #onMove(RecyclerView, ViewHolder, ViewHolder)} returns true.
1711 | *
1712 | * SwipePositionItemTouchHelper does not create an extra Bitmap or View while dragging, instead, it
1713 | * modifies the existing View. Because of this reason, it is important that the View is
1714 | * still part of the layout after it is moved. This may not work as intended when swapped
1715 | * Views are close to RecyclerView bounds or there are gaps between them (e.g. other Views
1716 | * which were not eligible for dropping over).
1717 | *
1718 | * This method is responsible to give necessary hint to the LayoutManager so that it will
1719 | * keep the View in visible area. For example, for LinearLayoutManager, this is as simple
1720 | *
1721 | *
1722 | * Default implementation calls {@link RecyclerView#scrollToPosition(int)} if the View's
1723 | * new position is likely to be out of bounds.
1724 | *
1725 | * It is important to ensure the ViewHolder will stay visible as otherwise, it might be
1726 | * removed by the LayoutManager if the move causes the View to go out of bounds. In that
1727 | * case, drag will end prematurely.
1728 | *
1729 | * @param recyclerView The RecyclerView controlled by the SwipePositionItemTouchHelper.
1730 | * @param viewHolder The ViewHolder under user's control.
1731 | * @param fromPos The previous adapter position of the dragged item (before it was
1732 | * moved).
1733 | * @param target The ViewHolder on which the currently active item has been dropped.
1734 | * @param toPos The new adapter position of the dragged item.
1735 | * @param x The updated left value of the dragged View after drag translations
1736 | * are applied. This value does not include margins added by
1737 | * {@link RecyclerView.ItemDecoration}s.
1738 | * @param y The updated top value of the dragged View after drag translations
1739 | * are applied. This value does not include margins added by
1740 | * {@link RecyclerView.ItemDecoration}s.
1741 | */
1742 | public void onMoved(final RecyclerView recyclerView,
1743 | final ViewHolder viewHolder, int fromPos, final ViewHolder target, int toPos, int x,
1744 | int y) {
1745 | final RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
1746 | if (layoutManager instanceof ViewDropHandler) {
1747 | ((ViewDropHandler) layoutManager).prepareForDrop(viewHolder.itemView,
1748 | target.itemView, x, y);
1749 | return;
1750 | }
1751 |
1752 | // if layout manager cannot handle it, do some guesswork
1753 | if (layoutManager.canScrollHorizontally()) {
1754 | final int minLeft = layoutManager.getDecoratedLeft(target.itemView);
1755 | if (minLeft <= recyclerView.getPaddingLeft()) {
1756 | recyclerView.scrollToPosition(toPos);
1757 | }
1758 | final int maxRight = layoutManager.getDecoratedRight(target.itemView);
1759 | if (maxRight >= recyclerView.getWidth() - recyclerView.getPaddingRight()) {
1760 | recyclerView.scrollToPosition(toPos);
1761 | }
1762 | }
1763 |
1764 | if (layoutManager.canScrollVertically()) {
1765 | final int minTop = layoutManager.getDecoratedTop(target.itemView);
1766 | if (minTop <= recyclerView.getPaddingTop()) {
1767 | recyclerView.scrollToPosition(toPos);
1768 | }
1769 | final int maxBottom = layoutManager.getDecoratedBottom(target.itemView);
1770 | if (maxBottom >= recyclerView.getHeight() - recyclerView.getPaddingBottom()) {
1771 | recyclerView.scrollToPosition(toPos);
1772 | }
1773 | }
1774 | }
1775 |
1776 | private void onDraw(Canvas c, RecyclerView parent, ViewHolder selected,
1777 | List
1829 | * This is a good place to clear all changes on the View that was done in
1830 | * {@link #onSelectedChanged(RecyclerView.ViewHolder, int)},
1831 | * {@link #onChildDraw(Canvas, RecyclerView, ViewHolder, float, float, int,
1832 | * boolean)} or
1833 | * {@link #onChildDrawOver(Canvas, RecyclerView, ViewHolder, float, float, int, boolean)}.
1834 | *
1835 | * @param recyclerView The RecyclerView which is controlled by the SwipePositionItemTouchHelper.
1836 | * @param viewHolder The View that was interacted by the user.
1837 | */
1838 | public void clearView(RecyclerView recyclerView, ViewHolder viewHolder) {
1839 | sUICallback.clearView(viewHolder.itemView);
1840 | }
1841 |
1842 | /**
1843 | * Called by SwipePositionItemTouchHelper on RecyclerView's onDraw callback.
1844 | *
1845 | * If you would like to customize how your View's respond to user interactions, this is
1846 | * a good place to override.
1847 | *
1848 | * Default implementation translates the child by the given
1879 | * If you would like to customize how your View's respond to user interactions, this is
1880 | * a good place to override.
1881 | *
1882 | * Default implementation translates the child by the given
1914 | * Default implementation uses ItemAnimator's duration values. If
1915 | *
1945 | * You can override this method to decide how much RecyclerView should scroll in response
1946 | * to this action. Default implementation calculates a value based on the amount of View
1947 | * out of bounds and the time it spent there. The longer user keeps the View out of bounds,
1948 | * the faster the list will scroll. Similarly, the larger portion of the View is out of
1949 | * bounds, the faster the RecyclerView will scroll.
1950 | *
1951 | * @param recyclerView The RecyclerView instance to which SwipePositionItemTouchHelper is
1952 | * attached to.
1953 | * @param viewSize The total size of the View in scroll direction, excluding
1954 | * item decorations.
1955 | * @param viewSizeOutOfBounds The total size of the View that is out of bounds. This value
1956 | * is negative if the View is dragged towards left or top edge.
1957 | * @param totalSize The total size of RecyclerView in the scroll direction.
1958 | * @param msSinceStartScroll The time passed since View is kept out of bounds.
1959 | * @return The amount that RecyclerView should scroll. Keep in mind that this value will
1960 | * be passed to {@link RecyclerView#scrollBy(int, int)} method.
1961 | */
1962 | public int interpolateOutOfBoundsScroll(RecyclerView recyclerView,
1963 | int viewSize, int viewSizeOutOfBounds,
1964 | int totalSize, long msSinceStartScroll) {
1965 | final int maxScroll = getMaxDragScroll(recyclerView);
1966 | final int absOutOfBounds = Math.abs(viewSizeOutOfBounds);
1967 | final int direction = (int) Math.signum(viewSizeOutOfBounds);
1968 | // might be negative if other direction
1969 | float outOfBoundsRatio = Math.min(1f, 1f * absOutOfBounds / viewSize);
1970 | final int cappedScroll = (int) (direction * maxScroll *
1971 | sDragViewScrollCapInterpolator.getInterpolation(outOfBoundsRatio));
1972 | final float timeRatio;
1973 | if (msSinceStartScroll > DRAG_SCROLL_ACCELERATION_LIMIT_TIME_MS) {
1974 | timeRatio = 1f;
1975 | } else {
1976 | timeRatio = (float) msSinceStartScroll / DRAG_SCROLL_ACCELERATION_LIMIT_TIME_MS;
1977 | }
1978 | final int value = (int) (cappedScroll * sDragScrollInterpolator
1979 | .getInterpolation(timeRatio));
1980 | if (value == 0) {
1981 | return viewSizeOutOfBounds > 0 ? 1 : -1;
1982 | }
1983 | return value;
1984 | }
1985 |
1986 | }
1987 |
1988 | private class ItemTouchHelperGestureListener extends GestureDetector.SimpleOnGestureListener {
1989 |
1990 | @Override
1991 | public boolean onDown(MotionEvent e) {
1992 | return true;
1993 | }
1994 |
1995 | @Override
1996 | public void onLongPress(MotionEvent e) {
1997 | View child = findChildView(e);
1998 | if (child != null) {
1999 | ViewHolder vh = mRecyclerView.getChildViewHolder(child);
2000 | if (vh != null) {
2001 | if (!mCallback.hasDragFlag(mRecyclerView, vh)) {
2002 | return;
2003 | }
2004 | int pointerId = MotionEventCompat.getPointerId(e, 0);
2005 | // Long press is deferred.
2006 | // Check w/ active pointer id to avoid selecting after motion
2007 | // event is canceled.
2008 | if (pointerId == mActivePointerId) {
2009 | final int index = MotionEventCompat
2010 | .findPointerIndex(e, mActivePointerId);
2011 | final float x = MotionEventCompat.getX(e, index);
2012 | final float y = MotionEventCompat.getY(e, index);
2013 | mInitialTouchX = x;
2014 | mInitialTouchY = y;
2015 | mDx = mDy = 0f;
2016 | if (DEBUG) {
2017 | Log.d(TAG,
2018 | "onlong press: x:" + mInitialTouchX + ",y:" + mInitialTouchY);
2019 | }
2020 | if (mCallback.isLongPressDragEnabled()) {
2021 | select(vh, ACTION_STATE_DRAG);
2022 | }
2023 | }
2024 | }
2025 | }
2026 | }
2027 |
2028 | @Override
2029 | public boolean onContextClick(MotionEvent e) {
2030 | return super.onContextClick(e);
2031 | }
2032 | }
2033 |
2034 | private class RecoverAnimation implements Animator.AnimatorListener {
2035 |
2036 | final float mStartDx;
2037 |
2038 | final float mStartDy;
2039 |
2040 | final float mTargetX;
2041 |
2042 | final float mTargetY;
2043 |
2044 | final ViewHolder mViewHolder;
2045 |
2046 | final int mActionState;
2047 |
2048 | private final ValueAnimator mValueAnimator;
2049 |
2050 | private final int mAnimationType;
2051 |
2052 | public boolean mIsPendingCleanup;
2053 |
2054 | float mX;
2055 |
2056 | float mY;
2057 |
2058 | // if user starts touching a recovering view, we put it into interaction mode again,
2059 | // instantly.
2060 | boolean mOverridden = false;
2061 |
2062 | private boolean mEnded = false;
2063 |
2064 | private float mFraction;
2065 |
2066 | public RecoverAnimation(ViewHolder viewHolder, int animationType,
2067 | int actionState, float startDx, float startDy, float targetX, float targetY) {
2068 | mActionState = actionState;
2069 | mAnimationType = animationType;
2070 | mViewHolder = viewHolder;
2071 | mStartDx = startDx;
2072 | mStartDy = startDy;
2073 | mTargetX = targetX;
2074 | mTargetY = targetY;
2075 | mValueAnimator = ValueAnimator.ofFloat(0f, 1f);
2076 | mValueAnimator.addUpdateListener(
2077 | new ValueAnimator.AnimatorUpdateListener() {
2078 | @Override
2079 | public void onAnimationUpdate(ValueAnimator animation) {
2080 | setFraction(animation.getAnimatedFraction());
2081 | }
2082 | });
2083 | mValueAnimator.setTarget(viewHolder.itemView);
2084 | mValueAnimator.addListener(this);
2085 | setFraction(0f);
2086 | }
2087 |
2088 | public void setDuration(long duration) {
2089 | mValueAnimator.setDuration(duration);
2090 | }
2091 |
2092 | public void start() {
2093 | mViewHolder.setIsRecyclable(false);
2094 | mValueAnimator.start();
2095 | }
2096 |
2097 | public void cancel() {
2098 | mValueAnimator.cancel();
2099 | }
2100 |
2101 | public void setFraction(float fraction) {
2102 | mFraction = fraction;
2103 | }
2104 |
2105 | /**
2106 | * We run updates on onDraw method but use the fraction from animator callback.
2107 | * This way, we can sync translate x/y values w/ the animators to avoid one-off frames.
2108 | */
2109 | public void update() {
2110 | /*if (mStartDx == mTargetX) {
2111 | // mX = ViewCompat.getTranslationX(mViewHolder.itemView);
2112 | } else */{
2113 | mX = mStartDx + mFraction * (mTargetX - mStartDx);
2114 | }
2115 | if (mStartDy == mTargetY) {
2116 | mY = ViewCompat.getTranslationY(mViewHolder.itemView);
2117 | } else {
2118 | mY = mStartDy + mFraction * (mTargetY - mStartDy);
2119 | }
2120 | }
2121 |
2122 | public float getHitX() {
2123 | return mX;
2124 | }
2125 |
2126 | public float getHitY() {
2127 | return mViewHolder.itemView.getY() + mY;
2128 | }
2129 |
2130 | @Override
2131 | public void onAnimationStart(Animator animation) {
2132 |
2133 | }
2134 |
2135 | @Override
2136 | public void onAnimationEnd(Animator animation) {
2137 | if (!mEnded) {
2138 | mViewHolder.setIsRecyclable(true);
2139 | }
2140 | mEnded = true;
2141 | }
2142 |
2143 | @Override
2144 | public void onAnimationCancel(Animator animation) {
2145 | setFraction(1f); //make sure we recover the view's state.
2146 | }
2147 |
2148 | @Override
2149 | public void onAnimationRepeat(Animator animation) {
2150 |
2151 | }
2152 | }
2153 | }
2154 |
--------------------------------------------------------------------------------
/app/src/main/java/org/buffer/android/multiactionswipehelper/SwipeToPerformActionCallback.kt:
--------------------------------------------------------------------------------
1 | package org.buffer.android.multiactionswipe
2 |
3 | import android.graphics.Canvas
4 | import android.graphics.Color
5 | import android.graphics.Paint
6 | import android.graphics.PorterDuff
7 | import android.graphics.PorterDuffXfermode
8 | import android.graphics.Rect
9 | import android.graphics.drawable.ColorDrawable
10 | import android.graphics.drawable.Drawable
11 | import android.support.v4.content.ContextCompat
12 | import android.support.v7.widget.RecyclerView
13 | import android.support.v7.widget.helper.ItemTouchHelper.LEFT
14 | import android.support.v7.widget.helper.ItemTouchHelper.RIGHT
15 |
16 | class SwipeToPerformActionCallback(private val swipeListener: SwipeActionListener,
17 | private val textPadding: Int = 0,
18 | var conversationActions: ListmakeMovementFlags(UP | DOWN, LEFT);
1348 | *
1349 | * @param dragFlags The directions in which the item can be dragged.
1350 | * @param swipeFlags The directions in which the item can be swiped.
1351 | * @return Returns an integer composed of the given drag and swipe flags.
1352 | */
1353 | public static int makeMovementFlags(int dragFlags, int swipeFlags) {
1354 | return makeFlag(ACTION_STATE_IDLE, swipeFlags | dragFlags) |
1355 | makeFlag(ACTION_STATE_SWIPE, swipeFlags) | makeFlag(ACTION_STATE_DRAG,
1356 | dragFlags);
1357 | }
1358 |
1359 | /**
1360 | * Shifts the given direction flags to the offset of the given action state.
1361 | *
1362 | * @param actionState The action state you want to get flags in. Should be one of
1363 | * {@link #ACTION_STATE_IDLE}, {@link #ACTION_STATE_SWIPE} or
1364 | * {@link #ACTION_STATE_DRAG}.
1365 | * @param directions The direction flags. Can be composed from {@link #UP}, {@link #DOWN},
1366 | * {@link #RIGHT}, {@link #LEFT} {@link #START} and {@link #END}.
1367 | * @return And integer that represents the given directions in the provided actionState.
1368 | */
1369 | public static int makeFlag(int actionState, int directions) {
1370 | return directions << (actionState * DIRECTION_FLAG_COUNT);
1371 | }
1372 |
1373 | public abstract int getMovementFlags(RecyclerView recyclerView,
1374 | ViewHolder viewHolder);
1375 |
1376 | /**
1377 | * Converts a given set of flags to absolution direction which means {@link #START} and
1378 | * {@link #END} are replaced with {@link #LEFT} and {@link #RIGHT} depending on the layout
1379 | * direction.
1380 | *
1381 | * @param flags The flag value that include any number of movement flags.
1382 | * @param layoutDirection The layout direction of the RecyclerView.
1383 | * @return Updated flags which includes only absolute direction values.
1384 | */
1385 | public int convertToAbsoluteDirection(int flags, int layoutDirection) {
1386 | int masked = flags & RELATIVE_DIR_FLAGS;
1387 | if (masked == 0) {
1388 | return flags;// does not have any relative flags, good.
1389 | }
1390 | flags &= ~masked; //remove start / end
1391 | if (layoutDirection == ViewCompat.LAYOUT_DIRECTION_LTR) {
1392 | // no change. just OR with 2 bits shifted mask and return
1393 | flags |= masked >> 2; // START is 2 bits after LEFT, END is 2 bits after RIGHT.
1394 | return flags;
1395 | } else {
1396 | // add START flag as RIGHT
1397 | flags |= ((masked >> 1) & ~RELATIVE_DIR_FLAGS);
1398 | // first clean start bit then add END flag as LEFT
1399 | flags |= ((masked >> 1) & RELATIVE_DIR_FLAGS) >> 2;
1400 | }
1401 | return flags;
1402 | }
1403 |
1404 | final int getAbsoluteMovementFlags(RecyclerView recyclerView,
1405 | ViewHolder viewHolder) {
1406 | final int flags = getMovementFlags(recyclerView, viewHolder);
1407 | return convertToAbsoluteDirection(flags, ViewCompat.getLayoutDirection(recyclerView));
1408 | }
1409 |
1410 | private boolean hasDragFlag(RecyclerView recyclerView, ViewHolder viewHolder) {
1411 | final int flags = getAbsoluteMovementFlags(recyclerView, viewHolder);
1412 | return (flags & ACTION_MODE_DRAG_MASK) != 0;
1413 | }
1414 |
1415 | private boolean hasSwipeFlag(RecyclerView recyclerView,
1416 | ViewHolder viewHolder) {
1417 | final int flags = getAbsoluteMovementFlags(recyclerView, viewHolder);
1418 | return (flags & ACTION_MODE_SWIPE_MASK) != 0;
1419 | }
1420 |
1421 | /**
1422 | * Return true if the current ViewHolder can be dropped over the the target ViewHolder.
1423 | * true
.
1477 | * @see #startSwipe(ViewHolder)
1478 | */
1479 | public boolean isItemViewSwipeEnabled() {
1480 | return true;
1481 | }
1482 |
1483 | /**
1484 | * When finding views under a dragged view, by default, SwipePositionItemTouchHelper searches for views
1485 | * that overlap with the dragged View. By overriding this method, you can extend or shrink
1486 | * the search box.
1487 | *
1488 | * @return The extra margin to be added to the hit box of the dragged View.
1489 | */
1490 | public int getBoundingBoxMargin() {
1491 | return 0;
1492 | }
1493 |
1494 | /**
1495 | * Returns the fraction that the user should move the View to be considered as swiped.
1496 | * The fraction is calculated with respect to RecyclerView's bounds.
1497 | * defaultValue
parameter.
1540 | * @see #getSwipeVelocityThreshold(float)
1541 | * @see #getSwipeThreshold(ViewHolder)
1542 | */
1543 | public float getSwipeEscapeVelocity(float defaultValue) {
1544 | return defaultValue;
1545 | }
1546 |
1547 | /**
1548 | * Defines the maximum velocity SwipePositionItemTouchHelper will ever calculate for pointer movements.
1549 | * defaultValue
parameter.
1564 | * @see #getSwipeEscapeVelocity(float)
1565 | */
1566 | public float getSwipeVelocityThreshold(float defaultValue) {
1567 | return defaultValue;
1568 | }
1569 |
1570 | /**
1571 | * Called by SwipePositionItemTouchHelper to select a drop target from the list of ViewHolders that
1572 | * are under the dragged View.
1573 | * view.getTop()
of the two views before and after drag started. If that value
1577 | * is different, the target view passes the filter.
1578 | * dX
,
1849 | * dY
.
1850 | * SwipePositionItemTouchHelper also takes care of drawing the child after other children if it is being
1851 | * dragged. This is done using child re-ordering mechanism. On platforms prior to L, this
1852 | * is
1853 | * achieved via {@link ViewGroup#getChildDrawingOrder(int, int)} and on L
1854 | * and after, it changes View's elevation value to be greater than all other children.)
1855 | *
1856 | * @param c The canvas which RecyclerView is drawing its children
1857 | * @param recyclerView The RecyclerView to which SwipePositionItemTouchHelper is attached to
1858 | * @param viewHolder The ViewHolder which is being interacted by the User or it was
1859 | * interacted and simply animating to its original position
1860 | * @param dX The amount of horizontal displacement caused by user's action
1861 | * @param dY The amount of vertical displacement caused by user's action
1862 | * @param actionState The type of interaction on the View. Is either {@link
1863 | * #ACTION_STATE_DRAG} or {@link #ACTION_STATE_SWIPE}.
1864 | * @param isCurrentlyActive True if this view is currently being controlled by the user or
1865 | * false it is simply animating back to its original state.
1866 | * @see #onChildDrawOver(Canvas, RecyclerView, ViewHolder, float, float, int,
1867 | * boolean)
1868 | */
1869 | public void onChildDraw(Canvas c, RecyclerView recyclerView,
1870 | ViewHolder viewHolder,
1871 | float dX, float dY, int actionState, boolean isCurrentlyActive) {
1872 | sUICallback.onDraw(c, recyclerView, viewHolder.itemView, dX, dY, actionState,
1873 | isCurrentlyActive);
1874 | }
1875 |
1876 | /**
1877 | * Called by SwipePositionItemTouchHelper on RecyclerView's onDraw callback.
1878 | * dX
,
1883 | * dY
.
1884 | * SwipePositionItemTouchHelper also takes care of drawing the child after other children if it is being
1885 | * dragged. This is done using child re-ordering mechanism. On platforms prior to L, this
1886 | * is
1887 | * achieved via {@link ViewGroup#getChildDrawingOrder(int, int)} and on L
1888 | * and after, it changes View's elevation value to be greater than all other children.)
1889 | *
1890 | * @param c The canvas which RecyclerView is drawing its children
1891 | * @param recyclerView The RecyclerView to which SwipePositionItemTouchHelper is attached to
1892 | * @param viewHolder The ViewHolder which is being interacted by the User or it was
1893 | * interacted and simply animating to its original position
1894 | * @param dX The amount of horizontal displacement caused by user's action
1895 | * @param dY The amount of vertical displacement caused by user's action
1896 | * @param actionState The type of interaction on the View. Is either {@link
1897 | * #ACTION_STATE_DRAG} or {@link #ACTION_STATE_SWIPE}.
1898 | * @param isCurrentlyActive True if this view is currently being controlled by the user or
1899 | * false it is simply animating back to its original state.
1900 | * @see #onChildDrawOver(Canvas, RecyclerView, ViewHolder, float, float, int,
1901 | * boolean)
1902 | */
1903 | public void onChildDrawOver(Canvas c, RecyclerView recyclerView,
1904 | ViewHolder viewHolder,
1905 | float dX, float dY, int actionState, boolean isCurrentlyActive) {
1906 | sUICallback.onDrawOver(c, recyclerView, viewHolder.itemView, dX, dY, actionState,
1907 | isCurrentlyActive);
1908 | }
1909 |
1910 | /**
1911 | * Called by the SwipePositionItemTouchHelper when user action finished on a ViewHolder and now the View
1912 | * will be animated to its final position.
1913 | * animationType
is {@link #ANIMATION_TYPE_DRAG}, it returns
1916 | * {@link RecyclerView.ItemAnimator#getMoveDuration()}, otherwise, it returns
1917 | * {@link RecyclerView.ItemAnimator#getRemoveDuration()}. If RecyclerView does not have
1918 | * any {@link RecyclerView.ItemAnimator} attached, this method returns
1919 | * {@code DEFAULT_DRAG_ANIMATION_DURATION} or {@code DEFAULT_SWIPE_ANIMATION_DURATION}
1920 | * depending on the animation type.
1921 | *
1922 | * @param recyclerView The RecyclerView to which the SwipePositionItemTouchHelper is attached to.
1923 | * @param animationType The type of animation. Is one of {@link #ANIMATION_TYPE_DRAG},
1924 | * {@link #ANIMATION_TYPE_SWIPE_CANCEL} or
1925 | * {@link #ANIMATION_TYPE_SWIPE_SUCCESS}.
1926 | * @param animateDx The horizontal distance that the animation will offset
1927 | * @param animateDy The vertical distance that the animation will offset
1928 | * @return The duration for the animation
1929 | */
1930 | public long getAnimationDuration(RecyclerView recyclerView, int animationType,
1931 | float animateDx, float animateDy) {
1932 | final RecyclerView.ItemAnimator itemAnimator = recyclerView.getItemAnimator();
1933 | if (itemAnimator == null) {
1934 | return animationType == ANIMATION_TYPE_DRAG ? DEFAULT_DRAG_ANIMATION_DURATION
1935 | : DEFAULT_SWIPE_ANIMATION_DURATION;
1936 | } else {
1937 | return animationType == ANIMATION_TYPE_DRAG ? itemAnimator.getMoveDuration()
1938 | : itemAnimator.getRemoveDuration();
1939 | }
1940 | }
1941 |
1942 | /**
1943 | * Called by the SwipePositionItemTouchHelper when user is dragging a view out of bounds.
1944 | *