394 | * You can attach ItemTouchHelper to a RecyclerView via
395 | * {@link #attachToRecyclerView(RecyclerView)}. Upon attaching, it will add an item decoration,
396 | * an onItemTouchListener and a Child attach / detach listener to the RecyclerView.
397 | *
398 | * @param callback The Callback which controls the behavior of this touch helper.
399 | */
400 | public ItemTouchHelper(Callback callback) {
401 | mCallback = callback;
402 | }
403 |
404 | private static boolean hitTest(View child, float x, float y, float left, float top) {
405 | return x >= left
406 | && x <= left + child.getWidth()
407 | && y >= top
408 | && y <= top + child.getHeight();
409 | }
410 |
411 | /**
412 | * Attaches the ItemTouchHelper to the provided RecyclerView. If TouchHelper is already
413 | * attached to a RecyclerView, it will first detach from the previous one. You can call this
414 | * method with {@code null} to detach it from the current RecyclerView.
415 | *
416 | * @param recyclerView The RecyclerView instance to which you want to add this helper or
417 | * {@code null} if you want to remove ItemTouchHelper from the current
418 | * RecyclerView.
419 | */
420 | public void attachToRecyclerView(@Nullable RecyclerView recyclerView) {
421 | if (mRecyclerView == recyclerView) {
422 | return; // nothing to do
423 | }
424 | if (mRecyclerView != null) {
425 | destroyCallbacks();
426 | }
427 | mRecyclerView = recyclerView;
428 | if (mRecyclerView != null) {
429 | final Resources resources = recyclerView.getResources();
430 | mSwipeEscapeVelocity = resources
431 | .getDimension(android.support.v7.recyclerview.R.dimen.item_touch_helper_swipe_escape_velocity);
432 | mMaxSwipeVelocity = resources
433 | .getDimension(android.support.v7.recyclerview.R.dimen.item_touch_helper_swipe_escape_max_velocity);
434 | setupCallbacks();
435 | }
436 | }
437 |
438 | private void setupCallbacks() {
439 | ViewConfiguration vc = ViewConfiguration.get(mRecyclerView.getContext());
440 | mSlop = vc.getScaledTouchSlop();
441 | mRecyclerView.addItemDecoration(this);
442 | mRecyclerView.addOnItemTouchListener(mOnItemTouchListener);
443 | mRecyclerView.addOnChildAttachStateChangeListener(this);
444 | initGestureDetector();
445 | }
446 |
447 | private void destroyCallbacks() {
448 | mRecyclerView.removeItemDecoration(this);
449 | mRecyclerView.removeOnItemTouchListener(mOnItemTouchListener);
450 | mRecyclerView.removeOnChildAttachStateChangeListener(this);
451 | // clean all attached
452 | final int recoverAnimSize = mRecoverAnimations.size();
453 | for (int i = recoverAnimSize - 1; i >= 0; i--) {
454 | final RecoverAnimation recoverAnimation = mRecoverAnimations.get(0);
455 | mCallback.clearView(mRecyclerView, recoverAnimation.mViewHolder);
456 | }
457 | mRecoverAnimations.clear();
458 | mOverdrawChild = null;
459 | mOverdrawChildPosition = -1;
460 | releaseVelocityTracker();
461 | }
462 |
463 | private void initGestureDetector() {
464 | if (mGestureDetector != null) {
465 | return;
466 | }
467 | mGestureDetector = new GestureDetectorCompat(mRecyclerView.getContext(),
468 | new ItemTouchHelperGestureListener());
469 | }
470 |
471 | private void getSelectedDxDy(float[] outPosition) {
472 | if ((mSelectedFlags & (LEFT | RIGHT)) != 0) {
473 | outPosition[0] = mSelectedStartX + mDx - mSelected.itemView.getLeft();
474 | } else {
475 | outPosition[0] = mSelected.itemView.getTranslationX();
476 | }
477 | if ((mSelectedFlags & (UP | DOWN)) != 0) {
478 | outPosition[1] = mSelectedStartY + mDy - mSelected.itemView.getTop();
479 | } else {
480 | outPosition[1] = mSelected.itemView.getTranslationY();
481 | }
482 | }
483 |
484 | @Override
485 | public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
486 | float dx = 0, dy = 0;
487 | if (mSelected != null) {
488 | getSelectedDxDy(mTmpPosition);
489 | dx = mTmpPosition[0];
490 | dy = mTmpPosition[1];
491 | }
492 | mCallback.onDrawOver(c, parent, mSelected,
493 | mRecoverAnimations, mActionState, dx, dy);
494 | }
495 |
496 | @Override
497 | public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
498 | // we don't know if RV changed something so we should invalidate this index.
499 | mOverdrawChildPosition = -1;
500 | float dx = 0, dy = 0;
501 | if (mSelected != null) {
502 | getSelectedDxDy(mTmpPosition);
503 | dx = mTmpPosition[0];
504 | dy = mTmpPosition[1];
505 | }
506 | mCallback.onDraw(c, parent, mSelected,
507 | mRecoverAnimations, mActionState, dx, dy);
508 | }
509 |
510 | /**
511 | * Starts dragging or swiping the given View. Call with null if you want to clear it.
512 | *
513 | * @param selected The ViewHolder to drag or swipe. Can be null if you want to cancel the
514 | * current action
515 | * @param actionState The type of action
516 | */
517 | void select(RecyclerView.ViewHolder selected, int actionState) {
518 | if (selected == mSelected && actionState == mActionState) {
519 | return;
520 | }
521 | mDragScrollStartTimeInMs = Long.MIN_VALUE;
522 | final int prevActionState = mActionState;
523 | // prevent duplicate animations
524 | endRecoverAnimation(selected, true);
525 | mActionState = actionState;
526 | if (actionState == ACTION_STATE_DRAG) {
527 | // we remove after animation is complete. this means we only elevate the last drag
528 | // child but that should perform good enough as it is very hard to start dragging a
529 | // new child before the previous one settles.
530 | mOverdrawChild = selected.itemView;
531 | addChildDrawingOrderCallback();
532 | }
533 | int actionStateMask = (1 << (DIRECTION_FLAG_COUNT + DIRECTION_FLAG_COUNT * actionState))
534 | - 1;
535 | boolean preventLayout = false;
536 |
537 | if (mSelected != null) {
538 | final RecyclerView.ViewHolder prevSelected = mSelected;
539 | if (prevSelected.itemView.getParent() != null) {
540 | final int swipeDir = prevActionState == ACTION_STATE_DRAG ? 0
541 | : swipeIfNecessary(prevSelected);
542 | releaseVelocityTracker();
543 | // find where we should animate to
544 | final float targetTranslateX, targetTranslateY;
545 | int animationType;
546 | switch (swipeDir) {
547 | case LEFT:
548 | case RIGHT:
549 | case START:
550 | case END:
551 | targetTranslateY = 0;
552 | targetTranslateX = Math.signum(mDx) * mRecyclerView.getWidth();
553 | break;
554 | case UP:
555 | case DOWN:
556 | targetTranslateX = 0;
557 | targetTranslateY = Math.signum(mDy) * mRecyclerView.getHeight();
558 | break;
559 | default:
560 | targetTranslateX = 0;
561 | targetTranslateY = 0;
562 | }
563 | if (prevActionState == ACTION_STATE_DRAG) {
564 | animationType = ANIMATION_TYPE_DRAG;
565 | } else if (swipeDir > 0) {
566 | animationType = ANIMATION_TYPE_SWIPE_SUCCESS;
567 | } else {
568 | animationType = ANIMATION_TYPE_SWIPE_CANCEL;
569 | }
570 | getSelectedDxDy(mTmpPosition);
571 | final float currentTranslateX = mTmpPosition[0];
572 | final float currentTranslateY = mTmpPosition[1];
573 | final RecoverAnimation rv = new RecoverAnimation(prevSelected, animationType,
574 | prevActionState, currentTranslateX, currentTranslateY,
575 | targetTranslateX, targetTranslateY) {
576 | @Override
577 | public void onAnimationEnd(Animator animation) {
578 | super.onAnimationEnd(animation);
579 | if (this.mOverridden) {
580 | return;
581 | }
582 | if (swipeDir <= 0) {
583 | // this is a drag or failed swipe. recover immediately
584 | mCallback.clearView(mRecyclerView, prevSelected);
585 | // full cleanup will happen on onDrawOver
586 | } else {
587 | // wait until remove animation is complete.
588 | mPendingCleanup.add(prevSelected.itemView);
589 | mIsPendingCleanup = true;
590 | if (swipeDir > 0) {
591 | // Animation might be ended by other animators during a layout.
592 | // We defer callback to avoid editing adapter during a layout.
593 | postDispatchSwipe(this, swipeDir);
594 | }
595 | }
596 | // removed from the list after it is drawn for the last time
597 | if (mOverdrawChild == prevSelected.itemView) {
598 | removeChildDrawingOrderCallbackIfNecessary(prevSelected.itemView);
599 | }
600 | }
601 | };
602 | final long duration = mCallback.getAnimationDuration(mRecyclerView, animationType,
603 | targetTranslateX - currentTranslateX, targetTranslateY - currentTranslateY);
604 | rv.setDuration(duration);
605 | mRecoverAnimations.add(rv);
606 | rv.start();
607 | preventLayout = true;
608 | } else {
609 | removeChildDrawingOrderCallbackIfNecessary(prevSelected.itemView);
610 | mCallback.clearView(mRecyclerView, prevSelected);
611 | }
612 | mSelected = null;
613 | }
614 | if (selected != null) {
615 | mSelectedFlags =
616 | (mCallback.getAbsoluteMovementFlags(mRecyclerView, selected) & actionStateMask)
617 | >> (mActionState * DIRECTION_FLAG_COUNT);
618 | mSelectedStartX = selected.itemView.getLeft();
619 | mSelectedStartY = selected.itemView.getTop();
620 | mSelected = selected;
621 |
622 | if (actionState == ACTION_STATE_DRAG) {
623 | //这个是给出触感反馈,震动的效果!
624 | mSelected.itemView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
625 | }
626 | }
627 | final ViewParent rvParent = mRecyclerView.getParent();
628 | if (rvParent != null) {
629 | rvParent.requestDisallowInterceptTouchEvent(mSelected != null);
630 | }
631 | if (!preventLayout) {
632 | mRecyclerView.getLayoutManager().requestSimpleAnimationsInNextLayout();
633 | }
634 | mCallback.onSelectedChanged(mSelected, mActionState);
635 | mRecyclerView.invalidate();
636 | }
637 |
638 | void postDispatchSwipe(final RecoverAnimation anim, final int swipeDir) {
639 | // wait until animations are complete.
640 | mRecyclerView.post(new Runnable() {
641 | @Override
642 | public void run() {
643 | if (mRecyclerView != null && mRecyclerView.isAttachedToWindow()
644 | && !anim.mOverridden
645 | && anim.mViewHolder.getAdapterPosition() != RecyclerView.NO_POSITION) {
646 | final RecyclerView.ItemAnimator animator = mRecyclerView.getItemAnimator();
647 | // if animator is running or we have other active recover animations, we try
648 | // not to call onSwiped because DefaultItemAnimator is not good at merging
649 | // animations. Instead, we wait and batch.
650 | if ((animator == null || !animator.isRunning(null))
651 | && !hasRunningRecoverAnim()) {
652 | mCallback.onSwiped(anim.mViewHolder, swipeDir);
653 | } else {
654 | mRecyclerView.post(this);
655 | }
656 | }
657 | }
658 | });
659 | }
660 |
661 | boolean hasRunningRecoverAnim() {
662 | final int size = mRecoverAnimations.size();
663 | for (int i = 0; i < size; i++) {
664 | if (!mRecoverAnimations.get(i).mEnded) {
665 | return true;
666 | }
667 | }
668 | return false;
669 | }
670 |
671 | /**
672 | * If user drags the view to the edge, trigger a scroll if necessary.
673 | */
674 | boolean scrollIfNecessary() {
675 | if (mSelected == null) {
676 | mDragScrollStartTimeInMs = Long.MIN_VALUE;
677 | return false;
678 | }
679 | final long now = System.currentTimeMillis();
680 | final long scrollDuration = mDragScrollStartTimeInMs
681 | == Long.MIN_VALUE ? 0 : now - mDragScrollStartTimeInMs;
682 | RecyclerView.LayoutManager lm = mRecyclerView.getLayoutManager();
683 | if (mTmpRect == null) {
684 | mTmpRect = new Rect();
685 | }
686 | int scrollX = 0;
687 | int scrollY = 0;
688 | lm.calculateItemDecorationsForChild(mSelected.itemView, mTmpRect);
689 | if (lm.canScrollHorizontally()) {
690 | int curX = (int) (mSelectedStartX + mDx);
691 | final int leftDiff = curX - mTmpRect.left - mRecyclerView.getPaddingLeft();
692 | if (mDx < 0 && leftDiff < 0) {
693 | scrollX = leftDiff;
694 | } else if (mDx > 0) {
695 | final int rightDiff =
696 | curX + mSelected.itemView.getWidth() + mTmpRect.right
697 | - (mRecyclerView.getWidth() - mRecyclerView.getPaddingRight());
698 | if (rightDiff > 0) {
699 | scrollX = rightDiff;
700 | }
701 | }
702 | }
703 | if (lm.canScrollVertically()) {
704 | int curY = (int) (mSelectedStartY + mDy);
705 | final int topDiff = curY - mTmpRect.top - mRecyclerView.getPaddingTop();
706 | if (mDy < 0 && topDiff < 0) {
707 | scrollY = topDiff;
708 | } else if (mDy > 0) {
709 | final int bottomDiff = curY + mSelected.itemView.getHeight() + mTmpRect.bottom
710 | - (mRecyclerView.getHeight() - mRecyclerView.getPaddingBottom());
711 | if (bottomDiff > 0) {
712 | scrollY = bottomDiff;
713 | }
714 | }
715 | }
716 | if (scrollX != 0) {
717 | scrollX = mCallback.interpolateOutOfBoundsScroll(mRecyclerView,
718 | mSelected.itemView.getWidth(), scrollX,
719 | mRecyclerView.getWidth(), scrollDuration);
720 | }
721 | if (scrollY != 0) {
722 | scrollY = mCallback.interpolateOutOfBoundsScroll(mRecyclerView,
723 | mSelected.itemView.getHeight(), scrollY,
724 | mRecyclerView.getHeight(), scrollDuration);
725 | }
726 | if (scrollX != 0 || scrollY != 0) {
727 | if (mDragScrollStartTimeInMs == Long.MIN_VALUE) {
728 | mDragScrollStartTimeInMs = now;
729 | }
730 | mRecyclerView.scrollBy(scrollX, scrollY);
731 | return true;
732 | }
733 | mDragScrollStartTimeInMs = Long.MIN_VALUE;
734 | return false;
735 | }
736 |
737 | private List
997 | * For this method to work:
998 | *
1008 | * For example, if you would like to let your user to be able to drag an Item by touching one
1009 | * of its descendants, you may implement it as follows:
1010 | *
1021 | *
1022 | * @param viewHolder The ViewHolder to start dragging. It must be a direct child of
1023 | * RecyclerView.
1024 | * @see android.support.v7.widget.helper.ItemTouchHelper.Callback#isItemViewSwipeEnabled()
1025 | */
1026 | public void startDrag(RecyclerView.ViewHolder viewHolder) {
1027 | if (!mCallback.hasDragFlag(mRecyclerView, viewHolder)) {
1028 | Log.e(TAG, "Start drag has been called but dragging is not enabled");
1029 | return;
1030 | }
1031 | if (viewHolder.itemView.getParent() != mRecyclerView) {
1032 | Log.e(TAG, "Start drag has been called with a view holder which is not a child of "
1033 | + "the RecyclerView which is controlled by this ItemTouchHelper.");
1034 | return;
1035 | }
1036 | obtainVelocityTracker();
1037 | mDx = mDy = 0f;
1038 | select(viewHolder, ACTION_STATE_DRAG);
1039 | }
1040 |
1041 | /**
1042 | * Starts swiping the provided ViewHolder. By default, ItemTouchHelper starts swiping a View
1043 | * when user swipes their finger (or mouse pointer) over the View. You can disable this
1044 | * behavior
1045 | * by overriding {@link android.support.v7.widget.helper.ItemTouchHelper.Callback}
1046 | *
1047 | * For this method to work:
1048 | *
1057 | * For example, if you would like to let your user to be able to swipe an Item by touching one
1058 | * of its descendants, you may implement it as follows:
1059 | *
1267 | * A LayoutManager should implement this interface to get ready for the upcoming move
1268 | * operation.
1269 | *
1270 | * For example, LinearLayoutManager sets up a "scrollToPositionWithOffset" calls so that
1271 | * the View under drag will be used as an anchor View while calculating the next layout,
1272 | * making layout stay consistent.
1273 | *
1274 | * @param view The View which is being dragged. It is very likely that user is still
1275 | * dragging this View so there might be other
1276 | * {@link #prepareForDrop(View, View, int, int)} after this one.
1277 | * @param target The target view which is being dropped on.
1278 | * @param x The
1291 | * To control which actions user can take on each view, you should override
1292 | * {@link #getMovementFlags(RecyclerView, RecyclerView.ViewHolder)} and return appropriate set
1293 | * of direction flags. ({@link #LEFT}, {@link #RIGHT}, {@link #START}, {@link #END},
1294 | * {@link #UP}, {@link #DOWN}). You can use
1295 | * {@link #makeMovementFlags(int, int)} to easily construct it. Alternatively, you can use
1296 | * {@link android.support.v7.widget.helper.ItemTouchHelper.SimpleCallback}.
1297 | *
1298 | * If user drags an item, ItemTouchHelper will call
1299 | * {@link android.support.v7.widget.helper.ItemTouchHelper.Callback#onMove(RecyclerView, RecyclerView.ViewHolder, RecyclerView.ViewHolder)
1300 | * onMove(recyclerView, dragged, target)}.
1301 | * Upon receiving this callback, you should move the item from the old position
1302 | * ({@code dragged.getAdapterPosition()}) to new position ({@code target.getAdapterPosition()})
1303 | * in your adapter and also call {@link RecyclerView.Adapter#notifyItemMoved(int, int)}.
1304 | * To control where a View can be dropped, you can override
1305 | * {@link #canDropOver(RecyclerView, RecyclerView.ViewHolder, RecyclerView.ViewHolder)}. When a
1306 | * dragging View overlaps multiple other views, Callback chooses the closest View with which
1307 | * dragged View might have changed positions. Although this approach works for many use cases,
1308 | * if you have a custom LayoutManager, you can override
1309 | * {@link #chooseDropTarget(RecyclerView.ViewHolder, List, int, int)} to select a
1310 | * custom drop target.
1311 | *
1312 | * When a View is swiped, ItemTouchHelper animates it until it goes out of bounds, then calls
1313 | * {@link #onSwiped(RecyclerView.ViewHolder, int)}. At this point, you should update your
1314 | * adapter (e.g. remove the item) and call related Adapter#notify event.
1315 | */
1316 | @SuppressWarnings("UnusedParameters")
1317 | public abstract static class Callback {
1318 |
1319 | public static final int DEFAULT_DRAG_ANIMATION_DURATION = 200;
1320 |
1321 | public static final int DEFAULT_SWIPE_ANIMATION_DURATION = 250;
1322 |
1323 | static final int RELATIVE_DIR_FLAGS = START | END
1324 | | ((START | END) << DIRECTION_FLAG_COUNT)
1325 | | ((START | END) << (2 * DIRECTION_FLAG_COUNT));
1326 |
1327 | private static final ItemTouchUIUtil sUICallback;
1328 |
1329 | private static final int ABS_HORIZONTAL_DIR_FLAGS = LEFT | RIGHT
1330 | | ((LEFT | RIGHT) << DIRECTION_FLAG_COUNT)
1331 | | ((LEFT | RIGHT) << (2 * DIRECTION_FLAG_COUNT));
1332 |
1333 | private static final Interpolator sDragScrollInterpolator = new Interpolator() {
1334 | @Override
1335 | public float getInterpolation(float t) {
1336 | return t * t * t * t * t;
1337 | }
1338 | };
1339 |
1340 | private static final Interpolator sDragViewScrollCapInterpolator = new Interpolator() {
1341 | @Override
1342 | public float getInterpolation(float t) {
1343 | t -= 1.0f;
1344 | return t * t * t * t * t + 1.0f;
1345 | }
1346 | };
1347 |
1348 | /**
1349 | * Drag scroll speed keeps accelerating until this many milliseconds before being capped.
1350 | */
1351 | private static final long DRAG_SCROLL_ACCELERATION_LIMIT_TIME_MS = 2000;
1352 |
1353 | private int mCachedMaxScrollSpeed = -1;
1354 |
1355 | static {
1356 | if (Build.VERSION.SDK_INT >= 21) {
1357 | sUICallback = new ItemTouchUIUtilImpl.Api21Impl();
1358 | } else {
1359 | sUICallback = new ItemTouchUIUtilImpl.BaseImpl();
1360 | }
1361 | }
1362 |
1363 | /**
1364 | * Returns the {@link ItemTouchUIUtil} that is used by the {@link android.support.v7.widget.helper.ItemTouchHelper.Callback} class for
1365 | * visual
1366 | * changes on Views in response to user interactions. {@link ItemTouchUIUtil} has different
1367 | * implementations for different platform versions.
1368 | *
1369 | * By default, {@link android.support.v7.widget.helper.ItemTouchHelper.Callback} applies these changes on
1370 | * {@link RecyclerView.ViewHolder#itemView}.
1371 | *
1372 | * For example, if you have a use case where you only want the text to move when user
1373 | * swipes over the view, you can do the following:
1374 | *
1440 | * For instance, if you want to let your items be drag & dropped vertically and swiped
1441 | * left to be dismissed, you can call this method with:
1442 | *
1472 | * Instead of composing this flag manually, you can use {@link #makeMovementFlags(int,
1473 | * int)}
1474 | * or {@link #makeFlag(int, int)}.
1475 | *
1476 | * This flag is composed of 3 sets of 8 bits, where first 8 bits are for IDLE state, next
1477 | * 8 bits are for SWIPE state and third 8 bits are for DRAG state.
1478 | * Each 8 bit sections can be constructed by simply OR'ing direction flags defined in
1479 | * {@link android.support.v7.widget.helper.ItemTouchHelper}.
1480 | *
1481 | * For example, if you want it to allow swiping LEFT and RIGHT but only allow starting to
1482 | * swipe by swiping RIGHT, you can return:
1483 | *
1546 | * This method is used when selecting drop target for the dragged View. After Views are
1547 | * eliminated either via bounds check or via this method, resulting set of views will be
1548 | * passed to {@link #chooseDropTarget(RecyclerView.ViewHolder, List, int, int)}.
1549 | *
1550 | * Default implementation returns true.
1551 | *
1552 | * @param recyclerView The RecyclerView to which ItemTouchHelper is attached to.
1553 | * @param current The ViewHolder that user is dragging.
1554 | * @param target The ViewHolder which is below the dragged ViewHolder.
1555 | * @return True if the dragged ViewHolder can be replaced with the target ViewHolder, false
1556 | * otherwise.
1557 | */
1558 | public boolean canDropOver(RecyclerView recyclerView, RecyclerView.ViewHolder current,
1559 | RecyclerView.ViewHolder target) {
1560 | return true;
1561 | }
1562 |
1563 | /**
1564 | * Called when ItemTouchHelper wants to move the dragged item from its old position to
1565 | * the new position.
1566 | *
1567 | * If this method returns true, ItemTouchHelper assumes {@code viewHolder} has been moved
1568 | * to the adapter position of {@code target} ViewHolder
1569 | * ({@link RecyclerView.ViewHolder#getAdapterPosition()
1570 | * ViewHolder#getAdapterPosition()}).
1571 | *
1572 | * If you don't support drag & drop, this method will never be called.
1573 | *
1574 | * @param recyclerView The RecyclerView to which ItemTouchHelper is attached to.
1575 | * @param viewHolder The ViewHolder which is being dragged by the user.
1576 | * @param target The ViewHolder over which the currently active item is being
1577 | * dragged.
1578 | * @return True if the {@code viewHolder} has been moved to the adapter position of
1579 | * {@code target}.
1580 | * @see #onMoved(RecyclerView, RecyclerView.ViewHolder, int, RecyclerView.ViewHolder, int, int, int)
1581 | */
1582 | public abstract boolean onMove(RecyclerView recyclerView,
1583 | RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target);
1584 |
1585 | /**
1586 | * Returns whether ItemTouchHelper should start a drag and drop operation if an item is
1587 | * long pressed.
1588 | *
1589 | * Default value returns true but you may want to disable this if you want to start
1590 | * dragging on a custom view touch using {@link #startDrag(RecyclerView.ViewHolder)}.
1591 | *
1592 | * @return True if ItemTouchHelper should start dragging an item when it is long pressed,
1593 | * false otherwise. Default value is
1604 | * Default value returns true but you may want to disable this if you want to start
1605 | * swiping on a custom view touch using {@link #startSwipe(RecyclerView.ViewHolder)}.
1606 | *
1607 | * @return True if ItemTouchHelper should start swiping an item when user swipes a pointer
1608 | * over the View, false otherwise. Default value is
1630 | * Default value is .5f, which means, to swipe a View, user must move the View at least
1631 | * half of RecyclerView's width or height, depending on the swipe direction.
1632 | *
1633 | * @param viewHolder The ViewHolder that is being dragged.
1634 | * @return A float value that denotes the fraction of the View size. Default value
1635 | * is .5f .
1636 | */
1637 | public float getSwipeThreshold(RecyclerView.ViewHolder viewHolder) {
1638 | return .5f;
1639 | }
1640 |
1641 | /**
1642 | * Returns the fraction that the user should move the View to be considered as it is
1643 | * dragged. After a view is moved this amount, ItemTouchHelper starts checking for Views
1644 | * below it for a possible drop.
1645 | *
1646 | * @param viewHolder The ViewHolder that is being dragged.
1647 | * @return A float value that denotes the fraction of the View size. Default value is
1648 | * .5f .
1649 | */
1650 | public float getMoveThreshold(RecyclerView.ViewHolder viewHolder) {
1651 | return .5f;
1652 | }
1653 |
1654 | /**
1655 | * Defines the minimum velocity which will be considered as a swipe action by the user.
1656 | *
1657 | * You can increase this value to make it harder to swipe or decrease it to make it easier.
1658 | * Keep in mind that ItemTouchHelper also checks the perpendicular velocity and makes sure
1659 | * current direction velocity is larger then the perpendicular one. Otherwise, user's
1660 | * movement is ambiguous. You can change the threshold by overriding
1661 | * {@link #getSwipeVelocityThreshold(float)}.
1662 | *
1663 | * The velocity is calculated in pixels per second.
1664 | *
1665 | * The default framework value is passed as a parameter so that you can modify it with a
1666 | * multiplier.
1667 | *
1668 | * @param defaultValue The default value (in pixels per second) used by the
1669 | * ItemTouchHelper.
1670 | * @return The minimum swipe velocity. The default implementation returns the
1671 | *
1682 | * To consider a movement as swipe, ItemTouchHelper requires it to be larger than the
1683 | * perpendicular movement. If both directions reach to the max threshold, none of them will
1684 | * be considered as a swipe because it is usually an indication that user rather tried to
1685 | * scroll then swipe.
1686 | *
1687 | * The velocity is calculated in pixels per second.
1688 | *
1689 | * You can customize this behavior by changing this method. If you increase the value, it
1690 | * will be easier for the user to swipe diagonally and if you decrease the value, user will
1691 | * need to make a rather straight finger movement to trigger a swipe.
1692 | *
1693 | * @param defaultValue The default value(in pixels per second) used by the ItemTouchHelper.
1694 | * @return The velocity cap for pointer movements. The default implementation returns the
1695 | *
1706 | * Default implementation filters the View with which dragged item have changed position
1707 | * in the drag direction. For instance, if the view is dragged UP, it compares the
1708 | *
1711 | * Among these Views which pass the test, the one closest to the dragged view is chosen.
1712 | *
1713 | * This method is called on the main thread every time user moves the View. If you want to
1714 | * override it, make sure it does not do any expensive operations.
1715 | *
1716 | * @param selected The ViewHolder being dragged by the user.
1717 | * @param dropTargets The list of ViewHolder that are under the dragged View and
1718 | * candidate as a drop.
1719 | * @param curX The updated left value of the dragged View after drag translations
1720 | * are applied. This value does not include margins added by
1721 | * {@link RecyclerView.ItemDecoration}s.
1722 | * @param curY The updated top value of the dragged View after drag translations
1723 | * are applied. This value does not include margins added by
1724 | * {@link RecyclerView.ItemDecoration}s.
1725 | * @return A ViewHolder to whose position the dragged ViewHolder should be
1726 | * moved to.
1727 | */
1728 | public RecyclerView.ViewHolder chooseDropTarget(RecyclerView.ViewHolder selected,
1729 | List
1787 | * If you are returning relative directions ({@link #START} , {@link #END}) from the
1788 | * {@link #getMovementFlags(RecyclerView, RecyclerView.ViewHolder)} method, this method
1789 | * will also use relative directions. Otherwise, it will use absolute directions.
1790 | *
1791 | * If you don't support swiping, this method will never be called.
1792 | *
1793 | * ItemTouchHelper will keep a reference to the View until it is detached from
1794 | * RecyclerView.
1795 | * As soon as it is detached, ItemTouchHelper will call
1796 | * {@link #clearView(RecyclerView, RecyclerView.ViewHolder)}.
1797 | *
1798 | * @param viewHolder The ViewHolder which has been swiped by the user.
1799 | * @param direction The direction to which the ViewHolder is swiped. It is one of
1800 | * {@link #UP}, {@link #DOWN},
1801 | * {@link #LEFT} or {@link #RIGHT}. If your
1802 | * {@link #getMovementFlags(RecyclerView, RecyclerView.ViewHolder)}
1803 | * method
1804 | * returned relative flags instead of {@link #LEFT} / {@link #RIGHT};
1805 | * `direction` will be relative as well. ({@link #START} or {@link
1806 | * #END}).
1807 | */
1808 | public abstract void onSwiped(RecyclerView.ViewHolder viewHolder, int direction);
1809 |
1810 | /**
1811 | * Called when the ViewHolder swiped or dragged by the ItemTouchHelper is changed.
1812 | *
1839 | * ItemTouchHelper does not create an extra Bitmap or View while dragging, instead, it
1840 | * modifies the existing View. Because of this reason, it is important that the View is
1841 | * still part of the layout after it is moved. This may not work as intended when swapped
1842 | * Views are close to RecyclerView bounds or there are gaps between them (e.g. other Views
1843 | * which were not eligible for dropping over).
1844 | *
1845 | * This method is responsible to give necessary hint to the LayoutManager so that it will
1846 | * keep the View in visible area. For example, for LinearLayoutManager, this is as simple
1847 | * as calling {@link LinearLayoutManager#scrollToPositionWithOffset(int, int)}.
1848 | *
1849 | * Default implementation calls {@link RecyclerView#scrollToPosition(int)} if the View's
1850 | * new position is likely to be out of bounds.
1851 | *
1852 | * It is important to ensure the ViewHolder will stay visible as otherwise, it might be
1853 | * removed by the LayoutManager if the move causes the View to go out of bounds. In that
1854 | * case, drag will end prematurely.
1855 | *
1856 | * @param recyclerView The RecyclerView controlled by the ItemTouchHelper.
1857 | * @param viewHolder The ViewHolder under user's control.
1858 | * @param fromPos The previous adapter position of the dragged item (before it was
1859 | * moved).
1860 | * @param target The ViewHolder on which the currently active item has been dropped.
1861 | * @param toPos The new adapter position of the dragged item.
1862 | * @param x The updated left value of the dragged View after drag translations
1863 | * are applied. This value does not include margins added by
1864 | * {@link RecyclerView.ItemDecoration}s.
1865 | * @param y The updated top value of the dragged View after drag translations
1866 | * are applied. This value does not include margins added by
1867 | * {@link RecyclerView.ItemDecoration}s.
1868 | */
1869 | public void onMoved(final RecyclerView recyclerView,
1870 | final RecyclerView.ViewHolder viewHolder, int fromPos, final RecyclerView.ViewHolder target, int toPos, int x,
1871 | int y) {
1872 | final RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
1873 | if (layoutManager instanceof android.support.v7.widget.helper.ItemTouchHelper.ViewDropHandler) {
1874 | ((android.support.v7.widget.helper.ItemTouchHelper.ViewDropHandler) layoutManager).prepareForDrop(viewHolder.itemView,
1875 | target.itemView, x, y);
1876 | return;
1877 | }
1878 |
1879 | // if layout manager cannot handle it, do some guesswork
1880 | if (layoutManager.canScrollHorizontally()) {
1881 | final int minLeft = layoutManager.getDecoratedLeft(target.itemView);
1882 | if (minLeft <= recyclerView.getPaddingLeft()) {
1883 | recyclerView.scrollToPosition(toPos);
1884 | }
1885 | final int maxRight = layoutManager.getDecoratedRight(target.itemView);
1886 | if (maxRight >= recyclerView.getWidth() - recyclerView.getPaddingRight()) {
1887 | recyclerView.scrollToPosition(toPos);
1888 | }
1889 | }
1890 |
1891 | if (layoutManager.canScrollVertically()) {
1892 | final int minTop = layoutManager.getDecoratedTop(target.itemView);
1893 | if (minTop <= recyclerView.getPaddingTop()) {
1894 | recyclerView.scrollToPosition(toPos);
1895 | }
1896 | final int maxBottom = layoutManager.getDecoratedBottom(target.itemView);
1897 | if (maxBottom >= recyclerView.getHeight() - recyclerView.getPaddingBottom()) {
1898 | recyclerView.scrollToPosition(toPos);
1899 | }
1900 | }
1901 | }
1902 |
1903 | void onDraw(Canvas c, RecyclerView parent, RecyclerView.ViewHolder selected,
1904 | List
1956 | * This is a good place to clear all changes on the View that was done in
1957 | * {@link #onSelectedChanged(RecyclerView.ViewHolder, int)},
1958 | * {@link #onChildDraw(Canvas, RecyclerView, RecyclerView.ViewHolder, float, float, int,
1959 | * boolean)} or
1960 | * {@link #onChildDrawOver(Canvas, RecyclerView, RecyclerView.ViewHolder, float, float, int, boolean)}.
1961 | *
1962 | * @param recyclerView The RecyclerView which is controlled by the ItemTouchHelper.
1963 | * @param viewHolder The View that was interacted by the user.
1964 | */
1965 | public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
1966 | sUICallback.clearView(viewHolder.itemView);
1967 | }
1968 |
1969 | /**
1970 | * Called by ItemTouchHelper on RecyclerView's onDraw callback.
1971 | *
1972 | * If you would like to customize how your View's respond to user interactions, this is
1973 | * a good place to override.
1974 | *
1975 | * Default implementation translates the child by the given
2006 | * If you would like to customize how your View's respond to user interactions, this is
2007 | * a good place to override.
2008 | *
2009 | * Default implementation translates the child by the given
2041 | * Default implementation uses ItemAnimator's duration values. If
2042 | *
2072 | * You can override this method to decide how much RecyclerView should scroll in response
2073 | * to this action. Default implementation calculates a value based on the amount of View
2074 | * out of bounds and the time it spent there. The longer user keeps the View out of bounds,
2075 | * the faster the list will scroll. Similarly, the larger portion of the View is out of
2076 | * bounds, the faster the RecyclerView will scroll.
2077 | *
2078 | * @param recyclerView The RecyclerView instance to which ItemTouchHelper is
2079 | * attached to.
2080 | * @param viewSize The total size of the View in scroll direction, excluding
2081 | * item decorations.
2082 | * @param viewSizeOutOfBounds The total size of the View that is out of bounds. This value
2083 | * is negative if the View is dragged towards left or top edge.
2084 | * @param totalSize The total size of RecyclerView in the scroll direction.
2085 | * @param msSinceStartScroll The time passed since View is kept out of bounds.
2086 | * @return The amount that RecyclerView should scroll. Keep in mind that this value will
2087 | * be passed to {@link RecyclerView#scrollBy(int, int)} method.
2088 | */
2089 | public int interpolateOutOfBoundsScroll(RecyclerView recyclerView,
2090 | int viewSize, int viewSizeOutOfBounds,
2091 | int totalSize, long msSinceStartScroll) {
2092 | final int maxScroll = getMaxDragScroll(recyclerView);
2093 | final int absOutOfBounds = Math.abs(viewSizeOutOfBounds);
2094 | final int direction = (int) Math.signum(viewSizeOutOfBounds);
2095 | // might be negative if other direction
2096 | float outOfBoundsRatio = Math.min(1f, 1f * absOutOfBounds / viewSize);
2097 | final int cappedScroll = (int) (direction * maxScroll
2098 | * sDragViewScrollCapInterpolator.getInterpolation(outOfBoundsRatio));
2099 | final float timeRatio;
2100 | if (msSinceStartScroll > DRAG_SCROLL_ACCELERATION_LIMIT_TIME_MS) {
2101 | timeRatio = 1f;
2102 | } else {
2103 | timeRatio = (float) msSinceStartScroll / DRAG_SCROLL_ACCELERATION_LIMIT_TIME_MS;
2104 | }
2105 | final int value = (int) (cappedScroll * sDragScrollInterpolator
2106 | .getInterpolation(timeRatio));
2107 | if (value == 0) {
2108 | return viewSizeOutOfBounds > 0 ? 1 : -1;
2109 | }
2110 | return value;
2111 | }
2112 | }
2113 |
2114 | /**
2115 | * A simple wrapper to the default Callback which you can construct with drag and swipe
2116 | * directions and this class will handle the flag callbacks. You should still override onMove
2117 | * or
2118 | * onSwiped depending on your use case.
2119 | *
2120 | *
999 | *
1007 | *
1011 | * viewHolder.dragButton.setOnTouchListener(new View.OnTouchListener() {
1012 | * public boolean onTouch(View v, MotionEvent event) {
1013 | * if (MotionEvent.getActionMasked(event) == MotionEvent.ACTION_DOWN) {
1014 | * mItemTouchHelper.startDrag(viewHolder);
1015 | * }
1016 | * return false;
1017 | * }
1018 | * });
1019 | *
1020 | *
1049 | *
1056 | *
1060 | * viewHolder.dragButton.setOnTouchListener(new View.OnTouchListener() {
1061 | * public boolean onTouch(View v, MotionEvent event) {
1062 | * if (MotionEvent.getActionMasked(event) == MotionEvent.ACTION_DOWN) {
1063 | * mItemTouchHelper.startSwipe(viewHolder);
1064 | * }
1065 | * return false;
1066 | * }
1067 | * });
1068 | *
1069 | *
1070 | * @param viewHolder The ViewHolder to start swiping. It must be a direct child of
1071 | * RecyclerView.
1072 | */
1073 | public void startSwipe(RecyclerView.ViewHolder viewHolder) {
1074 | if (!mCallback.hasSwipeFlag(mRecyclerView, viewHolder)) {
1075 | Log.e(TAG, "Start swipe has been called but swiping is not enabled");
1076 | return;
1077 | }
1078 | if (viewHolder.itemView.getParent() != mRecyclerView) {
1079 | Log.e(TAG, "Start swipe has been called with a view holder which is not a child of "
1080 | + "the RecyclerView controlled by this ItemTouchHelper.");
1081 | return;
1082 | }
1083 | obtainVelocityTracker();
1084 | mDx = mDy = 0f;
1085 | select(viewHolder, ACTION_STATE_SWIPE);
1086 | }
1087 |
1088 | RecoverAnimation findAnimation(MotionEvent event) {
1089 | if (mRecoverAnimations.isEmpty()) {
1090 | return null;
1091 | }
1092 | View target = findChildView(event);
1093 | for (int i = mRecoverAnimations.size() - 1; i >= 0; i--) {
1094 | final RecoverAnimation anim = mRecoverAnimations.get(i);
1095 | if (anim.mViewHolder.itemView == target) {
1096 | return anim;
1097 | }
1098 | }
1099 | return null;
1100 | }
1101 |
1102 | void updateDxDy(MotionEvent ev, int directionFlags, int pointerIndex) {
1103 | final float x = ev.getX(pointerIndex);
1104 | final float y = ev.getY(pointerIndex);
1105 |
1106 | // Calculate the distance moved
1107 | mDx = x - mInitialTouchX;
1108 | mDy = y - mInitialTouchY;
1109 | if ((directionFlags & LEFT) == 0) {
1110 | mDx = Math.max(0, mDx);
1111 | }
1112 | if ((directionFlags & RIGHT) == 0) {
1113 | mDx = Math.min(0, mDx);
1114 | }
1115 | if ((directionFlags & UP) == 0) {
1116 | mDy = Math.max(0, mDy);
1117 | }
1118 | if ((directionFlags & DOWN) == 0) {
1119 | mDy = Math.min(0, mDy);
1120 | }
1121 | }
1122 |
1123 | private int swipeIfNecessary(RecyclerView.ViewHolder viewHolder) {
1124 | if (mActionState == ACTION_STATE_DRAG) {
1125 | return 0;
1126 | }
1127 | final int originalMovementFlags = mCallback.getMovementFlags(mRecyclerView, viewHolder);
1128 | final int absoluteMovementFlags = mCallback.convertToAbsoluteDirection(
1129 | originalMovementFlags,
1130 | ViewCompat.getLayoutDirection(mRecyclerView));
1131 | final int flags = (absoluteMovementFlags
1132 | & ACTION_MODE_SWIPE_MASK) >> (ACTION_STATE_SWIPE * DIRECTION_FLAG_COUNT);
1133 | if (flags == 0) {
1134 | return 0;
1135 | }
1136 | final int originalFlags = (originalMovementFlags
1137 | & ACTION_MODE_SWIPE_MASK) >> (ACTION_STATE_SWIPE * DIRECTION_FLAG_COUNT);
1138 | int swipeDir;
1139 | if (Math.abs(mDx) > Math.abs(mDy)) {
1140 | if ((swipeDir = checkHorizontalSwipe(viewHolder, flags)) > 0) {
1141 | // if swipe dir is not in original flags, it should be the relative direction
1142 | if ((originalFlags & swipeDir) == 0) {
1143 | // convert to relative
1144 | return Callback.convertToRelativeDirection(swipeDir,
1145 | ViewCompat.getLayoutDirection(mRecyclerView));
1146 | }
1147 | return swipeDir;
1148 | }
1149 | if ((swipeDir = checkVerticalSwipe(viewHolder, flags)) > 0) {
1150 | return swipeDir;
1151 | }
1152 | } else {
1153 | if ((swipeDir = checkVerticalSwipe(viewHolder, flags)) > 0) {
1154 | return swipeDir;
1155 | }
1156 | if ((swipeDir = checkHorizontalSwipe(viewHolder, flags)) > 0) {
1157 | // if swipe dir is not in original flags, it should be the relative direction
1158 | if ((originalFlags & swipeDir) == 0) {
1159 | // convert to relative
1160 | return Callback.convertToRelativeDirection(swipeDir,
1161 | ViewCompat.getLayoutDirection(mRecyclerView));
1162 | }
1163 | return swipeDir;
1164 | }
1165 | }
1166 | return 0;
1167 | }
1168 |
1169 | private int checkHorizontalSwipe(RecyclerView.ViewHolder viewHolder, int flags) {
1170 | if ((flags & (LEFT | RIGHT)) != 0) {
1171 | final int dirFlag = mDx > 0 ? RIGHT : LEFT;
1172 | if (mVelocityTracker != null && mActivePointerId > -1) {
1173 | mVelocityTracker.computeCurrentVelocity(PIXELS_PER_SECOND,
1174 | mCallback.getSwipeVelocityThreshold(mMaxSwipeVelocity));
1175 | final float xVelocity = mVelocityTracker.getXVelocity(mActivePointerId);
1176 | final float yVelocity = mVelocityTracker.getYVelocity(mActivePointerId);
1177 | final int velDirFlag = xVelocity > 0f ? RIGHT : LEFT;
1178 | final float absXVelocity = Math.abs(xVelocity);
1179 | if ((velDirFlag & flags) != 0 && dirFlag == velDirFlag
1180 | && absXVelocity >= mCallback.getSwipeEscapeVelocity(mSwipeEscapeVelocity)
1181 | && absXVelocity > Math.abs(yVelocity)) {
1182 | return velDirFlag;
1183 | }
1184 | }
1185 |
1186 | final float threshold = mRecyclerView.getWidth() * mCallback
1187 | .getSwipeThreshold(viewHolder);
1188 |
1189 | if ((flags & dirFlag) != 0 && Math.abs(mDx) > threshold) {
1190 | return dirFlag;
1191 | }
1192 | }
1193 | return 0;
1194 | }
1195 |
1196 | private int checkVerticalSwipe(RecyclerView.ViewHolder viewHolder, int flags) {
1197 | if ((flags & (UP | DOWN)) != 0) {
1198 | final int dirFlag = mDy > 0 ? DOWN : UP;
1199 | if (mVelocityTracker != null && mActivePointerId > -1) {
1200 | mVelocityTracker.computeCurrentVelocity(PIXELS_PER_SECOND,
1201 | mCallback.getSwipeVelocityThreshold(mMaxSwipeVelocity));
1202 | final float xVelocity = mVelocityTracker.getXVelocity(mActivePointerId);
1203 | final float yVelocity = mVelocityTracker.getYVelocity(mActivePointerId);
1204 | final int velDirFlag = yVelocity > 0f ? DOWN : UP;
1205 | final float absYVelocity = Math.abs(yVelocity);
1206 | if ((velDirFlag & flags) != 0 && velDirFlag == dirFlag
1207 | && absYVelocity >= mCallback.getSwipeEscapeVelocity(mSwipeEscapeVelocity)
1208 | && absYVelocity > Math.abs(xVelocity)) {
1209 | return velDirFlag;
1210 | }
1211 | }
1212 |
1213 | final float threshold = mRecyclerView.getHeight() * mCallback
1214 | .getSwipeThreshold(viewHolder);
1215 | if ((flags & dirFlag) != 0 && Math.abs(mDy) > threshold) {
1216 | return dirFlag;
1217 | }
1218 | }
1219 | return 0;
1220 | }
1221 |
1222 | private void addChildDrawingOrderCallback() {
1223 | if (Build.VERSION.SDK_INT >= 21) {
1224 | return; // we use elevation on Lollipop
1225 | }
1226 | if (mChildDrawingOrderCallback == null) {
1227 | mChildDrawingOrderCallback = new RecyclerView.ChildDrawingOrderCallback() {
1228 | @Override
1229 | public int onGetChildDrawingOrder(int childCount, int i) {
1230 | if (mOverdrawChild == null) {
1231 | return i;
1232 | }
1233 | int childPosition = mOverdrawChildPosition;
1234 | if (childPosition == -1) {
1235 | childPosition = mRecyclerView.indexOfChild(mOverdrawChild);
1236 | mOverdrawChildPosition = childPosition;
1237 | }
1238 | if (i == childCount - 1) {
1239 | return childPosition;
1240 | }
1241 | return i < childPosition ? i : i + 1;
1242 | }
1243 | };
1244 | }
1245 | mRecyclerView.setChildDrawingOrderCallback(mChildDrawingOrderCallback);
1246 | }
1247 |
1248 | void removeChildDrawingOrderCallbackIfNecessary(View view) {
1249 | if (view == mOverdrawChild) {
1250 | mOverdrawChild = null;
1251 | // only remove if we've added
1252 | if (mChildDrawingOrderCallback != null) {
1253 | mRecyclerView.setChildDrawingOrderCallback(null);
1254 | }
1255 | }
1256 | }
1257 |
1258 | /**
1259 | * An interface which can be implemented by LayoutManager for better integration with
1260 | * {@link android.support.v7.widget.helper.ItemTouchHelper}.
1261 | */
1262 | public interface ViewDropHandler {
1263 |
1264 | /**
1265 | * Called by the {@link android.support.v7.widget.helper.ItemTouchHelper} after a View is dropped over another View.
1266 | * left
offset of the View that is being dragged. This value
1279 | * includes the movement caused by the user.
1280 | * @param y The top
offset of the View that is being dragged. This value
1281 | * includes the movement caused by the user.
1282 | */
1283 | void prepareForDrop(View view, View target, int x, int y);
1284 | }
1285 |
1286 | /**
1287 | * This class is the contract between ItemTouchHelper and your application. It lets you control
1288 | * which touch behaviors are enabled per each ViewHolder and also receive callbacks when user
1289 | * performs these actions.
1290 | *
1375 | * public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder){
1376 | * getDefaultUIUtil().clearView(((ItemTouchViewHolder) viewHolder).textView);
1377 | * }
1378 | * public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
1379 | * if (viewHolder != null){
1380 | * getDefaultUIUtil().onSelected(((ItemTouchViewHolder) viewHolder).textView);
1381 | * }
1382 | * }
1383 | * public void onChildDraw(Canvas c, RecyclerView recyclerView,
1384 | * RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState,
1385 | * boolean isCurrentlyActive) {
1386 | * getDefaultUIUtil().onDraw(c, recyclerView,
1387 | * ((ItemTouchViewHolder) viewHolder).textView, dX, dY,
1388 | * actionState, isCurrentlyActive);
1389 | * return true;
1390 | * }
1391 | * public void onChildDrawOver(Canvas c, RecyclerView recyclerView,
1392 | * RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState,
1393 | * boolean isCurrentlyActive) {
1394 | * getDefaultUIUtil().onDrawOver(c, recyclerView,
1395 | * ((ItemTouchViewHolder) viewHolder).textView, dX, dY,
1396 | * actionState, isCurrentlyActive);
1397 | * return true;
1398 | * }
1399 | *
1400 | *
1401 | * @return The {@link ItemTouchUIUtil} instance that is used by the {@link android.support.v7.widget.helper.ItemTouchHelper.Callback}
1402 | */
1403 | public static ItemTouchUIUtil getDefaultUIUtil() {
1404 | return sUICallback;
1405 | }
1406 |
1407 | /**
1408 | * Replaces a movement direction with its relative version by taking layout direction into
1409 | * account.
1410 | *
1411 | * @param flags The flag value that include any number of movement flags.
1412 | * @param layoutDirection The layout direction of the View. Can be obtained from
1413 | * {@link ViewCompat#getLayoutDirection(View)}.
1414 | * @return Updated flags which uses relative flags ({@link #START}, {@link #END}) instead
1415 | * of {@link #LEFT}, {@link #RIGHT}.
1416 | * @see #convertToAbsoluteDirection(int, int)
1417 | */
1418 | public static int convertToRelativeDirection(int flags, int layoutDirection) {
1419 | int masked = flags & ABS_HORIZONTAL_DIR_FLAGS;
1420 | if (masked == 0) {
1421 | return flags; // does not have any abs flags, good.
1422 | }
1423 | flags &= ~masked; //remove left / right.
1424 | if (layoutDirection == ViewCompat.LAYOUT_DIRECTION_LTR) {
1425 | // no change. just OR with 2 bits shifted mask and return
1426 | flags |= masked << 2; // START is 2 bits after LEFT, END is 2 bits after RIGHT.
1427 | return flags;
1428 | } else {
1429 | // add RIGHT flag as START
1430 | flags |= ((masked << 1) & ~ABS_HORIZONTAL_DIR_FLAGS);
1431 | // first clean RIGHT bit then add LEFT flag as END
1432 | flags |= ((masked << 1) & ABS_HORIZONTAL_DIR_FLAGS) << 2;
1433 | }
1434 | return flags;
1435 | }
1436 |
1437 | /**
1438 | * Convenience method to create movement flags.
1439 | * makeMovementFlags(UP | DOWN, LEFT);
1443 | *
1444 | * @param dragFlags The directions in which the item can be dragged.
1445 | * @param swipeFlags The directions in which the item can be swiped.
1446 | * @return Returns an integer composed of the given drag and swipe flags.
1447 | */
1448 | public static int makeMovementFlags(int dragFlags, int swipeFlags) {
1449 | return makeFlag(ACTION_STATE_IDLE, swipeFlags | dragFlags)
1450 | | makeFlag(ACTION_STATE_SWIPE, swipeFlags)
1451 | | makeFlag(ACTION_STATE_DRAG, dragFlags);
1452 | }
1453 |
1454 | /**
1455 | * Shifts the given direction flags to the offset of the given action state.
1456 | *
1457 | * @param actionState The action state you want to get flags in. Should be one of
1458 | * {@link #ACTION_STATE_IDLE}, {@link #ACTION_STATE_SWIPE} or
1459 | * {@link #ACTION_STATE_DRAG}.
1460 | * @param directions The direction flags. Can be composed from {@link #UP}, {@link #DOWN},
1461 | * {@link #RIGHT}, {@link #LEFT} {@link #START} and {@link #END}.
1462 | * @return And integer that represents the given directions in the provided actionState.
1463 | */
1464 | public static int makeFlag(int actionState, int directions) {
1465 | return directions << (actionState * DIRECTION_FLAG_COUNT);
1466 | }
1467 |
1468 | /**
1469 | * Should return a composite flag which defines the enabled move directions in each state
1470 | * (idle, swiping, dragging).
1471 | *
1484 | * makeFlag(ACTION_STATE_IDLE, RIGHT) | makeFlag(ACTION_STATE_SWIPE, LEFT | RIGHT);
1485 | *
1486 | * This means, allow right movement while IDLE and allow right and left movement while
1487 | * swiping.
1488 | *
1489 | * @param recyclerView The RecyclerView to which ItemTouchHelper is attached.
1490 | * @param viewHolder The ViewHolder for which the movement information is necessary.
1491 | * @return flags specifying which movements are allowed on this ViewHolder.
1492 | * @see #makeMovementFlags(int, int)
1493 | * @see #makeFlag(int, int)
1494 | */
1495 | public abstract int getMovementFlags(RecyclerView recyclerView,
1496 | RecyclerView.ViewHolder viewHolder);
1497 |
1498 | /**
1499 | * Converts a given set of flags to absolution direction which means {@link #START} and
1500 | * {@link #END} are replaced with {@link #LEFT} and {@link #RIGHT} depending on the layout
1501 | * direction.
1502 | *
1503 | * @param flags The flag value that include any number of movement flags.
1504 | * @param layoutDirection The layout direction of the RecyclerView.
1505 | * @return Updated flags which includes only absolute direction values.
1506 | */
1507 | public int convertToAbsoluteDirection(int flags, int layoutDirection) {
1508 | int masked = flags & RELATIVE_DIR_FLAGS;
1509 | if (masked == 0) {
1510 | return flags; // does not have any relative flags, good.
1511 | }
1512 | flags &= ~masked; //remove start / end
1513 | if (layoutDirection == ViewCompat.LAYOUT_DIRECTION_LTR) {
1514 | // no change. just OR with 2 bits shifted mask and return
1515 | flags |= masked >> 2; // START is 2 bits after LEFT, END is 2 bits after RIGHT.
1516 | return flags;
1517 | } else {
1518 | // add START flag as RIGHT
1519 | flags |= ((masked >> 1) & ~RELATIVE_DIR_FLAGS);
1520 | // first clean start bit then add END flag as LEFT
1521 | flags |= ((masked >> 1) & RELATIVE_DIR_FLAGS) >> 2;
1522 | }
1523 | return flags;
1524 | }
1525 |
1526 | final int getAbsoluteMovementFlags(RecyclerView recyclerView,
1527 | RecyclerView.ViewHolder viewHolder) {
1528 | final int flags = getMovementFlags(recyclerView, viewHolder);
1529 | return convertToAbsoluteDirection(flags, ViewCompat.getLayoutDirection(recyclerView));
1530 | }
1531 |
1532 | boolean hasDragFlag(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
1533 | final int flags = getAbsoluteMovementFlags(recyclerView, viewHolder);
1534 | return (flags & ACTION_MODE_DRAG_MASK) != 0;
1535 | }
1536 |
1537 | boolean hasSwipeFlag(RecyclerView recyclerView,
1538 | RecyclerView.ViewHolder viewHolder) {
1539 | final int flags = getAbsoluteMovementFlags(recyclerView, viewHolder);
1540 | return (flags & ACTION_MODE_SWIPE_MASK) != 0;
1541 | }
1542 |
1543 | /**
1544 | * Return true if the current ViewHolder can be dropped over the the target ViewHolder.
1545 | * true
.
1594 | * @see #startDrag(RecyclerView.ViewHolder)
1595 | */
1596 | public boolean isLongPressDragEnabled() {
1597 | return true;
1598 | }
1599 |
1600 | /**
1601 | * Returns whether ItemTouchHelper should start a swipe operation if a pointer is swiped
1602 | * over the View.
1603 | * true
.
1609 | * @see #startSwipe(RecyclerView.ViewHolder)
1610 | */
1611 | public boolean isItemViewSwipeEnabled() {
1612 | return true;
1613 | }
1614 |
1615 | /**
1616 | * When finding views under a dragged view, by default, ItemTouchHelper searches for views
1617 | * that overlap with the dragged View. By overriding this method, you can extend or shrink
1618 | * the search box.
1619 | *
1620 | * @return The extra margin to be added to the hit box of the dragged View.
1621 | */
1622 | public int getBoundingBoxMargin() {
1623 | return 0;
1624 | }
1625 |
1626 | /**
1627 | * Returns the fraction that the user should move the View to be considered as swiped.
1628 | * The fraction is calculated with respect to RecyclerView's bounds.
1629 | * defaultValue
parameter.
1672 | * @see #getSwipeVelocityThreshold(float)
1673 | * @see #getSwipeThreshold(RecyclerView.ViewHolder)
1674 | */
1675 | public float getSwipeEscapeVelocity(float defaultValue) {
1676 | return defaultValue;
1677 | }
1678 |
1679 | /**
1680 | * Defines the maximum velocity ItemTouchHelper will ever calculate for pointer movements.
1681 | * defaultValue
parameter.
1696 | * @see #getSwipeEscapeVelocity(float)
1697 | */
1698 | public float getSwipeVelocityThreshold(float defaultValue) {
1699 | return defaultValue;
1700 | }
1701 |
1702 | /**
1703 | * Called by ItemTouchHelper to select a drop target from the list of ViewHolders that
1704 | * are under the dragged View.
1705 | * view.getTop()
of the two views before and after drag started. If that value
1709 | * is different, the target view passes the filter.
1710 | * dX
,
1976 | * dY
.
1977 | * ItemTouchHelper also takes care of drawing the child after other children if it is being
1978 | * dragged. This is done using child re-ordering mechanism. On platforms prior to L, this
1979 | * is
1980 | * achieved via {@link ViewGroup#getChildDrawingOrder(int, int)} and on L
1981 | * and after, it changes View's elevation value to be greater than all other children.)
1982 | *
1983 | * @param c The canvas which RecyclerView is drawing its children
1984 | * @param recyclerView The RecyclerView to which ItemTouchHelper is attached to
1985 | * @param viewHolder The ViewHolder which is being interacted by the User or it was
1986 | * interacted and simply animating to its original position
1987 | * @param dX The amount of horizontal displacement caused by user's action
1988 | * @param dY The amount of vertical displacement caused by user's action
1989 | * @param actionState The type of interaction on the View. Is either {@link
1990 | * #ACTION_STATE_DRAG} or {@link #ACTION_STATE_SWIPE}.
1991 | * @param isCurrentlyActive True if this view is currently being controlled by the user or
1992 | * false it is simply animating back to its original state.
1993 | * @see #onChildDrawOver(Canvas, RecyclerView, RecyclerView.ViewHolder, float, float, int,
1994 | * boolean)
1995 | */
1996 | public void onChildDraw(Canvas c, RecyclerView recyclerView,
1997 | RecyclerView.ViewHolder viewHolder,
1998 | float dX, float dY, int actionState, boolean isCurrentlyActive) {
1999 | sUICallback.onDraw(c, recyclerView, viewHolder.itemView, dX, dY, actionState,
2000 | isCurrentlyActive);
2001 | }
2002 |
2003 | /**
2004 | * Called by ItemTouchHelper on RecyclerView's onDraw callback.
2005 | * dX
,
2010 | * dY
.
2011 | * ItemTouchHelper also takes care of drawing the child after other children if it is being
2012 | * dragged. This is done using child re-ordering mechanism. On platforms prior to L, this
2013 | * is
2014 | * achieved via {@link ViewGroup#getChildDrawingOrder(int, int)} and on L
2015 | * and after, it changes View's elevation value to be greater than all other children.)
2016 | *
2017 | * @param c The canvas which RecyclerView is drawing its children
2018 | * @param recyclerView The RecyclerView to which ItemTouchHelper is attached to
2019 | * @param viewHolder The ViewHolder which is being interacted by the User or it was
2020 | * interacted and simply animating to its original position
2021 | * @param dX The amount of horizontal displacement caused by user's action
2022 | * @param dY The amount of vertical displacement caused by user's action
2023 | * @param actionState The type of interaction on the View. Is either {@link
2024 | * #ACTION_STATE_DRAG} or {@link #ACTION_STATE_SWIPE}.
2025 | * @param isCurrentlyActive True if this view is currently being controlled by the user or
2026 | * false it is simply animating back to its original state.
2027 | * @see #onChildDrawOver(Canvas, RecyclerView, RecyclerView.ViewHolder, float, float, int,
2028 | * boolean)
2029 | */
2030 | public void onChildDrawOver(Canvas c, RecyclerView recyclerView,
2031 | RecyclerView.ViewHolder viewHolder,
2032 | float dX, float dY, int actionState, boolean isCurrentlyActive) {
2033 | sUICallback.onDrawOver(c, recyclerView, viewHolder.itemView, dX, dY, actionState,
2034 | isCurrentlyActive);
2035 | }
2036 |
2037 | /**
2038 | * Called by the ItemTouchHelper when user action finished on a ViewHolder and now the View
2039 | * will be animated to its final position.
2040 | * animationType
is {@link #ANIMATION_TYPE_DRAG}, it returns
2043 | * {@link RecyclerView.ItemAnimator#getMoveDuration()}, otherwise, it returns
2044 | * {@link RecyclerView.ItemAnimator#getRemoveDuration()}. If RecyclerView does not have
2045 | * any {@link RecyclerView.ItemAnimator} attached, this method returns
2046 | * {@code DEFAULT_DRAG_ANIMATION_DURATION} or {@code DEFAULT_SWIPE_ANIMATION_DURATION}
2047 | * depending on the animation type.
2048 | *
2049 | * @param recyclerView The RecyclerView to which the ItemTouchHelper is attached to.
2050 | * @param animationType The type of animation. Is one of {@link #ANIMATION_TYPE_DRAG},
2051 | * {@link #ANIMATION_TYPE_SWIPE_CANCEL} or
2052 | * {@link #ANIMATION_TYPE_SWIPE_SUCCESS}.
2053 | * @param animateDx The horizontal distance that the animation will offset
2054 | * @param animateDy The vertical distance that the animation will offset
2055 | * @return The duration for the animation
2056 | */
2057 | public long getAnimationDuration(RecyclerView recyclerView, int animationType,
2058 | float animateDx, float animateDy) {
2059 | final RecyclerView.ItemAnimator itemAnimator = recyclerView.getItemAnimator();
2060 | if (itemAnimator == null) {
2061 | return animationType == ANIMATION_TYPE_DRAG ? DEFAULT_DRAG_ANIMATION_DURATION
2062 | : DEFAULT_SWIPE_ANIMATION_DURATION;
2063 | } else {
2064 | return animationType == ANIMATION_TYPE_DRAG ? itemAnimator.getMoveDuration()
2065 | : itemAnimator.getRemoveDuration();
2066 | }
2067 | }
2068 |
2069 | /**
2070 | * Called by the ItemTouchHelper when user is dragging a view out of bounds.
2071 | *
2121 | * ItemTouchHelper mIth = new ItemTouchHelper(
2122 | * new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN,
2123 | * ItemTouchHelper.LEFT) {
2124 | * public abstract boolean onMove(RecyclerView recyclerView,
2125 | * ViewHolder viewHolder, ViewHolder target) {
2126 | * final int fromPos = viewHolder.getAdapterPosition();
2127 | * final int toPos = target.getAdapterPosition();
2128 | * // move item in `fromPos` to `toPos` in adapter.
2129 | * return true;// true if moved, false otherwise
2130 | * }
2131 | * public void onSwiped(ViewHolder viewHolder, int direction) {
2132 | * // remove from adapter
2133 | * }
2134 | * });
2135 | *
2136 | */
2137 | public abstract static class SimpleCallback extends android.support.v7.widget.helper.ItemTouchHelper.Callback {
2138 |
2139 | private int mDefaultSwipeDirs;
2140 |
2141 | private int mDefaultDragDirs;
2142 |
2143 | /**
2144 | * Creates a Callback for the given drag and swipe allowance. These values serve as
2145 | * defaults
2146 | * and if you want to customize behavior per ViewHolder, you can override
2147 | * {@link #getSwipeDirs(RecyclerView, RecyclerView.ViewHolder)}
2148 | * and / or {@link #getDragDirs(RecyclerView, RecyclerView.ViewHolder)}.
2149 | *
2150 | * @param dragDirs Binary OR of direction flags in which the Views can be dragged. Must be
2151 | * composed of {@link #LEFT}, {@link #RIGHT}, {@link #START}, {@link
2152 | * #END},
2153 | * {@link #UP} and {@link #DOWN}.
2154 | * @param swipeDirs Binary OR of direction flags in which the Views can be swiped. Must be
2155 | * composed of {@link #LEFT}, {@link #RIGHT}, {@link #START}, {@link
2156 | * #END},
2157 | * {@link #UP} and {@link #DOWN}.
2158 | */
2159 | public SimpleCallback(int dragDirs, int swipeDirs) {
2160 | mDefaultSwipeDirs = swipeDirs;
2161 | mDefaultDragDirs = dragDirs;
2162 | }
2163 |
2164 | /**
2165 | * Updates the default swipe directions. For example, you can use this method to toggle
2166 | * certain directions depending on your use case.
2167 | *
2168 | * @param defaultSwipeDirs Binary OR of directions in which the ViewHolders can be swiped.
2169 | */
2170 | public void setDefaultSwipeDirs(int defaultSwipeDirs) {
2171 | mDefaultSwipeDirs = defaultSwipeDirs;
2172 | }
2173 |
2174 | /**
2175 | * Updates the default drag directions. For example, you can use this method to toggle
2176 | * certain directions depending on your use case.
2177 | *
2178 | * @param defaultDragDirs Binary OR of directions in which the ViewHolders can be dragged.
2179 | */
2180 | public void setDefaultDragDirs(int defaultDragDirs) {
2181 | mDefaultDragDirs = defaultDragDirs;
2182 | }
2183 |
2184 | /**
2185 | * Returns the swipe directions for the provided ViewHolder.
2186 | * Default implementation returns the swipe directions that was set via constructor or
2187 | * {@link #setDefaultSwipeDirs(int)}.
2188 | *
2189 | * @param recyclerView The RecyclerView to which the ItemTouchHelper is attached to.
2190 | * @param viewHolder The RecyclerView for which the swipe direction is queried.
2191 | * @return A binary OR of direction flags.
2192 | */
2193 | public int getSwipeDirs(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
2194 | return mDefaultSwipeDirs;
2195 | }
2196 |
2197 | /**
2198 | * Returns the drag directions for the provided ViewHolder.
2199 | * Default implementation returns the drag directions that was set via constructor or
2200 | * {@link #setDefaultDragDirs(int)}.
2201 | *
2202 | * @param recyclerView The RecyclerView to which the ItemTouchHelper is attached to.
2203 | * @param viewHolder The RecyclerView for which the swipe direction is queried.
2204 | * @return A binary OR of direction flags.
2205 | */
2206 | public int getDragDirs(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
2207 | return mDefaultDragDirs;
2208 | }
2209 |
2210 | @Override
2211 | public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
2212 | return makeMovementFlags(getDragDirs(recyclerView, viewHolder),
2213 | getSwipeDirs(recyclerView, viewHolder));
2214 | }
2215 | }
2216 |
2217 | private class ItemTouchHelperGestureListener extends GestureDetector.SimpleOnGestureListener {
2218 |
2219 | ItemTouchHelperGestureListener() {
2220 | }
2221 |
2222 | @Override
2223 | public boolean onDown(MotionEvent e) {
2224 | return true;
2225 | }
2226 |
2227 | @Override
2228 | public void onLongPress(MotionEvent e) {
2229 | View child = findChildView(e);
2230 | if (child != null) {
2231 | RecyclerView.ViewHolder vh = mRecyclerView.getChildViewHolder(child);
2232 | if (vh != null) {
2233 | if (!mCallback.hasDragFlag(mRecyclerView, vh)) {
2234 | return;
2235 | }
2236 | if (mResponseEventListener != null && !mResponseEventListener
2237 | .isEnableSelectColumn(vh.getAdapterPosition())) {
2238 | return;
2239 | }
2240 | int pointerId = e.getPointerId(0);
2241 | // Long press is deferred.
2242 | // Check w/ active pointer id to avoid selecting after motion
2243 | // event is canceled.
2244 | if (pointerId == mActivePointerId) {
2245 | final int index = e.findPointerIndex(mActivePointerId);
2246 | final float x = e.getX(index);
2247 | final float y = e.getY(index);
2248 | mInitialTouchX = x;
2249 | mInitialTouchY = y;
2250 | mDx = mDy = 0f;
2251 | if (DEBUG) {
2252 | Log.d(TAG,
2253 | "onlong press: x:" + mInitialTouchX + ",y:" + mInitialTouchY);
2254 | }
2255 | if (mCallback.isLongPressDragEnabled()) {
2256 | select(vh, ACTION_STATE_DRAG);
2257 | }
2258 | }
2259 | }
2260 | }
2261 | }
2262 | }
2263 |
2264 | private static class RecoverAnimation implements Animator.AnimatorListener {
2265 |
2266 | final float mStartDx;
2267 |
2268 | final float mStartDy;
2269 |
2270 | final float mTargetX;
2271 |
2272 | final float mTargetY;
2273 |
2274 | final RecyclerView.ViewHolder mViewHolder;
2275 |
2276 | final int mActionState;
2277 |
2278 | private final ValueAnimator mValueAnimator;
2279 |
2280 | final int mAnimationType;
2281 |
2282 | public boolean mIsPendingCleanup;
2283 |
2284 | float mX;
2285 |
2286 | float mY;
2287 |
2288 | // if user starts touching a recovering view, we put it into interaction mode again,
2289 | // instantly.
2290 | boolean mOverridden = false;
2291 |
2292 | boolean mEnded = false;
2293 |
2294 | private float mFraction;
2295 |
2296 | RecoverAnimation(RecyclerView.ViewHolder viewHolder, int animationType,
2297 | int actionState, float startDx, float startDy, float targetX, float targetY) {
2298 | mActionState = actionState;
2299 | mAnimationType = animationType;
2300 | mViewHolder = viewHolder;
2301 | mStartDx = startDx;
2302 | mStartDy = startDy;
2303 | mTargetX = targetX;
2304 | mTargetY = targetY;
2305 | mValueAnimator = ValueAnimator.ofFloat(0f, 1f);
2306 | mValueAnimator.addUpdateListener(
2307 | new ValueAnimator.AnimatorUpdateListener() {
2308 | @Override
2309 | public void onAnimationUpdate(ValueAnimator animation) {
2310 | setFraction(animation.getAnimatedFraction());
2311 | }
2312 | });
2313 | mValueAnimator.setTarget(viewHolder.itemView);
2314 | mValueAnimator.addListener(this);
2315 | setFraction(0f);
2316 | }
2317 |
2318 | public void setDuration(long duration) {
2319 | mValueAnimator.setDuration(duration);
2320 | }
2321 |
2322 | public void start() {
2323 | mViewHolder.setIsRecyclable(false);
2324 | mValueAnimator.start();
2325 | }
2326 |
2327 | public void cancel() {
2328 | mValueAnimator.cancel();
2329 | }
2330 |
2331 | public void setFraction(float fraction) {
2332 | mFraction = fraction;
2333 | }
2334 |
2335 | /**
2336 | * We run updates on onDraw method but use the fraction from animator callback.
2337 | * This way, we can sync translate x/y values w/ the animators to avoid one-off frames.
2338 | */
2339 | public void update() {
2340 | if (mStartDx == mTargetX) {
2341 | mX = mViewHolder.itemView.getTranslationX();
2342 | } else {
2343 | mX = mStartDx + mFraction * (mTargetX - mStartDx);
2344 | }
2345 | if (mStartDy == mTargetY) {
2346 | mY = mViewHolder.itemView.getTranslationY();
2347 | } else {
2348 | mY = mStartDy + mFraction * (mTargetY - mStartDy);
2349 | }
2350 | }
2351 |
2352 | @Override
2353 | public void onAnimationStart(Animator animation) {
2354 |
2355 | }
2356 |
2357 | @Override
2358 | public void onAnimationEnd(Animator animation) {
2359 | if (!mEnded) {
2360 | mViewHolder.setIsRecyclable(true);
2361 | }
2362 | mEnded = true;
2363 | }
2364 |
2365 | @Override
2366 | public void onAnimationCancel(Animator animation) {
2367 | setFraction(1f); //make sure we recover the view's state.
2368 | }
2369 |
2370 | @Override
2371 | public void onAnimationRepeat(Animator animation) {
2372 |
2373 | }
2374 | }
2375 |
2376 | //------------------------------------------------------------------//
2377 |
2378 | /**
2379 | * 判断落点是否落在Header上
2380 | */
2381 | private Rect globalVisible = new Rect();
2382 |
2383 | private boolean landUpAssignView(MotionEvent event) {
2384 | View childView = findChildView(event);
2385 | if (childView == null) return false;
2386 | int x = (int) event.getRawX();
2387 | int y = (int) event.getRawY();
2388 | View header;
2389 | if (mResponseEventListener != null && mResponseEventListener.getResponseViewId() > 0) {
2390 | header = childView.findViewById(mResponseEventListener.getResponseViewId());
2391 | } else {
2392 | if (childView instanceof ViewGroup) {
2393 | header = ((ViewGroup) childView).getChildAt(0);
2394 | } else {
2395 | header = childView;
2396 | }
2397 | }
2398 | if (header == null) {
2399 | return false;
2400 | }
2401 | header.getGlobalVisibleRect(globalVisible);
2402 | return globalVisible.contains(x, y);
2403 | }
2404 |
2405 | private ResponseEventListener mResponseEventListener;
2406 |
2407 | public void setResponseEventListener(ResponseEventListener listener) {
2408 | mResponseEventListener = listener;
2409 | }
2410 |
2411 | public interface ResponseEventListener {
2412 | int getResponseViewId();
2413 |
2414 | boolean isEnableSelectColumn(int column);
2415 | }
2416 | }
2417 |
--------------------------------------------------------------------------------
/app/src/main/java/com/guolei/boardview/ItemTouchUIUtilImpl.java:
--------------------------------------------------------------------------------
1 | package com.guolei.boardview;
2 |
3 |
4 | import android.graphics.Canvas;
5 | import android.support.v4.view.ViewCompat;
6 | import android.support.v7.widget.RecyclerView;
7 | import android.support.v7.widget.helper.ItemTouchUIUtil;
8 | import android.view.View;
9 |
10 | /**
11 | * copy from android source code
12 | */
13 |
14 | class ItemTouchUIUtilImpl {
15 | static class Api21Impl extends ItemTouchUIUtilImpl.BaseImpl {
16 | @Override
17 | public void onDraw(Canvas c, RecyclerView recyclerView, View view,
18 | float dX, float dY, int actionState, boolean isCurrentlyActive) {
19 | if (isCurrentlyActive) {
20 | Object originalElevation = view.getTag(android.support.v7.recyclerview.R.id.item_touch_helper_previous_elevation);
21 | if (originalElevation == null) {
22 | originalElevation = ViewCompat.getElevation(view);
23 | float newElevation = 1f + findMaxElevation(recyclerView, view);
24 | ViewCompat.setElevation(view, newElevation);
25 | view.setTag(android.support.v7.recyclerview.R.id.item_touch_helper_previous_elevation, originalElevation);
26 | }
27 | }
28 | super.onDraw(c, recyclerView, view, dX, dY, actionState, isCurrentlyActive);
29 | }
30 |
31 | private float findMaxElevation(RecyclerView recyclerView, View itemView) {
32 | final int childCount = recyclerView.getChildCount();
33 | float max = 0;
34 | for (int i = 0; i < childCount; i++) {
35 | final View child = recyclerView.getChildAt(i);
36 | if (child == itemView) {
37 | continue;
38 | }
39 | final float elevation = ViewCompat.getElevation(child);
40 | if (elevation > max) {
41 | max = elevation;
42 | }
43 | }
44 | return max;
45 | }
46 |
47 | @Override
48 | public void clearView(View view) {
49 | final Object tag = view.getTag(android.support.v7.recyclerview.R.id.item_touch_helper_previous_elevation);
50 | if (tag != null && tag instanceof Float) {
51 | ViewCompat.setElevation(view, (Float) tag);
52 | }
53 | view.setTag(android.support.v7.recyclerview.R.id.item_touch_helper_previous_elevation, null);
54 | super.clearView(view);
55 | }
56 | }
57 |
58 | static class BaseImpl implements ItemTouchUIUtil {
59 |
60 | @Override
61 | public void clearView(View view) {
62 | view.setTranslationX(0f);
63 | view.setTranslationY(0f);
64 | }
65 |
66 | @Override
67 | public void onSelected(View view) {
68 |
69 | }
70 |
71 | @Override
72 | public void onDraw(Canvas c, RecyclerView recyclerView, View view,
73 | float dX, float dY, int actionState, boolean isCurrentlyActive) {
74 | view.setTranslationX(dX);
75 | view.setTranslationY(dY);
76 | }
77 |
78 | @Override
79 | public void onDrawOver(Canvas c, RecyclerView recyclerView,
80 | View view, float dX, float dY, int actionState, boolean isCurrentlyActive) {
81 |
82 | }
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/app/src/main/java/com/guolei/boardview/SimpleLayoutManager.java:
--------------------------------------------------------------------------------
1 | package com.guolei.boardview;
2 |
3 |
4 | import android.content.Context;
5 | import android.support.v7.widget.LinearLayoutManager;
6 | import android.support.v7.widget.RecyclerView;
7 | import android.util.AttributeSet;
8 | import android.util.Log;
9 |
10 |
11 | public class SimpleLayoutManager extends LinearLayoutManager {
12 |
13 | public SimpleLayoutManager(Context context) {
14 | super(context);
15 | }
16 |
17 | SimpleLayoutManager(Context context, int orientation, boolean reverseLayout) {
18 | super(context, orientation, reverseLayout);
19 | }
20 |
21 | @SuppressWarnings("unused")
22 | public SimpleLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
23 | super(context, attrs, defStyleAttr, defStyleRes);
24 | }
25 |
26 | @Override
27 | public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
28 | try {
29 | super.onLayoutChildren(recycler, state);
30 | } catch (Exception e) {
31 | e.printStackTrace();
32 | }
33 | }
34 |
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/app/src/main/java/com/guolei/ui/BoardViewAdapter.java:
--------------------------------------------------------------------------------
1 | package com.guolei.ui;
2 |
3 |
4 | import android.support.v7.widget.RecyclerView;
5 | import android.view.LayoutInflater;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 | import android.widget.TextView;
9 |
10 | import com.guolei.boardview.BoardViewStateHolder;
11 | import com.guolei.boardview.R;
12 | import com.guolei.boardview.SimpleLayoutManager;
13 |
14 | import java.util.ArrayList;
15 | import java.util.Collections;
16 | import java.util.List;
17 | import java.util.Random;
18 |
19 |
20 | public class BoardViewAdapter extends RecyclerView.Adapter> mData = new ArrayList<>();
23 |
24 | private BoardViewStateHolder mBoardViewHolder;
25 |
26 | public BoardViewAdapter(BoardViewStateHolder boardViewHolder) {
27 | mBoardViewHolder = boardViewHolder;
28 | initData();
29 | }
30 |
31 | private void initData() {
32 | for (int i = 0; i < 10; i++) {
33 | mData.add(new ArrayList<>());
34 | int max = new Random().nextInt(20);
35 | for (int j = 0; j < max; j++) {
36 | mData.get(i).add("column: " + i + ";row:" + j);
37 | }
38 | }
39 | }
40 |
41 | @Override
42 | public SimpleViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
43 | View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_recyclerview,
44 | parent,
45 | false);
46 | return new SimpleViewHolder(view);
47 | }
48 |
49 | @Override
50 | public void onBindViewHolder(final SimpleViewHolder holder, int position) {
51 | holder.recyclerView.setLayoutManager(new SimpleLayoutManager(holder.itemView.getContext()));
52 | RecyclerView.Adapter adapter = new ColumnAdapter(mBoardViewHolder, mData.get(position));
53 | holder.recyclerView.setAdapter(adapter);
54 | if (holder.recyclerView.getItemDecorationAt(0) == null) {
55 | holder.recyclerView.addItemDecoration(new CustomItemDecoration());
56 | }
57 | holder.recyclerView.getItemAnimator().setAddDuration(0);
58 | holder.recyclerView.getItemAnimator().setRemoveDuration(0);
59 | }
60 |
61 | @Override
62 | public int getItemCount() {
63 | return mData.size();
64 | }
65 |
66 | public void swap(int from, int to) {
67 | Collections.swap(mData, from, to);
68 | }
69 |
70 | class SimpleViewHolder extends RecyclerView.ViewHolder {
71 |
72 | RecyclerView recyclerView;
73 | TextView title;
74 |
75 | SimpleViewHolder(View itemView) {
76 | super(itemView);
77 | recyclerView = itemView.findViewById(R.id.recycler_view);
78 | title = itemView.findViewById(R.id.title);
79 | }
80 | }
81 |
82 | }
83 |
--------------------------------------------------------------------------------
/app/src/main/java/com/guolei/ui/ColumnAdapter.java:
--------------------------------------------------------------------------------
1 | package com.guolei.ui;
2 |
3 |
4 | import android.support.v7.widget.RecyclerView;
5 | import android.view.LayoutInflater;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 | import android.widget.TextView;
9 |
10 | import com.guolei.boardview.AbsBoardViewAdapter;
11 | import com.guolei.boardview.BoardViewStateHolder;
12 | import com.guolei.boardview.R;
13 |
14 | import java.util.ArrayList;
15 | import java.util.Collections;
16 | import java.util.List;
17 |
18 |
19 | public class ColumnAdapter extends AbsBoardViewAdapter