new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/widget/DefaultItemAnimatorBase.java
@@ -0,0 +1,685 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.widget;
+
+import android.support.annotation.NonNull;
+import android.support.v4.animation.AnimatorCompatHelper;
+import android.support.v4.view.ViewCompat;
+import android.support.v4.view.ViewPropertyAnimatorCompat;
+import android.support.v4.view.ViewPropertyAnimatorListener;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.SimpleItemAnimator;
+import android.view.View;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This basically follows the approach taken by Wasabeef:
+ * <a href="https://github.com/wasabeef/recyclerview-animators">https://github.com/wasabeef/recyclerview-animators</a>
+ * based off of Android's DefaultItemAnimator from October 2016:
+ * <a href="https://github.com/android/platform_frameworks_support/blob/432f3317f8a9b8cf98277938ea5df4021e983055/v7/recyclerview/src/android/support/v7/widget/DefaultItemAnimator.java">
+ * https://github.com/android/platform_frameworks_support/blob/432f3317f8a9b8cf98277938ea5df4021e983055/v7/recyclerview/src/android/support/v7/widget/DefaultItemAnimator.java
+ * </a>
+ * <p>
+ * Usage Notes:
+ * </p>
+ * <ul>
+ * <li>You <strong>must</strong> add a Default*VpaListener to your animate*Impl animation - the
+ * listener takes care of animation bookkeeping.</li>
+ * <li>You should call {@link #resetAnimation(RecyclerView.ViewHolder)} at some point in
+ * preAnimate*Impl if you choose to proceed with the animation. Some animations will want to
+ * know some or all of the current animation values for initializing their own animation
+ * values before resetting the current animation, so this class does not provide the reset
+ * service itself.</li>
+ * <li>{@link #resetViewProperties(View)} is used to reset a view any time an animation ends or
+ * gets canceled - you should redefine resetViewProperties if the version here doesn't reset
+ * all of the properties you're animating.</li>
+ * </ul>
+ */
+public class DefaultItemAnimatorBase extends SimpleItemAnimator {
+ private List<RecyclerView.ViewHolder> pendingRemovals = new ArrayList<>();
+ private List<RecyclerView.ViewHolder> pendingAdditions = new ArrayList<>();
+ private List<MoveInfo> pendingMoves = new ArrayList<>();
+ private List<ChangeInfo> pendingChanges = new ArrayList<>();
+
+ private List<List<RecyclerView.ViewHolder>> additionsList = new ArrayList<>();
+ private List<List<MoveInfo>> movesList = new ArrayList<>();
+ private List<List<ChangeInfo>> changesList = new ArrayList<>();
+
+ private List<RecyclerView.ViewHolder> addAnimations = new ArrayList<>();
+ private List<RecyclerView.ViewHolder> moveAnimations = new ArrayList<>();
+ private List<RecyclerView.ViewHolder> removeAnimations = new ArrayList<>();
+ private List<RecyclerView.ViewHolder> changeAnimations = new ArrayList<>();
+
+ protected static class MoveInfo {
+ public RecyclerView.ViewHolder holder;
+ public int fromX, fromY, toX, toY;
+
+ public MoveInfo(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;
+ }
+ }
+
+ protected static class ChangeInfo {
+ public RecyclerView.ViewHolder oldHolder, newHolder;
+ public int fromX, fromY, toX, toY;
+
+ public ChangeInfo(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder) {
+ this.oldHolder = oldHolder;
+ this.newHolder = newHolder;
+ }
+
+ public ChangeInfo(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder,
+ int fromX, int fromY, int toX, int toY) {
+ this(oldHolder, newHolder);
+ this.fromX = fromX;
+ this.fromY = fromY;
+ this.toX = toX;
+ this.toY = toY;
+ }
+
+ @Override
+ public String toString() {
+ return "ChangeInfo{" +
+ "oldHolder=" + oldHolder +
+ ", newHolder=" + newHolder +
+ ", fromX=" + fromX +
+ ", fromY=" + fromY +
+ ", toX=" + toX +
+ ", toY=" + toY +
+ '}';
+ }
+ }
+
+ @Override
+ public void runPendingAnimations() {
+ final boolean removalsPending = !pendingRemovals.isEmpty();
+ final boolean movesPending = !pendingMoves.isEmpty();
+ final boolean changesPending = !pendingChanges.isEmpty();
+ final boolean additionsPending = !pendingAdditions.isEmpty();
+ if (!removalsPending && !movesPending && !additionsPending && !changesPending) {
+ return;
+ }
+ // First, remove stuff.
+ for (final RecyclerView.ViewHolder holder : pendingRemovals) {
+ animateRemoveImpl(holder);
+ }
+ pendingRemovals.clear();
+ // Next, move stuff.
+ if (movesPending) {
+ final List<MoveInfo> moves = new ArrayList<>();
+ moves.addAll(pendingMoves);
+ movesList.add(moves);
+ pendingMoves.clear();
+ final Runnable mover = new Runnable() {
+ @Override
+ public void run() {
+ for (final MoveInfo moveInfo : moves) {
+ animateMoveImpl(moveInfo.holder, moveInfo.fromX, moveInfo.fromY,
+ moveInfo.toX, moveInfo.toY);
+ }
+ moves.clear();
+ movesList.remove(moves);
+ }
+ };
+ if (removalsPending) {
+ final View view = moves.get(0).holder.itemView;
+ ViewCompat.postOnAnimationDelayed(view, mover, getRemoveDuration());
+ } else {
+ mover.run();
+ }
+ }
+ // Next, change stuff, to run in parallel with move animations.
+ if (changesPending) {
+ final List<ChangeInfo> changes = new ArrayList<>();
+ changes.addAll(pendingChanges);
+ changesList.add(changes);
+ pendingChanges.clear();
+ final Runnable changer = new Runnable() {
+ @Override
+ public void run() {
+ for (final ChangeInfo change : changes) {
+ animateChangeImpl(change);
+ }
+ changes.clear();
+ changesList.remove(changes);
+ }
+ };
+ if (removalsPending) {
+ RecyclerView.ViewHolder holder = changes.get(0).oldHolder;
+ ViewCompat.postOnAnimationDelayed(holder.itemView, changer, getRemoveDuration());
+ } else {
+ changer.run();
+ }
+ }
+ // Next, add stuff.
+ if (additionsPending) {
+ final List<RecyclerView.ViewHolder> additions = new ArrayList<>();
+ additions.addAll(pendingAdditions);
+ additionsList.add(additions);
+ pendingAdditions.clear();
+ final Runnable adder = new Runnable() {
+ public void run() {
+ for (final RecyclerView.ViewHolder holder : additions) {
+ animateAddImpl(holder);
+ }
+ additions.clear();
+ additionsList.remove(additions);
+ }
+ };
+ if (removalsPending || movesPending || changesPending) {
+ final long removeDuration = removalsPending ? getRemoveDuration() : 0;
+ final long moveDuration = movesPending ? getMoveDuration() : 0;
+ final long changeDuration = changesPending ? getChangeDuration() : 0;
+ final long totalDelay = removeDuration + Math.max(moveDuration, changeDuration);
+ final View view = additions.get(0).itemView;
+ ViewCompat.postOnAnimationDelayed(view, adder, totalDelay);
+ } else {
+ adder.run();
+ }
+ }
+ }
+
+ @Override
+ public boolean animateRemove(final RecyclerView.ViewHolder holder) {
+ if (!preAnimateRemoveImpl(holder)) {
+ dispatchRemoveFinished(holder);
+ return false;
+ }
+ pendingRemovals.add(holder);
+ return true;
+ }
+
+ protected boolean preAnimateRemoveImpl(final RecyclerView.ViewHolder holder) {
+ resetAnimation(holder);
+ return true;
+ }
+
+ protected void animateRemoveImpl(final RecyclerView.ViewHolder holder) {
+ ViewCompat.animate(holder.itemView)
+ .setDuration(getRemoveDuration())
+ .alpha(0)
+ .setListener(new DefaultRemoveVpaListener(holder))
+ .start();
+ }
+
+ @Override
+ public boolean animateAdd(final RecyclerView.ViewHolder holder) {
+ if (!preAnimateAddImpl(holder)) {
+ dispatchAddFinished(holder);
+ return false;
+ }
+ pendingAdditions.add(holder);
+ return true;
+ }
+
+ protected boolean preAnimateAddImpl(RecyclerView.ViewHolder holder) {
+ resetAnimation(holder);
+ holder.itemView.setAlpha(0);
+ return true;
+ }
+
+ protected void animateAddImpl(final RecyclerView.ViewHolder holder) {
+ ViewCompat.animate(holder.itemView)
+ .setDuration(getAddDuration())
+ .alpha(1)
+ .setListener(new DefaultAddVpaListener(holder))
+ .start();
+ }
+
+ @Override
+ public boolean animateMove(final RecyclerView.ViewHolder holder,
+ int fromX, int fromY, int toX, int toY) {
+ final View view = holder.itemView;
+ fromX += ViewCompat.getTranslationX(holder.itemView);
+ fromY += ViewCompat.getTranslationY(holder.itemView);
+ final int deltaX = toX - fromX;
+ final int deltaY = toY - fromY;
+ if (deltaX == 0 && deltaY == 0) {
+ dispatchMoveFinished(holder);
+ return false;
+ }
+ resetAnimation(holder);
+ if (deltaX != 0) {
+ ViewCompat.setTranslationX(view, -deltaX);
+ }
+ if (deltaY != 0) {
+ ViewCompat.setTranslationY(view, -deltaY);
+ }
+ pendingMoves.add(new MoveInfo(holder, fromX, fromY, toX, toY));
+ return true;
+ }
+
+ protected void animateMoveImpl(final RecyclerView.ViewHolder holder,
+ int fromX, int fromY, int toX, int toY) {
+ final View view = holder.itemView;
+ final int deltaX = toX - fromX;
+ final int deltaY = toY - fromY;
+ if (deltaX != 0) {
+ ViewCompat.animate(view).translationX(0);
+ }
+ if (deltaY != 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.
+ final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view);
+ moveAnimations.add(holder);
+ animation.setDuration(getMoveDuration()).setListener(new VpaListenerAdapter() {
+ @Override
+ public void onAnimationStart(View view) {
+ dispatchMoveStarting(holder);
+ }
+ @Override
+ public void onAnimationCancel(View view) {
+ resetViewProperties(view);
+ }
+ @Override
+ public void onAnimationEnd(View view) {
+ animation.setListener(null);
+ dispatchMoveFinished(holder);
+ moveAnimations.remove(holder);
+ dispatchFinishedWhenDone();
+ }
+ }).start();
+ }
+
+ @Override
+ public boolean animateChange(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder,
+ int fromX, int fromY, int toX, int toY) {
+ if (oldHolder == newHolder) {
+ // Don't know how to run change animations when the same view holder is re-used.
+ // Run a move animation to handle position changes (if there are any).
+ if (fromX != toX || fromY != toY) {
+ // *Don't* call dispatchChangeFinished here, it leads to unbalanced isRecyclable calls.
+ return animateMove(oldHolder, fromX, fromY, toX, toY);
+ }
+ dispatchChangeFinished(oldHolder, true);
+ return false;
+ }
+ final float prevTranslationX = ViewCompat.getTranslationX(oldHolder.itemView);
+ final float prevTranslationY = ViewCompat.getTranslationY(oldHolder.itemView);
+ final float prevAlpha = ViewCompat.getAlpha(oldHolder.itemView);
+ resetAnimation(oldHolder);
+ final int deltaX = (int) (toX - fromX - prevTranslationX);
+ final int deltaY = (int) (toY - fromY - prevTranslationY);
+ // Recover previous translation state after ending animation.
+ ViewCompat.setTranslationX(oldHolder.itemView, prevTranslationX);
+ ViewCompat.setTranslationY(oldHolder.itemView, prevTranslationY);
+ ViewCompat.setAlpha(oldHolder.itemView, prevAlpha);
+ if (newHolder != null) {
+ // Carry over translation values.
+ resetAnimation(newHolder);
+ ViewCompat.setTranslationX(newHolder.itemView, -deltaX);
+ ViewCompat.setTranslationY(newHolder.itemView, -deltaY);
+ ViewCompat.setAlpha(newHolder.itemView, 0);
+ }
+ pendingChanges.add(new ChangeInfo(oldHolder, newHolder, fromX, fromY, toX, toY));
+ return true;
+ }
+
+ protected void animateChangeImpl(final ChangeInfo changeInfo) {
+ final RecyclerView.ViewHolder holder = changeInfo.oldHolder;
+ final View view = holder == null ? null : holder.itemView;
+ final RecyclerView.ViewHolder newHolder = changeInfo.newHolder;
+ final View newView = newHolder != null ? newHolder.itemView : null;
+ if (view != null) {
+ final ViewPropertyAnimatorCompat oldViewAnim = ViewCompat.animate(view).setDuration(
+ getChangeDuration());
+ changeAnimations.add(changeInfo.oldHolder);
+ oldViewAnim.translationX(changeInfo.toX - changeInfo.fromX);
+ oldViewAnim.translationY(changeInfo.toY - changeInfo.fromY);
+ oldViewAnim.alpha(0).setListener(new VpaListenerAdapter() {
+ @Override
+ public void onAnimationStart(View view) {
+ dispatchChangeStarting(changeInfo.oldHolder, true);
+ }
+
+ @Override
+ public void onAnimationEnd(View view) {
+ oldViewAnim.setListener(null);
+ resetViewProperties(view);
+ dispatchChangeFinished(changeInfo.oldHolder, true);
+ changeAnimations.remove(changeInfo.oldHolder);
+ dispatchFinishedWhenDone();
+ }
+ }).start();
+ }
+ if (newView != null) {
+ final ViewPropertyAnimatorCompat newViewAnimation = ViewCompat.animate(newView);
+ changeAnimations.add(changeInfo.newHolder);
+ newViewAnimation.translationX(0).translationY(0).setDuration(getChangeDuration()).
+ alpha(1).setListener(new VpaListenerAdapter() {
+ @Override
+ public void onAnimationStart(View view) {
+ dispatchChangeStarting(changeInfo.newHolder, false);
+ }
+ @Override
+ public void onAnimationEnd(View view) {
+ newViewAnimation.setListener(null);
+ resetViewProperties(view);
+ dispatchChangeFinished(changeInfo.newHolder, false);
+ changeAnimations.remove(changeInfo.newHolder);
+ dispatchFinishedWhenDone();
+ }
+ }).start();
+}
+ }
+
+ private void endChangeAnimation(List<ChangeInfo> infoList, RecyclerView.ViewHolder item) {
+ for (int i = infoList.size() - 1; i >= 0; i--) {
+ final ChangeInfo changeInfo = infoList.get(i);
+ if (endChangeAnimationIfNecessary(changeInfo, item)) {
+ if (changeInfo.oldHolder == null && changeInfo.newHolder == null) {
+ infoList.remove(changeInfo);
+ }
+ }
+ }
+ }
+
+ private void endChangeAnimationIfNecessary(ChangeInfo changeInfo) {
+ if (changeInfo.oldHolder != null) {
+ endChangeAnimationIfNecessary(changeInfo, changeInfo.oldHolder);
+ }
+ if (changeInfo.newHolder != null) {
+ endChangeAnimationIfNecessary(changeInfo, changeInfo.newHolder);
+ }
+ }
+
+ private boolean endChangeAnimationIfNecessary(ChangeInfo changeInfo, RecyclerView.ViewHolder item) {
+ boolean oldItem = false;
+ if (changeInfo.newHolder == item) {
+ changeInfo.newHolder = null;
+ } else if (changeInfo.oldHolder == item) {
+ changeInfo.oldHolder = null;
+ oldItem = true;
+ } else {
+ return false;
+ }
+ resetViewProperties(item.itemView);
+ dispatchChangeFinished(item, oldItem);
+ return true;
+ }
+
+ /**
+ * Called to reset all properties possibly animated by any and all defined animations.
+ */
+ protected void resetViewProperties(View view) {
+ view.setTranslationX(0);
+ view.setTranslationY(0);
+ view.setAlpha(1);
+ }
+
+ @Override
+ public void endAnimation(RecyclerView.ViewHolder item) {
+
+ final View view = item.itemView;
+ // This calls dispatch*Finished, resets view properties, and removes item from current
+ // animations list if the view is currently being animated.
+ ViewCompat.animate(view).cancel();
+ // TODO if some other animations are chained to end, how do we cancel them as well?
+ for (int i = pendingMoves.size() - 1; i >= 0; i--) {
+ final MoveInfo moveInfo = pendingMoves.get(i);
+ if (moveInfo.holder == item) {
+ resetViewProperties(view);
+ dispatchMoveFinished(item);
+ pendingMoves.remove(i);
+ }
+ }
+ endChangeAnimation(pendingChanges, item);
+ if (pendingRemovals.remove(item)) {
+ resetViewProperties(view);
+ dispatchRemoveFinished(item);
+ }
+ if (pendingAdditions.remove(item)) {
+ resetViewProperties(view);
+ dispatchAddFinished(item);
+ }
+
+ for (int i = changesList.size() - 1; i >= 0; i--) {
+ final List<ChangeInfo> changes = changesList.get(i);
+ endChangeAnimation(changes, item);
+ if (changes.isEmpty()) {
+ changesList.remove(i);
+ }
+ }
+ for (int i = movesList.size() - 1; i >= 0; i--) {
+ final List<MoveInfo> moves = movesList.get(i);
+ for (int j = moves.size() - 1; j >= 0; j--) {
+ final MoveInfo moveInfo = moves.get(j);
+ if (moveInfo.holder == item) {
+ resetViewProperties(view);
+ dispatchMoveFinished(item);
+ moves.remove(j);
+ if (moves.isEmpty()) {
+ movesList.remove(i);
+ }
+ break;
+ }
+ }
+ }
+ for (int i = additionsList.size() - 1; i >= 0; i--) {
+ final List<RecyclerView.ViewHolder> additions = additionsList.get(i);
+ if (additions.remove(item)) {
+ resetViewProperties(view);
+ dispatchAddFinished(item);
+ if (additions.isEmpty()) {
+ additionsList.remove(i);
+ }
+ }
+ }
+ dispatchFinishedWhenDone();
+ }
+
+ protected void resetAnimation(RecyclerView.ViewHolder holder) {
+ AnimatorCompatHelper.clearInterpolator(holder.itemView);
+ endAnimation(holder);
+ }
+
+ @Override
+ public boolean isRunning() {
+ return (!pendingAdditions.isEmpty() ||
+ !pendingChanges.isEmpty() ||
+ !pendingMoves.isEmpty() ||
+ !pendingRemovals.isEmpty() ||
+ !moveAnimations.isEmpty() ||
+ !removeAnimations.isEmpty() ||
+ !addAnimations.isEmpty() ||
+ !changeAnimations.isEmpty() ||
+ !movesList.isEmpty() ||
+ !additionsList.isEmpty() ||
+ !changesList.isEmpty());
+ }
+
+ /**
+ * 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();
+ }
+ }
+
+ @Override
+ public void endAnimations() {
+ int count = pendingMoves.size();
+ for (int i = count - 1; i >= 0; i--) {
+ final MoveInfo item = pendingMoves.get(i);
+ resetViewProperties(item.holder.itemView);
+ dispatchMoveFinished(item.holder);
+ pendingMoves.remove(i);
+ }
+ count = pendingRemovals.size();
+ for (int i = count - 1; i >= 0; i--) {
+ final RecyclerView.ViewHolder item = pendingRemovals.get(i);
+ resetViewProperties(item.itemView);
+ dispatchRemoveFinished(item);
+ pendingRemovals.remove(i);
+ }
+ count = pendingAdditions.size();
+ for (int i = count - 1; i >= 0; i--) {
+ final RecyclerView.ViewHolder item = pendingAdditions.get(i);
+ resetViewProperties(item.itemView);
+ dispatchAddFinished(item);
+ pendingAdditions.remove(i);
+ }
+ count = pendingChanges.size();
+ for (int i = count - 1; i >= 0; i--) {
+ endChangeAnimationIfNecessary(pendingChanges.get(i));
+ }
+ pendingChanges.clear();
+ if (!isRunning()) {
+ return;
+ }
+
+ int listCount = movesList.size();
+ for (int i = listCount - 1; i >= 0; i--) {
+ final List<MoveInfo> moves = movesList.get(i);
+ count = moves.size();
+ for (int j = count - 1; j >= 0; j--) {
+ final MoveInfo moveInfo = moves.get(j);
+ final RecyclerView.ViewHolder item = moveInfo.holder;
+ resetViewProperties(item.itemView);
+ dispatchMoveFinished(item);
+ moves.remove(j);
+ if (moves.isEmpty()) {
+ movesList.remove(moves);
+ }
+ }
+ }
+ listCount = additionsList.size();
+ for (int i = listCount - 1; i >= 0; i--) {
+ final List<RecyclerView.ViewHolder> additions = additionsList.get(i);
+ count = additions.size();
+ for (int j = count - 1; j >= 0; j--) {
+ final RecyclerView.ViewHolder item = additions.get(j);
+ resetViewProperties(item.itemView);
+ dispatchAddFinished(item);
+ additions.remove(j);
+ if (additions.isEmpty()) {
+ additionsList.remove(additions);
+ }
+ }
+ }
+ listCount = changesList.size();
+ for (int i = listCount - 1; i >= 0; i--) {
+ final List<ChangeInfo> changes = changesList.get(i);
+ count = changes.size();
+ for (int j = count - 1; j >= 0; j--) {
+ endChangeAnimationIfNecessary(changes.get(j));
+ if (changes.isEmpty()) {
+ changesList.remove(changes);
+ }
+ }
+ }
+
+ cancelAll(removeAnimations);
+ cancelAll(moveAnimations);
+ cancelAll(addAnimations);
+ cancelAll(changeAnimations);
+
+ dispatchAnimationsFinished();
+ }
+
+ public void cancelAll(List<RecyclerView.ViewHolder> viewHolders) {
+ for (int i = viewHolders.size() - 1; i >= 0; i--) {
+ ViewCompat.animate(viewHolders.get(i).itemView).cancel();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * If the payload list is not empty, DefaultItemAnimator returns <code>true</code>.
+ * When this is the case:
+ * <ul>
+ * <li>If you override
+ * {@link #animateChange(RecyclerView.ViewHolder, RecyclerView.ViewHolder, int, int, int, int)},
+ * both ViewHolder arguments will be the same instance.
+ * </li>
+ * <li>
+ * If you are not overriding
+ * {@link #animateChange(RecyclerView.ViewHolder, RecyclerView.ViewHolder, int, int, int, int)},
+ * then DefaultItemAnimator will call
+ * {@link #animateMove(RecyclerView.ViewHolder, int, int, int, int)} and run a move animation
+ * instead.
+ * </li>
+ * </ul>
+ */
+ @Override
+ public boolean canReuseUpdatedViewHolder(@NonNull RecyclerView.ViewHolder viewHolder,
+ @NonNull List<Object> payloads) {
+ return !payloads.isEmpty() || super.canReuseUpdatedViewHolder(viewHolder, payloads);
+ }
+
+ private class VpaListenerAdapter implements ViewPropertyAnimatorListener {
+ @Override
+ public void onAnimationStart(View view) {}
+
+ // Note that onAnimationEnd is called (in addition to OnAnimationCancel) whenever an
+ // animation is canceled.
+ @Override
+ public void onAnimationEnd(View view) {
+ resetViewProperties(view);
+ view.animate().setListener(null);
+ }
+
+ @Override
+ public void onAnimationCancel(View view) {}
+ }
+
+ protected class DefaultRemoveVpaListener extends VpaListenerAdapter {
+ private final RecyclerView.ViewHolder viewHolder;
+
+ public DefaultRemoveVpaListener(final RecyclerView.ViewHolder holder) {
+ viewHolder = holder;
+ }
+
+ @Override
+ public void onAnimationStart(View view) {
+ removeAnimations.add(viewHolder);
+ dispatchRemoveStarting(viewHolder);
+ }
+
+ @Override
+ public void onAnimationEnd(View view) {
+ removeAnimations.remove(viewHolder);
+ dispatchRemoveFinished(viewHolder);
+ dispatchFinishedWhenDone();
+ super.onAnimationEnd(view);
+ }
+ }
+
+ protected class DefaultAddVpaListener extends VpaListenerAdapter {
+ private final RecyclerView.ViewHolder viewHolder;
+
+ public DefaultAddVpaListener(final RecyclerView.ViewHolder holder) {
+ viewHolder = holder;
+ }
+
+ @Override
+ public void onAnimationStart(View view) {
+ addAnimations.add(viewHolder);
+ dispatchAddStarting(viewHolder);
+ }
+
+ @Override
+ public void onAnimationEnd(View view) {
+ addAnimations.remove(viewHolder);
+ dispatchAddFinished(viewHolder);
+ dispatchFinishedWhenDone();
+ super.onAnimationEnd(view);
+ }
+ }
+}
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -732,16 +732,17 @@ gbjar.sources += ['java/org/mozilla/geck
'widget/AnimatedHeightLayout.java',
'widget/BasicColorPicker.java',
'widget/CheckableLinearLayout.java',
'widget/ClickableWhenDisabledEditText.java',
'widget/ContentSecurityDoorHanger.java',
'widget/CropImageView.java',
'widget/DateTimePicker.java',
'widget/DefaultDoorHanger.java',
+ 'widget/DefaultItemAnimatorBase.java',
'widget/DoorHanger.java',
'widget/DoorhangerConfig.java',
'widget/EllipsisTextView.java',
'widget/ExternalIntentDuringPrivateBrowsingPromptFragment.java',
'widget/FadedMultiColorTextView.java',
'widget/FadedSingleColorTextView.java',
'widget/FadedTextView.java',
'widget/FaviconView.java',