Advertisement
Guest User

RecyclerViewSwipeToDismissTouchListener

a guest
Feb 8th, 2015
336
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Java 18.68 KB | None | 0 0
  1. import android.animation.Animator;
  2. import android.animation.AnimatorListenerAdapter;
  3. import android.animation.ValueAnimator;
  4. import android.content.DialogInterface;
  5. import android.graphics.Rect;
  6. import android.os.SystemClock;
  7. import android.support.annotation.NonNull;
  8. import android.support.v7.widget.RecyclerView;
  9. import android.util.Log;
  10. import android.view.MotionEvent;
  11. import android.view.VelocityTracker;
  12. import android.view.View;
  13. import android.view.ViewConfiguration;
  14. import android.view.ViewGroup;
  15. import android.widget.ListView;
  16.  
  17. import java.util.ArrayList;
  18. import java.util.Collections;
  19. import java.util.List;
  20.  
  21. import cn.pedant.SweetAlert.SweetAlertDialog;
  22.  
  23. public class SwipeableRecyclerViewTouchListener implements RecyclerView.OnItemTouchListener {
  24.     // Cached ViewConfiguration and system-wide constant values
  25.     private int mSlop;
  26.     private int mMinFlingVelocity;
  27.     private int mMaxFlingVelocity;
  28.     private long mAnimationTime;
  29.     // Fixed properties
  30.     private RecyclerView mRecyclerView;
  31.     private SwipeListener mSwipeListener;
  32.     private int mViewWidth = 1; // 1 and not 0 to prevent dividing by zero
  33.     // Transient properties
  34.     private List<PendingDismissData> mPendingDismisses = new ArrayList<>();
  35.     private int mDismissAnimationRefCount = 0;
  36.     private float mDownX;
  37.     private float mDownY;
  38.     private boolean mSwiping;
  39.     private int mSwipingSlop;
  40.     private VelocityTracker mVelocityTracker;
  41.     private int mDownPosition;
  42.     private View mDownView;
  43.     private boolean mPaused;
  44.     private float mFinalDelta;
  45.     /**
  46.      * Constructs a new swipe touch listener for the given {@link android.support.v7.widget.RecyclerView}
  47.      *
  48.      * @param recyclerView The recycler view whose items should be dismissable by swiping.
  49.      * @param listener The listener for the swipe events.
  50.      */
  51.     public SwipeableRecyclerViewTouchListener(RecyclerView recyclerView, SwipeListener listener) {
  52.         ViewConfiguration vc = ViewConfiguration.get(recyclerView.getContext());
  53.         mSlop = vc.getScaledTouchSlop();
  54.         mMinFlingVelocity = vc.getScaledMinimumFlingVelocity() * 16;
  55.         mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();
  56.         mAnimationTime = recyclerView.getContext().getResources().getInteger(
  57.                 android.R.integer.config_shortAnimTime);
  58.         mRecyclerView = recyclerView;
  59.         mSwipeListener = listener;
  60. /**
  61.  * This will ensure that this SwipeableRecyclerViewTouchListener is paused during list view scrolling.
  62.  * If a scroll listener is already assigned, the caller should still pass scroll changes through
  63.  * to this listener.
  64.  */
  65.         mRecyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
  66.             @Override
  67.             public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
  68.                 setEnabled(newState != RecyclerView.SCROLL_STATE_DRAGGING);
  69.             }
  70.             @Override
  71.             public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
  72.             }
  73.         });
  74.     }
  75.     /**
  76.      * Enables or disables (pauses or resumes) watching for swipe-to-dismiss gestures.
  77.      *
  78.      * @param enabled Whether or not to watch for gestures.
  79.      */
  80.     public void setEnabled(boolean enabled) {
  81.         mPaused = !enabled;
  82.     }
  83.     @Override
  84.     public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent motionEvent) {
  85.         return handleTouchEvent(motionEvent);
  86.     }
  87.     @Override
  88.     public void onTouchEvent(RecyclerView rv, MotionEvent motionEvent) {
  89.         handleTouchEvent(motionEvent);
  90.     }
  91.     private boolean handleTouchEvent(MotionEvent motionEvent) {
  92.         if (mViewWidth < 2) {
  93.             mViewWidth = mRecyclerView.getWidth();
  94.         }
  95.         switch (motionEvent.getActionMasked()) {
  96.             case MotionEvent.ACTION_DOWN: {
  97.                 if (mPaused) {
  98.                     break;
  99.                 }
  100. // Find the child view that was touched (perform a hit test)
  101.                 Rect rect = new Rect();
  102.                 int childCount = mRecyclerView.getChildCount();
  103.                 int[] listViewCoords = new int[2];
  104.                 mRecyclerView.getLocationOnScreen(listViewCoords);
  105.                 int x = (int) motionEvent.getRawX() - listViewCoords[0];
  106.                 int y = (int) motionEvent.getRawY() - listViewCoords[1];
  107.                 View child;
  108.                 for (int i = 0; i < childCount; i++) {
  109.                     child = mRecyclerView.getChildAt(i);
  110.                     child.getHitRect(rect);
  111.                     if (rect.contains(x, y)) {
  112.                         mDownView = child;
  113.                         break;
  114.                     }
  115.                 }
  116.                 if (mDownView != null) {
  117.                     mDownX = motionEvent.getRawX();
  118.                     mDownY = motionEvent.getRawY();
  119.                     mDownPosition = mRecyclerView.getChildPosition(mDownView);
  120.                     if (mSwipeListener.canSwipe(mDownPosition)) {
  121.                         mVelocityTracker = VelocityTracker.obtain();
  122.                         mVelocityTracker.addMovement(motionEvent);
  123.                     } else {
  124.                         mDownView = null;
  125.                     }
  126.                 }
  127.                 break;
  128.             }
  129.  
  130.             case MotionEvent.ACTION_CANCEL: {
  131.                 if (mVelocityTracker == null) {
  132.                     break;
  133.                 }
  134.                 if (mDownView != null && mSwiping) {
  135. // cancel
  136.                     mDownView.animate()
  137.                             .translationX(0)
  138.                             .alpha(1)
  139.                             .setDuration(mAnimationTime)
  140.                             .setListener(null);
  141.                 }
  142.                 mVelocityTracker.recycle();
  143.                 mVelocityTracker = null;
  144.                 mDownX = 0;
  145.                 mDownY = 0;
  146.                 mDownView = null;
  147.                 mDownPosition = ListView.INVALID_POSITION;
  148.                 mSwiping = false;
  149.                 break;
  150.             }
  151.             case MotionEvent.ACTION_UP: {
  152.                 if (mVelocityTracker == null) {
  153.                     break;
  154.                 }
  155.                 mFinalDelta = motionEvent.getRawX() - mDownX;
  156.                 mVelocityTracker.addMovement(motionEvent);
  157.                 mVelocityTracker.computeCurrentVelocity(1000);
  158.                 float velocityX = mVelocityTracker.getXVelocity();
  159.                 float absVelocityX = Math.abs(velocityX);
  160.                 float absVelocityY = Math.abs(mVelocityTracker.getYVelocity());
  161.                 boolean dismiss = false;
  162.                 boolean dismissRight = false;
  163.                 if (Math.abs(mFinalDelta) > mViewWidth / 2 && mSwiping) {
  164.                     dismiss = true;
  165.                     dismissRight = mFinalDelta > 0;
  166.                 } else if (mMinFlingVelocity <= absVelocityX && absVelocityX <= mMaxFlingVelocity
  167.                         && absVelocityY < absVelocityX && mSwiping) {
  168. // dismiss only if flinging in the same direction as dragging
  169.                     dismiss = (velocityX < 0) == (mFinalDelta < 0);
  170.                     dismissRight = mVelocityTracker.getXVelocity() > 0;
  171.                 }
  172.  
  173.  
  174.                 if(!dismiss) {
  175.                     mDownView.animate()
  176.                             .translationX(0)
  177.                             .alpha(1)
  178.                             .setDuration(mAnimationTime)
  179.                             .setListener(null);
  180.  
  181.  
  182.                     mVelocityTracker.recycle();
  183.                     mVelocityTracker = null;
  184.                     mDownX = 0;
  185.                     mDownY = 0;
  186.                     mDownView = null;
  187.                     mDownPosition = ListView.INVALID_POSITION;
  188.                     mSwiping = false;
  189.  
  190.                     Log.d("if!dismiss", "here now");
  191.                 }
  192.  
  193.  
  194.                 if (dismiss && mDownPosition != ListView.INVALID_POSITION) {
  195. // dismiss
  196.                     final View downView = mDownView; // mDownView gets null'd before animation ends
  197.                     final int downPosition = mDownPosition;
  198.                     ++mDismissAnimationRefCount;
  199.  
  200.  
  201.                     final SweetAlertDialog dialog = new SweetAlertDialog(mRecyclerView.getContext(), SweetAlertDialog.WARNING_TYPE);
  202.                     dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
  203.                         @Override
  204.                         public void onDismiss(DialogInterface dialog) {
  205.                             mRecyclerView.invalidateItemDecorations();
  206.                         }
  207.                     });
  208.                     final boolean finalDismissRight = dismissRight;
  209.                     dialog
  210.                             .setTitleText("Are you sure?")
  211.                             .setContentText("Won't be able to recover this file!")
  212.                             .setCancelText("No,cancel plx!")
  213.                             .setConfirmText("Yes,delete it!")
  214.                             .showCancelButton(true)
  215.                             .setCancelClickListener(new SweetAlertDialog.OnSweetClickListener() {
  216.                                 @Override
  217.                                 public void onClick(SweetAlertDialog sDialog) {
  218.                                     sDialog.dismiss();
  219.                                     dialog.dismiss();
  220.  
  221.                                     if(mDownView != null) {
  222.                                         mDownView.animate()
  223.                                                 .translationX(0)
  224.                                                 .alpha(1)
  225.                                                 .setDuration(mAnimationTime)
  226.                                                 .setListener(null);
  227.                                         Log.d("CancelClickListener", "If statement");
  228.                                         mRecyclerView.invalidateItemDecorations();
  229.                                     }
  230.  
  231.                                     else {
  232.                                         downView.animate()
  233.                                                 .translationX(0)
  234.                                                 .alpha(1)
  235.                                                 .setDuration(mAnimationTime)
  236.                                                 .setListener(null);
  237.                                         Log.d("CancelClickListener", "Else statement");
  238.                                         mRecyclerView.invalidateItemDecorations();
  239.                                     }
  240.  
  241.                                         mVelocityTracker.recycle();
  242.                                         mVelocityTracker = null;
  243.                                         mDownX = 0;
  244.                                         mDownY = 0;
  245.                                         mDownView = null;
  246.                                         mDownPosition = ListView.INVALID_POSITION;
  247.                                         mSwiping = false;
  248.  
  249.                                     Log.d("CancelClickListener", "After if and else statement");
  250.                                     mRecyclerView.invalidateItemDecorations();
  251.                                     sDialog.dismiss();
  252.                                 }
  253.                             })
  254.                             .setConfirmClickListener(new SweetAlertDialog.OnSweetClickListener() {
  255.                                 @Override
  256.                                 public void onClick(SweetAlertDialog sweetAlertDialog) {
  257.                                     dialog.cancel();
  258.                                     new SweetAlertDialog(sweetAlertDialog.getContext(), SweetAlertDialog.SUCCESS_TYPE)
  259.                                             .setTitleText("Good job!")
  260.                                             .setContentText("You clicked the button!")
  261.                                             .show();
  262.  
  263.                                     mDownView.animate()
  264.                                             .translationX(finalDismissRight ? mViewWidth : -mViewWidth)
  265.                                             .alpha(0)
  266.                                             .setDuration(mAnimationTime)
  267.                                             .setListener(new AnimatorListenerAdapter() {
  268.                                                 @Override
  269.                                                 public void onAnimationEnd(Animator animation) {
  270.                                                     performDismiss(downView, downPosition);
  271.                                                     Log.d("ConfirmClickListener", "OnAnimationEnd");
  272.                                                 }
  273.                                             });
  274.  
  275.                                     mVelocityTracker.recycle();
  276.                                     mVelocityTracker = null;
  277.                                     mDownX = 0;
  278.                                     mDownY = 0;
  279.                                     mDownView = null;
  280.                                     mDownPosition = ListView.INVALID_POSITION;
  281.                                     mSwiping = false;
  282.                                     Log.d("ConfirmClickListener", "last statement");
  283.  
  284.                                 }
  285.                             })
  286.                             .show();
  287.                 }
  288.                 break;
  289.             }
  290.             case MotionEvent.ACTION_MOVE: {
  291.                 if (mVelocityTracker == null || mPaused) {
  292.                     break;
  293.                 }
  294.                 mVelocityTracker.addMovement(motionEvent);
  295.                 float deltaX = motionEvent.getRawX() - mDownX;
  296.                 float deltaY = motionEvent.getRawY() - mDownY;
  297.                 if (!mSwiping && Math.abs(deltaX) > mSlop && Math.abs(deltaY) < Math.abs(deltaX) / 2) {
  298.                     mSwiping = true;
  299.                     mSwipingSlop = (deltaX > 0 ? mSlop : -mSlop);
  300.                 }
  301.                 if (mSwiping) {
  302.                     mDownView.setTranslationX(deltaX - mSwipingSlop);
  303.                     mDownView.setAlpha(Math.max(0f, Math.min(1f,
  304.                             1f - Math.abs(deltaX) / mViewWidth)));
  305.                     return true;
  306.                 }
  307.                 break;
  308.             }
  309.         }
  310.         return false;
  311.     }
  312.     private void performDismiss(final View dismissView, final int dismissPosition) {
  313. // Animate the dismissed list item to zero-height and fire the dismiss callback when
  314. // all dismissed list item animations have completed. This triggers layout on each animation
  315. // frame; in the future we may want to do something smarter and more performant.
  316.  
  317.         final ViewGroup.LayoutParams lp = dismissView.getLayoutParams();
  318.         final int originalHeight = dismissView.getHeight();
  319.         ValueAnimator animator = ValueAnimator.ofInt(originalHeight, 1).setDuration(mAnimationTime);
  320.         animator.addListener(new AnimatorListenerAdapter() {
  321.             @Override
  322.             public void onAnimationEnd(Animator animation) {
  323.                 --mDismissAnimationRefCount;
  324.                 if (mDismissAnimationRefCount == 0) {
  325. // No active animations, process all pending dismisses.
  326. // Sort by descending position
  327.  
  328.                 Collections.sort(mPendingDismisses);
  329.                 int[] dismissPositions = new int[mPendingDismisses.size()];
  330.                 for (int i = mPendingDismisses.size() - 1; i >= 0; i--) {
  331.                     dismissPositions[i] = mPendingDismisses.get(i).position;
  332.                 }
  333.                 if (mFinalDelta > 0) {
  334.                     mSwipeListener.onDismissedBySwipeRight(mRecyclerView, dismissPositions);
  335.                 } else {
  336.                     mSwipeListener.onDismissedBySwipeLeft(mRecyclerView, dismissPositions);
  337.                 }
  338. // Reset mDownPosition to avoid MotionEvent.ACTION_UP trying to start a dismiss
  339. // animation with a stale position
  340.                 mDownPosition = ListView.INVALID_POSITION;
  341.                 ViewGroup.LayoutParams lp;
  342.                 for (PendingDismissData pendingDismiss : mPendingDismisses) {
  343. // Reset view presentation
  344.                     pendingDismiss.view.setAlpha(1f);
  345.                     pendingDismiss.view.setTranslationX(0);
  346.                     lp = pendingDismiss.view.getLayoutParams();
  347.                     lp.height = originalHeight;
  348.                     pendingDismiss.view.setLayoutParams(lp);
  349.                 }
  350. // Send a cancel event
  351.                 long time = SystemClock.uptimeMillis();
  352.                 MotionEvent cancelEvent = MotionEvent.obtain(time, time,
  353.                         MotionEvent.ACTION_CANCEL, 0, 0, 0);
  354.                 mRecyclerView.dispatchTouchEvent(cancelEvent);
  355.                 mPendingDismisses.clear();
  356.             }
  357.         }
  358.     });
  359.     animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
  360.         @Override
  361.         public void onAnimationUpdate(ValueAnimator valueAnimator) {
  362.             lp.height = (Integer) valueAnimator.getAnimatedValue();
  363.             dismissView.setLayoutParams(lp);
  364.         }
  365.     });
  366.     mPendingDismisses.add(new PendingDismissData(dismissPosition, dismissView));
  367.     animator.start();
  368.  
  369.  
  370.     }
  371.     /**
  372.      * The callback interface used by {@link SwipeableRecyclerViewTouchListener} to inform its client
  373.      * about a swipe of one or more list item positions.
  374.      */
  375.     public interface SwipeListener {
  376.         /**
  377.          * Called to determine whether the given position can be swiped.
  378.          */
  379.         boolean canSwipe(int position);
  380.         /**
  381.          * Called when the item has been dismissed by swiping to the left.
  382.          *
  383.          * @param recyclerView The originating {@link android.support.v7.widget.RecyclerView}.
  384.          * @param reverseSortedPositions An array of positions to dismiss, sorted in descending
  385.          * order for convenience.
  386.          */
  387.         void onDismissedBySwipeLeft(RecyclerView recyclerView, int[] reverseSortedPositions);
  388.         /**
  389.          * Called when the item has been dismissed by swiping to the right.
  390.          *
  391.          * @param recyclerView The originating {@link android.support.v7.widget.RecyclerView}.
  392.          * @param reverseSortedPositions An array of positions to dismiss, sorted in descending
  393.          * order for convenience.
  394.          */
  395.         void onDismissedBySwipeRight(RecyclerView recyclerView, int[] reverseSortedPositions);
  396.     }
  397.     class PendingDismissData implements Comparable<PendingDismissData> {
  398.         public int position;
  399.         public View view;
  400.         public PendingDismissData(int position, View view) {
  401.             this.position = position;
  402.             this.view = view;
  403.         }
  404.         @Override
  405.         public int compareTo(@NonNull PendingDismissData other) {
  406. // Sort by descending position
  407.             return other.position - position;
  408.         }
  409.     }
  410. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement