protected ObjectAnimator animateAlpha(final View view, final boolean fadeIn) {
    final ObjectAnimator anim =
        ObjectAnimator.ofFloat(view, "alpha", fadeIn ? new float[] {0f, 1f} : new float[] {1f, 0f});

    anim.setDuration((long) (ALPHA_ANIM_DURATION * animationDurationFactor));

    return anim;
  }
  protected ObjectAnimator animateY(
      final View view, final float oldY, final float newY, final float durationUnit) {
    final int duration = getDuration(oldY, newY, durationUnit);

    final ObjectAnimator anim =
        ObjectAnimator.ofFloat(AnimatorProxy.wrap(view), "translationY", oldY - newY, 0);

    final int finalDuration = Math.min(Math.max(duration, MIN_ANIM_DURATION), MAX_ANIM_DURATION);

    anim.setDuration((long) (finalDuration * animationDurationFactor));
    anim.setInterpolator(translateInterpolater);

    return anim;
  }
  /** Animate items that are deleted entirely and items that move out of bounds. */
  private void animatePreLayout(
      final float durationUnit, final Animator.AnimatorListener listener) {
    final AnimatorSet animatorSet = new AnimatorSet();

    final int firstVisiblePosition = getFirstVisiblePosition();
    final int childCount = getChildCount();

    for (final Iterator<Entry<Long, Float>> iter = yMap.entrySet().iterator(); iter.hasNext(); ) {
      final Entry<Long, Float> entry = iter.next();

      final long id = entry.getKey();
      final int oldPos = positionMap.get(id);
      final View child = getChildAt(oldPos - firstVisiblePosition);
      final int newPos = getPositionForId(id);

      // fade out items that disappear
      if (newPos == -1) {
        final ObjectAnimator anim = animateAlpha(child, false);
        animatorSet.play(anim);

        iter.remove();
        positionMap.remove(id);
        continue;
      }

      // translate items that move out of bounds
      if (newPos < firstVisiblePosition || newPos > firstVisiblePosition + childCount) {
        final float offset;

        if (newPos < firstVisiblePosition) {
          offset = -getHeight();
        } else {
          offset = getHeight();
        }

        final AnimatorProxy proxy = AnimatorProxy.wrap(child);
        final ObjectAnimator anim = ObjectAnimator.ofFloat(proxy, "translationY", 0f, offset);

        final int finalDuration = getDuration(0, getHeight() / 2, durationUnit);

        anim.setInterpolator(new AccelerateInterpolator());
        anim.setDuration((long) (finalDuration * animationDurationFactor));

        animatorSet.addListener(
            new AnimatorListenerAdapter() {
              @Override
              public void onAnimationEnd(final Animator animation) {
                child.post(
                    new Runnable() {

                      @Override
                      public void run() {
                        proxy.setTranslationY(0f);
                      }
                    });
              }
            });
        animatorSet.play(anim);

        iter.remove();
        positionMap.remove(id);
        continue;
      }
    }

    if (!animatorSet.getChildAnimations().isEmpty()) {
      animatorSet.addListener(listener);
      animatorSet.start();
    } else {
      listener.onAnimationEnd(animatorSet);
    }
  }