title: How can I make my Android SwipeableCardViews more like the IOS 7 mail app (swipe to show buttons)
body: <p>How can I make my <code>SwipeableCardViews</code> more like the IOS 7 mail app (swipe to show buttons)</p>
  
  <p>So far I have created an android application which allows the user to swipe <code>Cardviews</code> either to the left or to the right. Each card has 2 buttons, which at a later date I will assign functions to. </p>
  
  <p>(As the image below shows):</p>
  
  <p><img src="https://i.stack.imgur.com/wdAJW.png" alt="enter image description here"></p>
  
  <p>What I would like to accomplish is (but don't yet know how), is instead of swiping to the left or right to completely remove a card.</p>
  
  <p>Instead I would like either swipe gesture, to reveal a button which was hidden underneath the <em>right</em> or <em>left</em> card.</p>
  
  <p>The IOS 7 mail app already performs this function (screenshot below) I would like to perform this in Android using <code>Cardviews</code> <strong>(not <code>ListViews</code>)</strong>. </p>
  
  <p><em>How can I go about doing this?</em></p>
  
  <p><img src="https://i.stack.imgur.com/2HXtc.jpg" alt="enter image description here"></p>
  
  <p><strong>My code so far:</strong></p>
  
  <p><strong>MainActivity.java</strong></p>
  
  <pre><code>public class MainActivity extends ActionBarActivity {
      private RecyclerView mRecyclerView;
      private CardViewAdapter mAdapter;
  
      private ArrayList&lt;String&gt; mItems;
  
      @Override
      protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.activity_main);
  
          mItems = new ArrayList&lt;&gt;(30);
          for (int i = 0; i &lt; 30; i++) {
              mItems.add(String.format("Card number %2d", i));
          }
  
          OnItemTouchListener itemTouchListener = new OnItemTouchListener() {
              @Override
              public void onCardViewTap(View view, int position) {
                  Toast.makeText(MainActivity.this, "Tapped " + mItems.get(position), Toast.LENGTH_SHORT).show();
              }
  
              @Override
              public void onButton1Click(View view, int position) {
                  Toast.makeText(MainActivity.this, "Clicked Button1 in " + mItems.get(position), Toast.LENGTH_SHORT).show();
              }
  
              @Override
              public void onButton2Click(View view, int position) {
                  Toast.makeText(MainActivity.this, "Clicked Button2 in " + mItems.get(position), Toast.LENGTH_SHORT).show();
              }
          };
  
          mAdapter = new CardViewAdapter(mItems, itemTouchListener);
  
          mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
  
          mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
          mRecyclerView.setAdapter(mAdapter);
  
          SwipeableRecyclerViewTouchListener swipeTouchListener =
                  new SwipeableRecyclerViewTouchListener(mRecyclerView,
                          new SwipeableRecyclerViewTouchListener.SwipeListener() {
                              @Override
                              public boolean canSwipe(int position) {
                                  return true;
                              }
  
                              @Override
                              public void onDismissedBySwipeLeft(RecyclerView recyclerView, int[] reverseSortedPositions) {
                                  for (int position : reverseSortedPositions) {
                                      mItems.remove(position);
                                      mAdapter.notifyItemRemoved(position);
                                  }
                                  mAdapter.notifyDataSetChanged();
                              }
  
                              @Override
                              public void onDismissedBySwipeRight(RecyclerView recyclerView, int[] reverseSortedPositions) {
                                  for (int position : reverseSortedPositions) {
                                      mItems.remove(position);
                                      mAdapter.notifyItemRemoved(position);
                                  }
                                  mAdapter.notifyDataSetChanged();
                              }
                          });
  
          mRecyclerView.addOnItemTouchListener(swipeTouchListener);
      }
  
      /**
       * Interface for the touch events in each item
       */
      public interface OnItemTouchListener {
          /**
           * Callback invoked when the user Taps one of the RecyclerView items
           *
           * @param view     the CardView touched
           * @param position the index of the item touched in the RecyclerView
           */
          public void onCardViewTap(View view, int position);
  
          /**
           * Callback invoked when the Button1 of an item is touched
           *
           * @param view     the Button touched
           * @param position the index of the item touched in the RecyclerView
           */
          public void onButton1Click(View view, int position);
  
          /**
           * Callback invoked when the Button2 of an item is touched
           *
           * @param view     the Button touched
           * @param position the index of the item touched in the RecyclerView
           */
          public void onButton2Click(View view, int position);
      }
  
      /**
       * A simple adapter that loads a CardView layout with one TextView and two Buttons, and
       * listens to clicks on the Buttons or on the CardView
       */
      public class CardViewAdapter extends RecyclerView.Adapter&lt;CardViewAdapter.ViewHolder&gt; {
          private List&lt;String&gt; cards;
          private OnItemTouchListener onItemTouchListener;
  
          public CardViewAdapter(List&lt;String&gt; cards, OnItemTouchListener onItemTouchListener) {
              this.cards = cards;
              this.onItemTouchListener = onItemTouchListener;
          }
  
          @Override
          public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
              View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.card_view_layout, viewGroup, false);
              return new ViewHolder(v);
          }
  
          @Override
          public void onBindViewHolder(ViewHolder viewHolder, int i) {
              viewHolder.title.setText(cards.get(i));
          }
  
          @Override
          public int getItemCount() {
              return cards == null ? 0 : cards.size();
          }
  
          public class ViewHolder extends RecyclerView.ViewHolder {
              private TextView title;
              private Button button1;
              private Button button2;
  
              public ViewHolder(View itemView) {
                  super(itemView);
                  title = (TextView) itemView.findViewById(R.id.card_view_title);
                  button1 = (Button) itemView.findViewById(R.id.card_view_button1);
                  button2 = (Button) itemView.findViewById(R.id.card_view_button2);
  
                  button1.setOnClickListener(new View.OnClickListener() {
                      @Override
                      public void onClick(View v) {
                          onItemTouchListener.onButton1Click(v, getPosition());
                      }
                  });
  
                  button2.setOnClickListener(new View.OnClickListener() {
                      @Override
                      public void onClick(View v) {
                          onItemTouchListener.onButton2Click(v, getPosition());
                      }
                  });
  
                  itemView.setOnClickListener(new View.OnClickListener() {
                      @Override
                      public void onClick(View v) {
                          onItemTouchListener.onCardViewTap(v, getPosition());
                      }
                  });
              }
          }
      }
  }
  </code></pre>
  
  <p><strong>SwipeableRecyclerViewTouchListener.java</strong></p>
  
  <pre><code>public class SwipeableRecyclerViewTouchListener implements RecyclerView.OnItemTouchListener {
      // Cached ViewConfiguration and system-wide constant values
      private int mSlop;
      private int mMinFlingVelocity;
      private int mMaxFlingVelocity;
      private long mAnimationTime;
  
      // Fixed properties
      private RecyclerView mRecyclerView;
      private SwipeListener mSwipeListener;
      private int mViewWidth = 1; // 1 and not 0 to prevent dividing by zero
  
      // Transient properties
      private List&lt;PendingDismissData&gt; mPendingDismisses = new ArrayList&lt;&gt;();
      private int mDismissAnimationRefCount = 0;
      private float mDownX;
      private float mDownY;
      private boolean mSwiping;
      private int mSwipingSlop;
      private VelocityTracker mVelocityTracker;
      private int mDownPosition;
      private View mDownView;
      private boolean mPaused;
      private float mFinalDelta;
  
      /**
       * Constructs a new swipe touch listener for the given {@link android.support.v7.widget.RecyclerView}
       *
       * @param recyclerView The recycler view whose items should be dismissable by swiping.
       * @param listener     The listener for the swipe events.
       */
      public SwipeableRecyclerViewTouchListener(RecyclerView recyclerView, SwipeListener listener) {
          ViewConfiguration vc = ViewConfiguration.get(recyclerView.getContext());
          mSlop = vc.getScaledTouchSlop();
          mMinFlingVelocity = vc.getScaledMinimumFlingVelocity() * 16;
          mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();
          mAnimationTime = recyclerView.getContext().getResources().getInteger(
                  android.R.integer.config_shortAnimTime);
          mRecyclerView = recyclerView;
          mSwipeListener = listener;
  
  
          /**
           * This will ensure that this SwipeableRecyclerViewTouchListener is paused during list view scrolling.
           * If a scroll listener is already assigned, the caller should still pass scroll changes through
           * to this listener.
           */
          mRecyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
              @Override
              public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                  setEnabled(newState != RecyclerView.SCROLL_STATE_DRAGGING);
              }
  
              @Override
              public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
              }
          });
      }
  
      /**
       * Enables or disables (pauses or resumes) watching for swipe-to-dismiss gestures.
       *
       * @param enabled Whether or not to watch for gestures.
       */
      public void setEnabled(boolean enabled) {
          mPaused = !enabled;
      }
  
      @Override
      public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent motionEvent) {
          return handleTouchEvent(motionEvent);
      }
  
      @Override
      public void onTouchEvent(RecyclerView rv, MotionEvent motionEvent) {
          handleTouchEvent(motionEvent);
      }
  
      private boolean handleTouchEvent(MotionEvent motionEvent) {
          if (mViewWidth &lt; 2) {
              mViewWidth = mRecyclerView.getWidth();
          }
  
          switch (motionEvent.getActionMasked()) {
              case MotionEvent.ACTION_DOWN: {
                  if (mPaused) {
                      break;
                  }
  
                  // Find the child view that was touched (perform a hit test)
                  Rect rect = new Rect();
                  int childCount = mRecyclerView.getChildCount();
                  int[] listViewCoords = new int[2];
                  mRecyclerView.getLocationOnScreen(listViewCoords);
                  int x = (int) motionEvent.getRawX() - listViewCoords[0];
                  int y = (int) motionEvent.getRawY() - listViewCoords[1];
                  View child;
                  for (int i = 0; i &lt; childCount; i++) {
                      child = mRecyclerView.getChildAt(i);
                      child.getHitRect(rect);
                      if (rect.contains(x, y)) {
                          mDownView = child;
                          break;
                      }
                  }
  
                  if (mDownView != null) {
                      mDownX = motionEvent.getRawX();
                      mDownY = motionEvent.getRawY();
                      mDownPosition = mRecyclerView.getChildPosition(mDownView);
                      if (mSwipeListener.canSwipe(mDownPosition)) {
                          mVelocityTracker = VelocityTracker.obtain();
                          mVelocityTracker.addMovement(motionEvent);
                      } else {
                          mDownView = null;
                      }
                  }
                  break;
              }
  
              case MotionEvent.ACTION_CANCEL: {
                  if (mVelocityTracker == null) {
                      break;
                  }
  
                  if (mDownView != null &amp;&amp; mSwiping) {
                      // cancel
                      mDownView.animate()
                              .translationX(0)
                              .alpha(1)
                              .setDuration(mAnimationTime)
                              .setListener(null);
                  }
                  mVelocityTracker.recycle();
                  mVelocityTracker = null;
                  mDownX = 0;
                  mDownY = 0;
                  mDownView = null;
                  mDownPosition = ListView.INVALID_POSITION;
                  mSwiping = false;
                  break;
              }
  
              case MotionEvent.ACTION_UP: {
                  if (mVelocityTracker == null) {
                      break;
                  }
  
                  mFinalDelta = motionEvent.getRawX() - mDownX;
                  mVelocityTracker.addMovement(motionEvent);
                  mVelocityTracker.computeCurrentVelocity(1000);
                  float velocityX = mVelocityTracker.getXVelocity();
                  float absVelocityX = Math.abs(velocityX);
                  float absVelocityY = Math.abs(mVelocityTracker.getYVelocity());
                  boolean dismiss = false;
                  boolean dismissRight = false;
                  if (Math.abs(mFinalDelta) &gt; mViewWidth / 2 &amp;&amp; mSwiping) {
                      dismiss = true;
                      dismissRight = mFinalDelta &gt; 0;
                  } else if (mMinFlingVelocity &lt;= absVelocityX &amp;&amp; absVelocityX &lt;= mMaxFlingVelocity
                          &amp;&amp; absVelocityY &lt; absVelocityX &amp;&amp; mSwiping) {
                      // dismiss only if flinging in the same direction as dragging
                      dismiss = (velocityX &lt; 0) == (mFinalDelta &lt; 0);
                      dismissRight = mVelocityTracker.getXVelocity() &gt; 0;
                  }
                  if (dismiss &amp;&amp; mDownPosition != ListView.INVALID_POSITION) {
                      // dismiss
                      final View downView = mDownView; // mDownView gets null'd before animation ends
                      final int downPosition = mDownPosition;
                      ++mDismissAnimationRefCount;
                      mDownView.animate()
                              .translationX(dismissRight ? mViewWidth : -mViewWidth)
                              .alpha(0)
                              .setDuration(mAnimationTime)
                              .setListener(new AnimatorListenerAdapter() {
                                  @Override
                                  public void onAnimationEnd(Animator animation) {
                                      performDismiss(downView, downPosition);
                                  }
                              });
                  } else {
                      // cancel
                      mDownView.animate()
                              .translationX(0)
                              .alpha(1)
                              .setDuration(mAnimationTime)
                              .setListener(null);
                  }
                  mVelocityTracker.recycle();
                  mVelocityTracker = null;
                  mDownX = 0;
                  mDownY = 0;
                  mDownView = null;
                  mDownPosition = ListView.INVALID_POSITION;
                  mSwiping = false;
                  break;
              }
  
              case MotionEvent.ACTION_MOVE: {
                  if (mVelocityTracker == null || mPaused) {
                      break;
                  }
  
                  mVelocityTracker.addMovement(motionEvent);
                  float deltaX = motionEvent.getRawX() - mDownX;
                  float deltaY = motionEvent.getRawY() - mDownY;
                  if (!mSwiping &amp;&amp; Math.abs(deltaX) &gt; mSlop &amp;&amp; Math.abs(deltaY) &lt; Math.abs(deltaX) / 2) {
                      mSwiping = true;
                      mSwipingSlop = (deltaX &gt; 0 ? mSlop : -mSlop);
                  }
  
                  if (mSwiping) {
                      mDownView.setTranslationX(deltaX - mSwipingSlop);
                      mDownView.setAlpha(Math.max(0f, Math.min(1f,
                              1f - Math.abs(deltaX) / mViewWidth)));
                      return true;
                  }
                  break;
              }
          }
  
          return false;
      }
  
      private void performDismiss(final View dismissView, final int dismissPosition) {
          // Animate the dismissed list item to zero-height and fire the dismiss callback when
          // all dismissed list item animations have completed. This triggers layout on each animation
          // frame; in the future we may want to do something smarter and more performant.
  
          final ViewGroup.LayoutParams lp = dismissView.getLayoutParams();
          final int originalHeight = dismissView.getHeight();
  
          ValueAnimator animator = ValueAnimator.ofInt(originalHeight, 1).setDuration(mAnimationTime);
  
          animator.addListener(new AnimatorListenerAdapter() {
              @Override
              public void onAnimationEnd(Animator animation) {
                  --mDismissAnimationRefCount;
                  if (mDismissAnimationRefCount == 0) {
                      // No active animations, process all pending dismisses.
                      // Sort by descending position
                      Collections.sort(mPendingDismisses);
  
                      int[] dismissPositions = new int[mPendingDismisses.size()];
                      for (int i = mPendingDismisses.size() - 1; i &gt;= 0; i--) {
                          dismissPositions[i] = mPendingDismisses.get(i).position;
                      }
  
                      if (mFinalDelta &gt; 0) {
                          mSwipeListener.onDismissedBySwipeRight(mRecyclerView, dismissPositions);
                      } else {
                          mSwipeListener.onDismissedBySwipeLeft(mRecyclerView, dismissPositions);
                      }
  
                      // Reset mDownPosition to avoid MotionEvent.ACTION_UP trying to start a dismiss
                      // animation with a stale position
                      mDownPosition = ListView.INVALID_POSITION;
  
                      ViewGroup.LayoutParams lp;
                      for (PendingDismissData pendingDismiss : mPendingDismisses) {
                          // Reset view presentation
                          pendingDismiss.view.setAlpha(1f);
                          pendingDismiss.view.setTranslationX(0);
                          lp = pendingDismiss.view.getLayoutParams();
                          lp.height = originalHeight;
                          pendingDismiss.view.setLayoutParams(lp);
                      }
  
                      // Send a cancel event
                      long time = SystemClock.uptimeMillis();
                      MotionEvent cancelEvent = MotionEvent.obtain(time, time,
                              MotionEvent.ACTION_CANCEL, 0, 0, 0);
                      mRecyclerView.dispatchTouchEvent(cancelEvent);
  
                      mPendingDismisses.clear();
                  }
              }
          });
  
          animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
              @Override
              public void onAnimationUpdate(ValueAnimator valueAnimator) {
                  lp.height = (Integer) valueAnimator.getAnimatedValue();
                  dismissView.setLayoutParams(lp);
              }
          });
  
          mPendingDismisses.add(new PendingDismissData(dismissPosition, dismissView));
          animator.start();
      }
  
      /**
       * The callback interface used by {@link SwipeableRecyclerViewTouchListener} to inform its client
       * about a swipe of one or more list item positions.
       */
      public interface SwipeListener {
          /**
           * Called to determine whether the given position can be swiped.
           */
          boolean canSwipe(int position);
  
          /**
           * Called when the item has been dismissed by swiping to the left.
           *
           * @param recyclerView           The originating {@link android.support.v7.widget.RecyclerView}.
           * @param reverseSortedPositions An array of positions to dismiss, sorted in descending
           *                               order for convenience.
           */
          void onDismissedBySwipeLeft(RecyclerView recyclerView, int[] reverseSortedPositions);
  
          /**
           * Called when the item has been dismissed by swiping to the right.
           *
           * @param recyclerView           The originating {@link android.support.v7.widget.RecyclerView}.
           * @param reverseSortedPositions An array of positions to dismiss, sorted in descending
           *                               order for convenience.
           */
          void onDismissedBySwipeRight(RecyclerView recyclerView, int[] reverseSortedPositions);
      }
  
      class PendingDismissData implements Comparable&lt;PendingDismissData&gt; {
          public int position;
          public View view;
  
          public PendingDismissData(int position, View view) {
              this.position = position;
              this.view = view;
          }
  
          @Override
          public int compareTo(@NonNull PendingDismissData other) {
              // Sort by descending position
              return other.position - position;
          }
      }
  }
  </code></pre>
  
  <p><strong>activity_main.xml</strong></p>
  
  <pre><code>&lt;android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
      android:id="@+id/recycler_view"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:scrollbars="vertical"&gt;
  
  &lt;/android.support.v7.widget.RecyclerView&gt;
  </code></pre>
  
  <p><strong>card_view_layout.xml</strong></p>
  
  <pre><code>&lt;?xml version="1.0" encoding="utf-8"?&gt;
  &lt;android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:card_view="http://schemas.android.com/apk/res-auto"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:layout_margin="5dp"
      android:clickable="true"
      android:foreground="?android:attr/selectableItemBackground"
      android:orientation="vertical"
      card_view:cardCornerRadius="5dp"&gt;
  
      &lt;RelativeLayout
          android:layout_width="match_parent"
          android:layout_height="match_parent"&gt;
  
          &lt;TextView
              android:id="@+id/card_view_title"
              android:layout_width="match_parent"
              android:layout_height="50dp"
              android:layout_alignParentTop="true"
              android:layout_centerHorizontal="true"
              android:gravity="center"
              android:textColor="@android:color/black"
              android:textSize="24sp" /&gt;
  
          &lt;LinearLayout
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:layout_alignParentBottom="true"
              android:layout_below="@id/card_view_title"
              android:layout_centerHorizontal="true"
              android:gravity="center"&gt;
  
              &lt;Button
                  android:id="@+id/card_view_button1"
                  android:layout_width="wrap_content"
                  android:layout_height="wrap_content"
                  android:text="Button1" /&gt;
  
              &lt;Button
                  android:id="@+id/card_view_button2"
                  android:layout_width="wrap_content"
                  android:layout_height="wrap_content"
                  android:text="Button2" /&gt;
          &lt;/LinearLayout&gt;
      &lt;/RelativeLayout&gt;
  
  &lt;/android.support.v7.widget.CardView&gt;
  </code></pre>