content: /*
   * Copyright (C) 2017 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package com.android.internal.widget;
  
  import android.annotation.NonNull;
  import android.annotation.Nullable;
  import android.util.Log;
  import android.view.View;
  
  import com.android.internal.widget.RecyclerView.Adapter;
  import com.android.internal.widget.RecyclerView.ViewHolder;
  
  /**
   * A wrapper class for ItemAnimator that records View bounds and decides whether it should run
   * move, change, add or remove animations. This class also replicates the original ItemAnimator
   * API.
   * <p>
   * It uses {@link ItemHolderInfo} to track the bounds information of the Views. If you would like
   * to
   * extend this class, you can override {@link #obtainHolderInfo()} method to provide your own info
   * class that extends {@link ItemHolderInfo}.
   */
  public abstract class SimpleItemAnimator extends RecyclerView.ItemAnimator {
  
      private static final boolean DEBUG = false;
  
      private static final String TAG = "SimpleItemAnimator";
  
      boolean mSupportsChangeAnimations = true;
  
      /**
       * Returns whether this ItemAnimator supports animations of change events.
       *
       * @return true if change animations are supported, false otherwise
       */
      @SuppressWarnings("unused")
      public boolean getSupportsChangeAnimations() {
          return mSupportsChangeAnimations;
      }
  
      /**
       * Sets whether this ItemAnimator supports animations of item change events.
       * If you set this property to false, actions on the data set which change the
       * contents of items will not be animated. What those animations do is left
       * up to the discretion of the ItemAnimator subclass, in its
       * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} implementation.
       * The value of this property is true by default.
       *
       * @param supportsChangeAnimations true if change animations are supported by
       *                                 this ItemAnimator, false otherwise. If the property is false,
       *                                 the ItemAnimator
       *                                 will not receive a call to
       *                                 {@link #animateChange(ViewHolder, ViewHolder, int, int, int,
       *                                 int)} when changes occur.
       * @see Adapter#notifyItemChanged(int)
       * @see Adapter#notifyItemRangeChanged(int, int)
       */
      public void setSupportsChangeAnimations(boolean supportsChangeAnimations) {
          mSupportsChangeAnimations = supportsChangeAnimations;
      }
  
      /**
       * {@inheritDoc}
       *
       * @return True if change animations are not supported or the ViewHolder is invalid,
       * false otherwise.
       *
       * @see #setSupportsChangeAnimations(boolean)
       */
      @Override
      public boolean canReuseUpdatedViewHolder(@NonNull RecyclerView.ViewHolder viewHolder) {
          return !mSupportsChangeAnimations || viewHolder.isInvalid();
      }
  
      @Override
      public boolean animateDisappearance(@NonNull ViewHolder viewHolder,
              @NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo) {
          int oldLeft = preLayoutInfo.left;
          int oldTop = preLayoutInfo.top;
          View disappearingItemView = viewHolder.itemView;
          int newLeft = postLayoutInfo == null ? disappearingItemView.getLeft() : postLayoutInfo.left;
          int newTop = postLayoutInfo == null ? disappearingItemView.getTop() : postLayoutInfo.top;
          if (!viewHolder.isRemoved() && (oldLeft != newLeft || oldTop != newTop)) {
              disappearingItemView.layout(newLeft, newTop,
                      newLeft + disappearingItemView.getWidth(),
                      newTop + disappearingItemView.getHeight());
              if (DEBUG) {
                  Log.d(TAG, "DISAPPEARING: " + viewHolder + " with view " + disappearingItemView);
              }
              return animateMove(viewHolder, oldLeft, oldTop, newLeft, newTop);
          } else {
              if (DEBUG) {
                  Log.d(TAG, "REMOVED: " + viewHolder + " with view " + disappearingItemView);
              }
              return animateRemove(viewHolder);
          }
      }
  
      @Override
      public boolean animateAppearance(@NonNull ViewHolder viewHolder,
              @Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo) {
          if (preLayoutInfo != null && (preLayoutInfo.left != postLayoutInfo.left
                  || preLayoutInfo.top != postLayoutInfo.top)) {
              // slide items in if before/after locations differ
              if (DEBUG) {
                  Log.d(TAG, "APPEARING: " + viewHolder + " with view " + viewHolder);
              }
              return animateMove(viewHolder, preLayoutInfo.left, preLayoutInfo.top,
                      postLayoutInfo.left, postLayoutInfo.top);
          } else {
              if (DEBUG) {
                  Log.d(TAG, "ADDED: " + viewHolder + " with view " + viewHolder);
              }
              return animateAdd(viewHolder);
          }
      }
  
      @Override
      public boolean animatePersistence(@NonNull ViewHolder viewHolder,
              @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) {
          if (preInfo.left != postInfo.left || preInfo.top != postInfo.top) {
              if (DEBUG) {
                  Log.d(TAG, "PERSISTENT: " + viewHolder
                          + " with view " + viewHolder.itemView);
              }
              return animateMove(viewHolder,
                      preInfo.left, preInfo.top, postInfo.left, postInfo.top);
          }
          dispatchMoveFinished(viewHolder);
          return false;
      }
  
      @Override
      public boolean animateChange(@NonNull ViewHolder oldHolder, @NonNull ViewHolder newHolder,
              @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) {
          if (DEBUG) {
              Log.d(TAG, "CHANGED: " + oldHolder + " with view " + oldHolder.itemView);
          }
          final int fromLeft = preInfo.left;
          final int fromTop = preInfo.top;
          final int toLeft, toTop;
          if (newHolder.shouldIgnore()) {
              toLeft = preInfo.left;
              toTop = preInfo.top;
          } else {
              toLeft = postInfo.left;
              toTop = postInfo.top;
          }
          return animateChange(oldHolder, newHolder, fromLeft, fromTop, toLeft, toTop);
      }
  
      /**
       * Called when an item is removed from the RecyclerView. Implementors can choose
       * whether and how to animate that change, but must always call
       * {@link #dispatchRemoveFinished(ViewHolder)} when done, either
       * immediately (if no animation will occur) or after the animation actually finishes.
       * The return value indicates whether an animation has been set up and whether the
       * ItemAnimator's {@link #runPendingAnimations()} method should be called at the
       * next opportunity. This mechanism allows ItemAnimator to set up individual animations
       * as separate calls to {@link #animateAdd(ViewHolder) animateAdd()},
       * {@link #animateMove(ViewHolder, int, int, int, int) animateMove()},
       * {@link #animateRemove(ViewHolder) animateRemove()}, and
       * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} come in one by one,
       * then start the animations together in the later call to {@link #runPendingAnimations()}.
       *
       * <p>This method may also be called for disappearing items which continue to exist in the
       * RecyclerView, but for which the system does not have enough information to animate
       * them out of view. In that case, the default animation for removing items is run
       * on those items as well.</p>
       *
       * @param holder The item that is being removed.
       * @return true if a later call to {@link #runPendingAnimations()} is requested,
       * false otherwise.
       */
      public abstract boolean animateRemove(ViewHolder holder);
  
      /**
       * Called when an item is added to the RecyclerView. Implementors can choose
       * whether and how to animate that change, but must always call
       * {@link #dispatchAddFinished(ViewHolder)} when done, either
       * immediately (if no animation will occur) or after the animation actually finishes.
       * The return value indicates whether an animation has been set up and whether the
       * ItemAnimator's {@link #runPendingAnimations()} method should be called at the
       * next opportunity. This mechanism allows ItemAnimator to set up individual animations
       * as separate calls to {@link #animateAdd(ViewHolder) animateAdd()},
       * {@link #animateMove(ViewHolder, int, int, int, int) animateMove()},
       * {@link #animateRemove(ViewHolder) animateRemove()}, and
       * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} come in one by one,
       * then start the animations together in the later call to {@link #runPendingAnimations()}.
       *
       * <p>This method may also be called for appearing items which were already in the
       * RecyclerView, but for which the system does not have enough information to animate
       * them into view. In that case, the default animation for adding items is run
       * on those items as well.</p>
       *
       * @param holder The item that is being added.
       * @return true if a later call to {@link #runPendingAnimations()} is requested,
       * false otherwise.
       */
      public abstract boolean animateAdd(ViewHolder holder);
  
      /**
       * Called when an item is moved in the RecyclerView. Implementors can choose
       * whether and how to animate that change, but must always call
       * {@link #dispatchMoveFinished(ViewHolder)} when done, either
       * immediately (if no animation will occur) or after the animation actually finishes.
       * The return value indicates whether an animation has been set up and whether the
       * ItemAnimator's {@link #runPendingAnimations()} method should be called at the
       * next opportunity. This mechanism allows ItemAnimator to set up individual animations
       * as separate calls to {@link #animateAdd(ViewHolder) animateAdd()},
       * {@link #animateMove(ViewHolder, int, int, int, int) animateMove()},
       * {@link #animateRemove(ViewHolder) animateRemove()}, and
       * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} come in one by one,
       * then start the animations together in the later call to {@link #runPendingAnimations()}.
       *
       * @param holder The item that is being moved.
       * @return true if a later call to {@link #runPendingAnimations()} is requested,
       * false otherwise.
       */
      public abstract boolean animateMove(ViewHolder holder, int fromX, int fromY,
              int toX, int toY);
  
      /**
       * Called when an item is changed in the RecyclerView, as indicated by a call to
       * {@link Adapter#notifyItemChanged(int)} or
       * {@link Adapter#notifyItemRangeChanged(int, int)}.
       * <p>
       * Implementers can choose whether and how to animate changes, but must always call
       * {@link #dispatchChangeFinished(ViewHolder, boolean)} for each non-null distinct ViewHolder,
       * either immediately (if no animation will occur) or after the animation actually finishes.
       * If the {@code oldHolder} is the same ViewHolder as the {@code newHolder}, you must call
       * {@link #dispatchChangeFinished(ViewHolder, boolean)} once and only once. In that case, the
       * second parameter of {@code dispatchChangeFinished} is ignored.
       * <p>
       * The return value indicates whether an animation has been set up and whether the
       * ItemAnimator's {@link #runPendingAnimations()} method should be called at the
       * next opportunity. This mechanism allows ItemAnimator to set up individual animations
       * as separate calls to {@link #animateAdd(ViewHolder) animateAdd()},
       * {@link #animateMove(ViewHolder, int, int, int, int) animateMove()},
       * {@link #animateRemove(ViewHolder) animateRemove()}, and
       * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} come in one by one,
       * then start the animations together in the later call to {@link #runPendingAnimations()}.
       *
       * @param oldHolder The original item that changed.
       * @param newHolder The new item that was created with the changed content. Might be null
       * @param fromLeft  Left of the old view holder
       * @param fromTop   Top of the old view holder
       * @param toLeft    Left of the new view holder
       * @param toTop     Top of the new view holder
       * @return true if a later call to {@link #runPendingAnimations()} is requested,
       * false otherwise.
       */
      public abstract boolean animateChange(ViewHolder oldHolder,
              ViewHolder newHolder, int fromLeft, int fromTop, int toLeft, int toTop);
  
      /**
       * Method to be called by subclasses when a remove animation is done.
       *
       * @param item The item which has been removed
       * @see RecyclerView.ItemAnimator#animateDisappearance(ViewHolder, ItemHolderInfo,
       * ItemHolderInfo)
       */
      public final void dispatchRemoveFinished(ViewHolder item) {
          onRemoveFinished(item);
          dispatchAnimationFinished(item);
      }
  
      /**
       * Method to be called by subclasses when a move animation is done.
       *
       * @param item The item which has been moved
       * @see RecyclerView.ItemAnimator#animateDisappearance(ViewHolder, ItemHolderInfo,
       * ItemHolderInfo)
       * @see RecyclerView.ItemAnimator#animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo)
       * @see RecyclerView.ItemAnimator#animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)
       */
      public final void dispatchMoveFinished(ViewHolder item) {
          onMoveFinished(item);
          dispatchAnimationFinished(item);
      }
  
      /**
       * Method to be called by subclasses when an add animation is done.
       *
       * @param item The item which has been added
       */
      public final void dispatchAddFinished(ViewHolder item) {
          onAddFinished(item);
          dispatchAnimationFinished(item);
      }
  
      /**
       * Method to be called by subclasses when a change animation is done.
       *
       * @param item    The item which has been changed (this method must be called for
       *                each non-null ViewHolder passed into
       *                {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)}).
       * @param oldItem true if this is the old item that was changed, false if
       *                it is the new item that replaced the old item.
       * @see #animateChange(ViewHolder, ViewHolder, int, int, int, int)
       */
      public final void dispatchChangeFinished(ViewHolder item, boolean oldItem) {
          onChangeFinished(item, oldItem);
          dispatchAnimationFinished(item);
      }
  
      /**
       * Method to be called by subclasses when a remove animation is being started.
       *
       * @param item The item being removed
       */
      public final void dispatchRemoveStarting(ViewHolder item) {
          onRemoveStarting(item);
      }
  
      /**
       * Method to be called by subclasses when a move animation is being started.
       *
       * @param item The item being moved
       */
      public final void dispatchMoveStarting(ViewHolder item) {
          onMoveStarting(item);
      }
  
      /**
       * Method to be called by subclasses when an add animation is being started.
       *
       * @param item The item being added
       */
      public final void dispatchAddStarting(ViewHolder item) {
          onAddStarting(item);
      }
  
      /**
       * Method to be called by subclasses when a change animation is being started.
       *
       * @param item    The item which has been changed (this method must be called for
       *                each non-null ViewHolder passed into
       *                {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)}).
       * @param oldItem true if this is the old item that was changed, false if
       *                it is the new item that replaced the old item.
       */
      public final void dispatchChangeStarting(ViewHolder item, boolean oldItem) {
          onChangeStarting(item, oldItem);
      }
  
      /**
       * Called when a remove animation is being started on the given ViewHolder.
       * The default implementation does nothing. Subclasses may wish to override
       * this method to handle any ViewHolder-specific operations linked to animation
       * lifecycles.
       *
       * @param item The ViewHolder being animated.
       */
      @SuppressWarnings("UnusedParameters")
      public void onRemoveStarting(ViewHolder item) {
      }
  
      /**
       * Called when a remove animation has ended on the given ViewHolder.
       * The default implementation does nothing. Subclasses may wish to override
       * this method to handle any ViewHolder-specific operations linked to animation
       * lifecycles.
       *
       * @param item The ViewHolder being animated.
       */
      public void onRemoveFinished(ViewHolder item) {
      }
  
      /**
       * Called when an add animation is being started on the given ViewHolder.
       * The default implementation does nothing. Subclasses may wish to override
       * this method to handle any ViewHolder-specific operations linked to animation
       * lifecycles.
       *
       * @param item The ViewHolder being animated.
       */
      @SuppressWarnings("UnusedParameters")
      public void onAddStarting(ViewHolder item) {
      }
  
      /**
       * Called when an add animation has ended on the given ViewHolder.
       * The default implementation does nothing. Subclasses may wish to override
       * this method to handle any ViewHolder-specific operations linked to animation
       * lifecycles.
       *
       * @param item The ViewHolder being animated.
       */
      public void onAddFinished(ViewHolder item) {
      }
  
      /**
       * Called when a move animation is being started on the given ViewHolder.
       * The default implementation does nothing. Subclasses may wish to override
       * this method to handle any ViewHolder-specific operations linked to animation
       * lifecycles.
       *
       * @param item The ViewHolder being animated.
       */
      @SuppressWarnings("UnusedParameters")
      public void onMoveStarting(ViewHolder item) {
      }
  
      /**
       * Called when a move animation has ended on the given ViewHolder.
       * The default implementation does nothing. Subclasses may wish to override
       * this method to handle any ViewHolder-specific operations linked to animation
       * lifecycles.
       *
       * @param item The ViewHolder being animated.
       */
      public void onMoveFinished(ViewHolder item) {
      }
  
      /**
       * Called when a change animation is being started on the given ViewHolder.
       * The default implementation does nothing. Subclasses may wish to override
       * this method to handle any ViewHolder-specific operations linked to animation
       * lifecycles.
       *
       * @param item    The ViewHolder being animated.
       * @param oldItem true if this is the old item that was changed, false if
       *                it is the new item that replaced the old item.
       */
      @SuppressWarnings("UnusedParameters")
      public void onChangeStarting(ViewHolder item, boolean oldItem) {
      }
  
      /**
       * Called when a change animation has ended on the given ViewHolder.
       * The default implementation does nothing. Subclasses may wish to override
       * this method to handle any ViewHolder-specific operations linked to animation
       * lifecycles.
       *
       * @param item    The ViewHolder being animated.
       * @param oldItem true if this is the old item that was changed, false if
       *                it is the new item that replaced the old item.
       */
      public void onChangeFinished(ViewHolder item, boolean oldItem) {
      }
  }
  
  