title: Custom item animator for RecyclerView. Collapse space after slide animation
body: <p>I try to write custom <strong>ItemAnimator</strong> for <strong>RecyclerView</strong> based on these <a href="https://github.com/gabrielemariotti/RecyclerViewItemAnimators" rel="nofollow">sources</a></p>
  
  <p>So, I have BaseItemAnimator</p>
  
  <pre><code>public abstract class BaseItemAnimator extends RecyclerView.ItemAnimator {
      protected List&lt;RecyclerView.ViewHolder&gt; pendingRemovals     = new ArrayList&lt;&gt;();
      protected List&lt;RecyclerView.ViewHolder&gt; pendingAdditions    = new ArrayList&lt;&gt;();
      protected List&lt;MoveState&gt; pendingMoves                      = new ArrayList&lt;&gt;();
  
      protected List&lt;RecyclerView.ViewHolder&gt; pendingRunAdditions = new ArrayList&lt;&gt;();
      protected List&lt;MoveState&gt; pendingRunMoves                   = new ArrayList&lt;&gt;();
  
      protected List&lt;RecyclerView.ViewHolder&gt; removals    = new ArrayList&lt;&gt;();
      protected List&lt;RecyclerView.ViewHolder&gt; additions   = new ArrayList&lt;&gt;();
      protected List&lt;RecyclerView.ViewHolder&gt; moves       = new ArrayList&lt;&gt;();
  
      protected static class MoveState {
          public RecyclerView.ViewHolder holder;
          public int fromX, fromY, toX, toY;
  
          /**
           * Constructor
           * @param holder ViewHolder
           * @param fromX Start x-coordinate of view location
           * @param fromY Start y-coordinate of view location
           * @param toX End x-coordinate of view location
           * @param toY End y-coordinate of view location
           */
          public MoveState(RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY) {
              this.holder = holder;
              this.fromX = fromX;
              this.fromY = fromY;
              this.toX = toX;
              this.toY = toY;
          }
      }
  
      @Override
      public void runPendingAnimations() {
          final boolean isEmptyRemovals     = pendingRemovals.isEmpty();
          final boolean isEmptyAdditions    = pendingAdditions.isEmpty();
          final boolean isEmptyMoves        = pendingMoves.isEmpty();
  
          if(isEmptyRemovals &amp;&amp; isEmptyAdditions &amp;&amp; isEmptyMoves)
              return; //Animation is not required
  
          //Execute remove animations
          for(RecyclerView.ViewHolder holder : pendingRemovals) {
              executeRemoveAnimation(holder);
          }
  
          pendingRemovals.clear();
          //Execute move animations
          if(!isEmptyMoves) {
              pendingRunMoves.addAll(pendingMoves);
              pendingMoves.clear();
              Runnable moveAnimationRunner = new Runnable() {
                  @Override
                  public void run() {
  
                      for(MoveState moveState : pendingRunMoves)
                          executeMoveAnimation(moveState);
  
                      pendingRunMoves.clear();
                  }
              };
  
              if(!isEmptyMoves) {
  
              }
              else {
                  moveAnimationRunner.run();
              }
          }
  
          //Execute add animations
          if(!isEmptyAdditions) {
              pendingRunAdditions.addAll(pendingAdditions);
              pendingAdditions.clear();
              Runnable addAnimationRunner = new Runnable() {
  
                  @Override
                  public void run() {
  
                      for(RecyclerView.ViewHolder holder : pendingRunAdditions)
                          executeAddAnimation(holder);
  
                      pendingRunAdditions.clear();
                  }
              };
  
              if(!(isEmptyMoves || isEmptyRemovals)) {
  
              }
              else {
                  addAnimationRunner.run();
              }
          }
      }
  
      @Override
      public boolean animateRemove(RecyclerView.ViewHolder holder) {
          prepareRemoveAnimation(holder);
          pendingRemovals.add(holder);
          return true;
      }
  
      @Override
      public boolean animateAdd(RecyclerView.ViewHolder holder) {
          prepareAddAnimation(holder);
          pendingAdditions.add(holder);
          return true;
      }
  
      @Override
      public boolean animateMove(RecyclerView.ViewHolder holder,
                                 int fromX, int fromY, int toX, int toY) {
          final View view = holder.itemView;
          int dX = toX - fromX;
          int dY = toY - fromY;
  
          if (dX == 0 &amp;&amp; dY == 0) {
              dispatchMoveFinished(holder);
              return false;
          }
  
          if (dX != 0) {
              ViewCompat.setTranslationX(view, -dX);
          }
  
          if (dY != 0) {
              ViewCompat.setTranslationY(view, -dY);
          }
  
          pendingMoves.add(new MoveState(holder, fromX, fromY, toX, toY));
          return true;
      }
  
      @Override
      public boolean animateChange(RecyclerView.ViewHolder oldHolder,
                                   RecyclerView.ViewHolder newHolder,
                                   int fromLeft, int fromTop,
                                   int toLeft, int toTop) {
          //TODO: Make change animation
          return false;
      }
  
      private void resetTranslation(final View view) {
          ViewCompat.setTranslationY(view, 0);
          ViewCompat.setTranslationX(view, 0);
      }
  
      @Override
      public void endAnimation(RecyclerView.ViewHolder item) {
          final View view = item.itemView;
          ViewCompat.animate(view).cancel();
  
          if (pendingMoves.remove(item)) {
              resetTranslation(view);
              dispatchMoveFinished(item);
          }
  
          if (pendingRemovals.remove(item)) {
              dispatchRemoveFinished(item);
          }
  
          if (pendingAdditions.remove(item)) {
              ViewCompat.setAlpha(view, 1);
              dispatchAddFinished(item);
          }
  
          if (moves.remove(item)) {
              resetTranslation(view);
              dispatchMoveFinished(item);
          }
  
          if (removals.remove(item)) {
              ViewCompat.setAlpha(view, 1);
              dispatchRemoveFinished(item);
          }
  
          if (additions.remove(item)) {
              ViewCompat.setAlpha(view, 1);
              dispatchAddFinished(item);
          }
  
          dispatchFinishedWhenDone();
      }
  
      @Override
      public void endAnimations() {
          int count = pendingMoves.size();
  
          for (int i = count - 1; i &gt;= 0; i--) {
              MoveState item = pendingMoves.get(i);
              View view = item.holder.itemView;
              ViewCompat.animate(view).cancel();
              resetTranslation(view);
              dispatchMoveFinished(item.holder);
              pendingMoves.remove(item);
          }
  
          count = pendingRemovals.size();
  
          for (int i = count - 1; i &gt;= 0; i--) {
              RecyclerView.ViewHolder item = pendingRemovals.get(i);
              dispatchRemoveFinished(item);
              pendingRemovals.remove(item);
          }
  
          count = pendingAdditions.size();
  
          for (int i = count - 1; i &gt;= 0; i--) {
              RecyclerView.ViewHolder item = pendingAdditions.get(i);
              View view = item.itemView;
              ViewCompat.setAlpha(view, 1);
              dispatchAddFinished(item);
              pendingAdditions.remove(item);
          }
  
          if (!isRunning()) {
              return;
          }
  
          count = moves.size();
  
          for (int i = count - 1; i &gt;= 0; i--) {
              RecyclerView.ViewHolder item = moves.get(i);
              View view = item.itemView;
              ViewCompat.animate(view).cancel();
              resetTranslation(view);
              dispatchMoveFinished(item);
              moves.remove(item);
          }
  
          count = removals.size();
  
          for (int i = count - 1; i &gt;= 0; i--) {
              RecyclerView.ViewHolder item = removals.get(i);
              View view = item.itemView;
              ViewCompat.animate(view).cancel();
              ViewCompat.setAlpha(view, 1);
              dispatchRemoveFinished(item);
              removals.remove(item);
          }
  
          count = additions.size();
  
          for (int i = count - 1; i &gt;= 0; i--) {
              RecyclerView.ViewHolder item = additions.get(i);
              View view = item.itemView;
              ViewCompat.animate(view).cancel();
              ViewCompat.setAlpha(view, 1);
              dispatchAddFinished(item);
              additions.remove(item);
          }
  
          pendingRunMoves.clear();
          pendingRunAdditions.clear();
          dispatchAnimationsFinished();
      }
  
      @Override
      public boolean isRunning() {
          return !(moves.isEmpty()
                  &amp;&amp; removals.isEmpty()
                  &amp;&amp; additions.isEmpty()
                  &amp;&amp; pendingRunMoves.isEmpty()
                  &amp;&amp; pendingRunAdditions.isEmpty());
      }
  
      protected abstract void prepareAddAnimation(RecyclerView.ViewHolder holder);
  
      protected abstract void prepareRemoveAnimation(RecyclerView.ViewHolder holder);
  
      protected abstract void executeAddAnimation(RecyclerView.ViewHolder holder);
  
      protected abstract void executeRemoveAnimation(RecyclerView.ViewHolder holder);
  
      protected void executeMoveAnimation(final MoveState moveState) {
          final View view = moveState.holder.itemView;
          final int dX = moveState.toX - moveState.fromX;
          final int dY = moveState.toY - moveState.fromY;
          ViewCompat.animate(view).cancel();
  
          if (dX != 0) {
              ViewCompat.animate(view).translationX(0);
          }
  
          if (dY != 0) {
              ViewCompat.animate(view).translationY(0);
          }
  
          // TODO: make EndActions end listeners instead, since end actions aren't called when
          // vpas are canceled (and can't end them. why?)
          // need listener functionality in VPACompat for this. Ick.
          ViewCompat.animate(view)
                  .setDuration(getMoveDuration())
                  .setListener(new DefaultVPAListener() {
  
                      @Override
                      public void onAnimationCancel(View view) {
  
                          if (dX != 0) {
                              ViewCompat.setTranslationX(view, 0);
                          }
  
                          if (dY != 0) {
                              ViewCompat.setTranslationY(view, 0);
                          }
                      }
  
                      @Override
                      public void onAnimationEnd(View view) {
                          dispatchMoveFinished(moveState.holder);
                          moves.remove(moveState.holder);
                          dispatchFinishedWhenDone();
                      }
  
                  }).start();
  
          moves.add(moveState.holder);
      }
  
      /**
       * Check the state of currently pending and running animations. If there are none
       * pending/running, call {@link #dispatchAnimationsFinished()} to notify any
       * listeners.
       */
      protected void dispatchFinishedWhenDone() {
  
          if (!isRunning()) {
              dispatchAnimationsFinished();
          }
      }
  
      public static class DefaultVPAListener implements ViewPropertyAnimatorListener {
  
          @Override
          public void onAnimationStart(View view) {
  
          }
  
          @Override
          public void onAnimationEnd(View view) {
  
          }
  
          @Override
          public void onAnimationCancel(View view) {
  
          }
      }
  }
  </code></pre>
  
  <p>And ColorSlideItemAnimator</p>
  
  <pre><code>public class ColorSlideItemAnimator extends BaseItemAnimator {
      protected RecyclerView recyclerView;
  
      public ColorSlideItemAnimator(RecyclerView recyclerView) {
          this.recyclerView = recyclerView;
      }
  
      @Override
      protected void prepareAddAnimation(final RecyclerView.ViewHolder holder) {
          ViewCompat.setTranslationX(holder.itemView, -recyclerView.getWidth());
      }
  
      @Override
      protected void prepareRemoveAnimation(RecyclerView.ViewHolder holder) {
  
      }
  
      @Override
      protected void executeAddAnimation(final RecyclerView.ViewHolder holder) {
          final View view = holder.itemView;
          ViewCompat.animate(view).cancel();
          ViewCompat.animate(view)
                  .translationX(0)
                  .setDuration(getAddDuration())
                  .setListener(new DefaultVPAListener() {
  
                      @Override
                      public void onAnimationCancel(View view) {
                          ViewCompat.setTranslationX(view, 0);
                      }
  
                      @Override
                      public void onAnimationEnd(View view) {
                          dispatchAddFinished(holder);
                          additions.remove(holder);
                          dispatchFinishedWhenDone();
                      }
  
                  }).start();
  
          additions.add(holder);
      }
  
      @Override
      protected void executeRemoveAnimation(final RecyclerView.ViewHolder holder) {
          final View view = holder.itemView;
          ViewCompat.animate(view).cancel();
          ViewCompat.animate(view)
                  .setDuration(getRemoveDuration())
                  .translationX(-recyclerView.getWidth())
                  .setListener(new DefaultVPAListener() {
  
                      @Override
                      public void onAnimationEnd(View view) {
                          ViewCompat.setTranslationX(view, -recyclerView.getWidth());
                          dispatchRemoveFinished(holder);
                          removals.remove(holder);
                          dispatchFinishedWhenDone();
                      }
  
                  }).start();
  
          removals.add(holder);
      }
  }
  </code></pre>
  
  <p>I write this code to understand source on git to I could write animator that needed me. And this code works, it's slide item on remove event but I cannot understand how to collapse space when item deleted? </p>
  
  <p>Now, I have empty space after item sliding.</p>
  
  <p>P.S. I very sorry for my English. If something isn't understandable, I try to explain more details.</p>