content: /*
   * Copyright (C) 2011 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 android.view;
  
  import android.animation.Animator;
  import android.animation.TimeInterpolator;
  import android.animation.ValueAnimator;
  import android.graphics.RenderNode;
  
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.Set;
  
  /**
   * This class enables automatic and optimized animation of select properties on View objects.
   * If only one or two properties on a View object are being animated, then using an
   * {@link android.animation.ObjectAnimator} is fine; the property setters called by ObjectAnimator
   * are well equipped to do the right thing to set the property and invalidate the view
   * appropriately. But if several properties are animated simultaneously, or if you just want a
   * more convenient syntax to animate a specific property, then ViewPropertyAnimator might be
   * more well-suited to the task.
   *
   * <p>This class may provide better performance for several simultaneous animations, because
   * it will optimize invalidate calls to take place only once for several properties instead of each
   * animated property independently causing its own invalidation. Also, the syntax of using this
   * class could be easier to use because the caller need only tell the View object which
   * property to animate, and the value to animate either to or by, and this class handles the
   * details of configuring the underlying Animator class and starting it.</p>
   *
   * <p>This class is not constructed by the caller, but rather by the View whose properties
   * it will animate. Calls to {@link android.view.View#animate()} will return a reference
   * to the appropriate ViewPropertyAnimator object for that View.</p>
   *
   */
  public class ViewPropertyAnimator {
  
      /**
       * The View whose properties are being animated by this class. This is set at
       * construction time.
       */
      final View mView;
  
      /**
       * The duration of the underlying Animator object. By default, we don't set the duration
       * on the Animator and just use its default duration. If the duration is ever set on this
       * Animator, then we use the duration that it was set to.
       */
      private long mDuration;
  
      /**
       * A flag indicating whether the duration has been set on this object. If not, we don't set
       * the duration on the underlying Animator, but instead just use its default duration.
       */
      private boolean mDurationSet = false;
  
      /**
       * The startDelay of the underlying Animator object. By default, we don't set the startDelay
       * on the Animator and just use its default startDelay. If the startDelay is ever set on this
       * Animator, then we use the startDelay that it was set to.
       */
      private long mStartDelay = 0;
  
      /**
       * A flag indicating whether the startDelay has been set on this object. If not, we don't set
       * the startDelay on the underlying Animator, but instead just use its default startDelay.
       */
      private boolean mStartDelaySet = false;
  
      /**
       * The interpolator of the underlying Animator object. By default, we don't set the interpolator
       * on the Animator and just use its default interpolator. If the interpolator is ever set on
       * this Animator, then we use the interpolator that it was set to.
       */
      private TimeInterpolator mInterpolator;
  
      /**
       * A flag indicating whether the interpolator has been set on this object. If not, we don't set
       * the interpolator on the underlying Animator, but instead just use its default interpolator.
       */
      private boolean mInterpolatorSet = false;
  
      /**
       * Listener for the lifecycle events of the underlying ValueAnimator object.
       */
      private Animator.AnimatorListener mListener = null;
  
      /**
       * Listener for the update events of the underlying ValueAnimator object.
       */
      private ValueAnimator.AnimatorUpdateListener mUpdateListener = null;
  
      /**
       * A lazily-created ValueAnimator used in order to get some default animator properties
       * (duration, start delay, interpolator, etc.).
       */
      private ValueAnimator mTempValueAnimator;
  
      /**
       * This listener is the mechanism by which the underlying Animator causes changes to the
       * properties currently being animated, as well as the cleanup after an animation is
       * complete.
       */
      private AnimatorEventListener mAnimatorEventListener = new AnimatorEventListener();
  
      /**
       * This list holds the properties that have been asked to animate. We allow the caller to
       * request several animations prior to actually starting the underlying animator. This
       * enables us to run one single animator to handle several properties in parallel. Each
       * property is tossed onto the pending list until the animation actually starts (which is
       * done by posting it onto mView), at which time the pending list is cleared and the properties
       * on that list are added to the list of properties associated with that animator.
       */
      ArrayList<NameValuesHolder> mPendingAnimations = new ArrayList<NameValuesHolder>();
      private Runnable mPendingSetupAction;
      private Runnable mPendingCleanupAction;
      private Runnable mPendingOnStartAction;
      private Runnable mPendingOnEndAction;
  
      /**
       * Constants used to associate a property being requested and the mechanism used to set
       * the property (this class calls directly into View to set the properties in question).
       */
      static final int NONE           = 0x0000;
      static final int TRANSLATION_X  = 0x0001;
      static final int TRANSLATION_Y  = 0x0002;
      static final int TRANSLATION_Z  = 0x0004;
      static final int SCALE_X        = 0x0008;
      static final int SCALE_Y        = 0x0010;
      static final int ROTATION       = 0x0020;
      static final int ROTATION_X     = 0x0040;
      static final int ROTATION_Y     = 0x0080;
      static final int X              = 0x0100;
      static final int Y              = 0x0200;
      static final int Z              = 0x0400;
      static final int ALPHA          = 0x0800;
  
      private static final int TRANSFORM_MASK = TRANSLATION_X | TRANSLATION_Y | TRANSLATION_Z |
              SCALE_X | SCALE_Y | ROTATION | ROTATION_X | ROTATION_Y | X | Y | Z;
  
      /**
       * The mechanism by which the user can request several properties that are then animated
       * together works by posting this Runnable to start the underlying Animator. Every time
       * a property animation is requested, we cancel any previous postings of the Runnable
       * and re-post it. This means that we will only ever run the Runnable (and thus start the
       * underlying animator) after the caller is done setting the properties that should be
       * animated together.
       */
      private Runnable mAnimationStarter = new Runnable() {
          @Override
          public void run() {
              startAnimation();
          }
      };
  
      /**
       * This class holds information about the overall animation being run on the set of
       * properties. The mask describes which properties are being animated and the
       * values holder is the list of all property/value objects.
       */
      private static class PropertyBundle {
          int mPropertyMask;
          ArrayList<NameValuesHolder> mNameValuesHolder;
  
          PropertyBundle(int propertyMask, ArrayList<NameValuesHolder> nameValuesHolder) {
              mPropertyMask = propertyMask;
              mNameValuesHolder = nameValuesHolder;
          }
  
          /**
           * Removes the given property from being animated as a part of this
           * PropertyBundle. If the property was a part of this bundle, it returns
           * true to indicate that it was, in fact, canceled. This is an indication
           * to the caller that a cancellation actually occurred.
           *
           * @param propertyConstant The property whose cancellation is requested.
           * @return true if the given property is a part of this bundle and if it
           * has therefore been canceled.
           */
          boolean cancel(int propertyConstant) {
              if ((mPropertyMask & propertyConstant) != 0 && mNameValuesHolder != null) {
                  int count = mNameValuesHolder.size();
                  for (int i = 0; i < count; ++i) {
                      NameValuesHolder nameValuesHolder = mNameValuesHolder.get(i);
                      if (nameValuesHolder.mNameConstant == propertyConstant) {
                          mNameValuesHolder.remove(i);
                          mPropertyMask &= ~propertyConstant;
                          return true;
                      }
                  }
              }
              return false;
          }
      }
  
      /**
       * This list tracks the list of properties being animated by any particular animator.
       * In most situations, there would only ever be one animator running at a time. But it is
       * possible to request some properties to animate together, then while those properties
       * are animating, to request some other properties to animate together. The way that
       * works is by having this map associate the group of properties being animated with the
       * animator handling the animation. On every update event for an Animator, we ask the
       * map for the associated properties and set them accordingly.
       */
      private HashMap<Animator, PropertyBundle> mAnimatorMap =
              new HashMap<Animator, PropertyBundle>();
      private HashMap<Animator, Runnable> mAnimatorSetupMap;
      private HashMap<Animator, Runnable> mAnimatorCleanupMap;
      private HashMap<Animator, Runnable> mAnimatorOnStartMap;
      private HashMap<Animator, Runnable> mAnimatorOnEndMap;
  
      /**
       * This is the information we need to set each property during the animation.
       * mNameConstant is used to set the appropriate field in View, and the from/delta
       * values are used to calculate the animated value for a given animation fraction
       * during the animation.
       */
      static class NameValuesHolder {
          int mNameConstant;
          float mFromValue;
          float mDeltaValue;
          NameValuesHolder(int nameConstant, float fromValue, float deltaValue) {
              mNameConstant = nameConstant;
              mFromValue = fromValue;
              mDeltaValue = deltaValue;
          }
      }
  
      /**
       * Constructor, called by View. This is private by design, as the user should only
       * get a ViewPropertyAnimator by calling View.animate().
       *
       * @param view The View associated with this ViewPropertyAnimator
       */
      ViewPropertyAnimator(View view) {
          mView = view;
          view.ensureTransformationInfo();
      }
  
      /**
       * Sets the duration for the underlying animator that animates the requested properties.
       * By default, the animator uses the default value for ValueAnimator. Calling this method
       * will cause the declared value to be used instead.
       * @param duration The length of ensuing property animations, in milliseconds. The value
       * cannot be negative.
       * @return This object, allowing calls to methods in this class to be chained.
       */
      public ViewPropertyAnimator setDuration(long duration) {
          if (duration < 0) {
              throw new IllegalArgumentException("Animators cannot have negative duration: " +
                      duration);
          }
          mDurationSet = true;
          mDuration = duration;
          return this;
      }
  
      /**
       * Returns the current duration of property animations. If the duration was set on this
       * object, that value is returned. Otherwise, the default value of the underlying Animator
       * is returned.
       *
       * @see #setDuration(long)
       * @return The duration of animations, in milliseconds.
       */
      public long getDuration() {
          if (mDurationSet) {
              return mDuration;
          } else {
              // Just return the default from ValueAnimator, since that's what we'd get if
              // the value has not been set otherwise
              if (mTempValueAnimator == null) {
                  mTempValueAnimator = new ValueAnimator();
              }
              return mTempValueAnimator.getDuration();
          }
      }
  
      /**
       * Returns the current startDelay of property animations. If the startDelay was set on this
       * object, that value is returned. Otherwise, the default value of the underlying Animator
       * is returned.
       *
       * @see #setStartDelay(long)
       * @return The startDelay of animations, in milliseconds.
       */
      public long getStartDelay() {
          if (mStartDelaySet) {
              return mStartDelay;
          } else {
              // Just return the default from ValueAnimator (0), since that's what we'd get if
              // the value has not been set otherwise
              return 0;
          }
      }
  
      /**
       * Sets the startDelay for the underlying animator that animates the requested properties.
       * By default, the animator uses the default value for ValueAnimator. Calling this method
       * will cause the declared value to be used instead.
       * @param startDelay The delay of ensuing property animations, in milliseconds. The value
       * cannot be negative.
       * @return This object, allowing calls to methods in this class to be chained.
       */
      public ViewPropertyAnimator setStartDelay(long startDelay) {
          if (startDelay < 0) {
              throw new IllegalArgumentException("Animators cannot have negative start " +
                  "delay: " + startDelay);
          }
          mStartDelaySet = true;
          mStartDelay = startDelay;
          return this;
      }
  
      /**
       * Sets the interpolator for the underlying animator that animates the requested properties.
       * By default, the animator uses the default interpolator for ValueAnimator. Calling this method
       * will cause the declared object to be used instead.
       *
       * @param interpolator The TimeInterpolator to be used for ensuing property animations. A value
       * of <code>null</code> will result in linear interpolation.
       * @return This object, allowing calls to methods in this class to be chained.
       */
      public ViewPropertyAnimator setInterpolator(TimeInterpolator interpolator) {
          mInterpolatorSet = true;
          mInterpolator = interpolator;
          return this;
      }
  
      /**
       * Returns the timing interpolator that this animation uses.
       *
       * @return The timing interpolator for this animation.
       */
      public TimeInterpolator getInterpolator() {
          if (mInterpolatorSet) {
              return mInterpolator;
          } else {
              // Just return the default from ValueAnimator, since that's what we'd get if
              // the value has not been set otherwise
              if (mTempValueAnimator == null) {
                  mTempValueAnimator = new ValueAnimator();
              }
              return mTempValueAnimator.getInterpolator();
          }
      }
  
      /**
       * Sets a listener for events in the underlying Animators that run the property
       * animations.
       *
       * @see Animator.AnimatorListener
       *
       * @param listener The listener to be called with AnimatorListener events. A value of
       * <code>null</code> removes any existing listener.
       * @return This object, allowing calls to methods in this class to be chained.
       */
      public ViewPropertyAnimator setListener(Animator.AnimatorListener listener) {
          mListener = listener;
          return this;
      }
  
      Animator.AnimatorListener getListener() {
          return mListener;
      }
  
      /**
       * Sets a listener for update events in the underlying ValueAnimator that runs
       * the property animations. Note that the underlying animator is animating between
       * 0 and 1 (these values are then turned into the actual property values internally
       * by ViewPropertyAnimator). So the animator cannot give information on the current
       * values of the properties being animated by this ViewPropertyAnimator, although
       * the view object itself can be queried to get the current values.
       *
       * @see android.animation.ValueAnimator.AnimatorUpdateListener
       *
       * @param listener The listener to be called with update events. A value of
       * <code>null</code> removes any existing listener.
       * @return This object, allowing calls to methods in this class to be chained.
       */
      public ViewPropertyAnimator setUpdateListener(ValueAnimator.AnimatorUpdateListener listener) {
          mUpdateListener = listener;
          return this;
      }
  
      ValueAnimator.AnimatorUpdateListener getUpdateListener() {
          return mUpdateListener;
      }
  
      /**
       * Starts the currently pending property animations immediately. Calling <code>start()</code>
       * is optional because all animations start automatically at the next opportunity. However,
       * if the animations are needed to start immediately and synchronously (not at the time when
       * the next event is processed by the hierarchy, which is when the animations would begin
       * otherwise), then this method can be used.
       */
      public void start() {
          mView.removeCallbacks(mAnimationStarter);
          startAnimation();
      }
  
      /**
       * Cancels all property animations that are currently running or pending.
       */
      public void cancel() {
          if (mAnimatorMap.size() > 0) {
              HashMap<Animator, PropertyBundle> mAnimatorMapCopy =
                      (HashMap<Animator, PropertyBundle>)mAnimatorMap.clone();
              Set<Animator> animatorSet = mAnimatorMapCopy.keySet();
              for (Animator runningAnim : animatorSet) {
                  runningAnim.cancel();
              }
          }
          mPendingAnimations.clear();
          mPendingSetupAction = null;
          mPendingCleanupAction = null;
          mPendingOnStartAction = null;
          mPendingOnEndAction = null;
          mView.removeCallbacks(mAnimationStarter);
      }
  
      /**
       * This method will cause the View's <code>x</code> property to be animated to the
       * specified value. Animations already running on the property will be canceled.
       *
       * @param value The value to be animated to.
       * @see View#setX(float)
       * @return This object, allowing calls to methods in this class to be chained.
       */
      public ViewPropertyAnimator x(float value) {
          animateProperty(X, value);
          return this;
      }
  
      /**
       * This method will cause the View's <code>x</code> property to be animated by the
       * specified value. Animations already running on the property will be canceled.
       *
       * @param value The amount to be animated by, as an offset from the current value.
       * @see View#setX(float)
       * @return This object, allowing calls to methods in this class to be chained.
       */
      public ViewPropertyAnimator xBy(float value) {
          animatePropertyBy(X, value);
          return this;
      }
  
      /**
       * This method will cause the View's <code>y</code> property to be animated to the
       * specified value. Animations already running on the property will be canceled.
       *
       * @param value The value to be animated to.
       * @see View#setY(float)
       * @return This object, allowing calls to methods in this class to be chained.
       */
      public ViewPropertyAnimator y(float value) {
          animateProperty(Y, value);
          return this;
      }
  
      /**
       * This method will cause the View's <code>y</code> property to be animated by the
       * specified value. Animations already running on the property will be canceled.
       *
       * @param value The amount to be animated by, as an offset from the current value.
       * @see View#setY(float)
       * @return This object, allowing calls to methods in this class to be chained.
       */
      public ViewPropertyAnimator yBy(float value) {
          animatePropertyBy(Y, value);
          return this;
      }
  
      /**
       * This method will cause the View's <code>z</code> property to be animated to the
       * specified value. Animations already running on the property will be canceled.
       *
       * @param value The value to be animated to.
       * @see View#setZ(float)
       * @return This object, allowing calls to methods in this class to be chained.
       */
      public ViewPropertyAnimator z(float value) {
          animateProperty(Z, value);
          return this;
      }
  
      /**
       * This method will cause the View's <code>z</code> property to be animated by the
       * specified value. Animations already running on the property will be canceled.
       *
       * @param value The amount to be animated by, as an offset from the current value.
       * @see View#setZ(float)
       * @return This object, allowing calls to methods in this class to be chained.
       */
      public ViewPropertyAnimator zBy(float value) {
          animatePropertyBy(Z, value);
          return this;
      }
  
      /**
       * This method will cause the View's <code>rotation</code> property to be animated to the
       * specified value. Animations already running on the property will be canceled.
       *
       * @param value The value to be animated to.
       * @see View#setRotation(float)
       * @return This object, allowing calls to methods in this class to be chained.
       */
      public ViewPropertyAnimator rotation(float value) {
          animateProperty(ROTATION, value);
          return this;
      }
  
      /**
       * This method will cause the View's <code>rotation</code> property to be animated by the
       * specified value. Animations already running on the property will be canceled.
       *
       * @param value The amount to be animated by, as an offset from the current value.
       * @see View#setRotation(float)
       * @return This object, allowing calls to methods in this class to be chained.
       */
      public ViewPropertyAnimator rotationBy(float value) {
          animatePropertyBy(ROTATION, value);
          return this;
      }
  
      /**
       * This method will cause the View's <code>rotationX</code> property to be animated to the
       * specified value. Animations already running on the property will be canceled.
       *
       * @param value The value to be animated to.
       * @see View#setRotationX(float)
       * @return This object, allowing calls to methods in this class to be chained.
       */
      public ViewPropertyAnimator rotationX(float value) {
          animateProperty(ROTATION_X, value);
          return this;
      }
  
      /**
       * This method will cause the View's <code>rotationX</code> property to be animated by the
       * specified value. Animations already running on the property will be canceled.
       *
       * @param value The amount to be animated by, as an offset from the current value.
       * @see View#setRotationX(float)
       * @return This object, allowing calls to methods in this class to be chained.
       */
      public ViewPropertyAnimator rotationXBy(float value) {
          animatePropertyBy(ROTATION_X, value);
          return this;
      }
  
      /**
       * This method will cause the View's <code>rotationY</code> property to be animated to the
       * specified value. Animations already running on the property will be canceled.
       *
       * @param value The value to be animated to.
       * @see View#setRotationY(float)
       * @return This object, allowing calls to methods in this class to be chained.
       */
      public ViewPropertyAnimator rotationY(float value) {
          animateProperty(ROTATION_Y, value);
          return this;
      }
  
      /**
       * This method will cause the View's <code>rotationY</code> property to be animated by the
       * specified value. Animations already running on the property will be canceled.
       *
       * @param value The amount to be animated by, as an offset from the current value.
       * @see View#setRotationY(float)
       * @return This object, allowing calls to methods in this class to be chained.
       */
      public ViewPropertyAnimator rotationYBy(float value) {
          animatePropertyBy(ROTATION_Y, value);
          return this;
      }
  
      /**
       * This method will cause the View's <code>translationX</code> property to be animated to the
       * specified value. Animations already running on the property will be canceled.
       *
       * @param value The value to be animated to.
       * @see View#setTranslationX(float)
       * @return This object, allowing calls to methods in this class to be chained.
       */
      public ViewPropertyAnimator translationX(float value) {
          animateProperty(TRANSLATION_X, value);
          return this;
      }
  
      /**
       * This method will cause the View's <code>translationX</code> property to be animated by the
       * specified value. Animations already running on the property will be canceled.
       *
       * @param value The amount to be animated by, as an offset from the current value.
       * @see View#setTranslationX(float)
       * @return This object, allowing calls to methods in this class to be chained.
       */
      public ViewPropertyAnimator translationXBy(float value) {
          animatePropertyBy(TRANSLATION_X, value);
          return this;
      }
  
      /**
       * This method will cause the View's <code>translationY</code> property to be animated to the
       * specified value. Animations already running on the property will be canceled.
       *
       * @param value The value to be animated to.
       * @see View#setTranslationY(float)
       * @return This object, allowing calls to methods in this class to be chained.
       */
      public ViewPropertyAnimator translationY(float value) {
          animateProperty(TRANSLATION_Y, value);
          return this;
      }
  
      /**
       * This method will cause the View's <code>translationY</code> property to be animated by the
       * specified value. Animations already running on the property will be canceled.
       *
       * @param value The amount to be animated by, as an offset from the current value.
       * @see View#setTranslationY(float)
       * @return This object, allowing calls to methods in this class to be chained.
       */
      public ViewPropertyAnimator translationYBy(float value) {
          animatePropertyBy(TRANSLATION_Y, value);
          return this;
      }
  
      /**
       * This method will cause the View's <code>translationZ</code> property to be animated to the
       * specified value. Animations already running on the property will be canceled.
       *
       * @param value The value to be animated to.
       * @see View#setTranslationZ(float)
       * @return This object, allowing calls to methods in this class to be chained.
       */
      public ViewPropertyAnimator translationZ(float value) {
          animateProperty(TRANSLATION_Z, value);
          return this;
      }
  
      /**
       * This method will cause the View's <code>translationZ</code> property to be animated by the
       * specified value. Animations already running on the property will be canceled.
       *
       * @param value The amount to be animated by, as an offset from the current value.
       * @see View#setTranslationZ(float)
       * @return This object, allowing calls to methods in this class to be chained.
       */
      public ViewPropertyAnimator translationZBy(float value) {
          animatePropertyBy(TRANSLATION_Z, value);
          return this;
      }
      /**
       * This method will cause the View's <code>scaleX</code> property to be animated to the
       * specified value. Animations already running on the property will be canceled.
       *
       * @param value The value to be animated to.
       * @see View#setScaleX(float)
       * @return This object, allowing calls to methods in this class to be chained.
       */
      public ViewPropertyAnimator scaleX(float value) {
          animateProperty(SCALE_X, value);
          return this;
      }
  
      /**
       * This method will cause the View's <code>scaleX</code> property to be animated by the
       * specified value. Animations already running on the property will be canceled.
       *
       * @param value The amount to be animated by, as an offset from the current value.
       * @see View#setScaleX(float)
       * @return This object, allowing calls to methods in this class to be chained.
       */
      public ViewPropertyAnimator scaleXBy(float value) {
          animatePropertyBy(SCALE_X, value);
          return this;
      }
  
      /**
       * This method will cause the View's <code>scaleY</code> property to be animated to the
       * specified value. Animations already running on the property will be canceled.
       *
       * @param value The value to be animated to.
       * @see View#setScaleY(float)
       * @return This object, allowing calls to methods in this class to be chained.
       */
      public ViewPropertyAnimator scaleY(float value) {
          animateProperty(SCALE_Y, value);
          return this;
      }
  
      /**
       * This method will cause the View's <code>scaleY</code> property to be animated by the
       * specified value. Animations already running on the property will be canceled.
       *
       * @param value The amount to be animated by, as an offset from the current value.
       * @see View#setScaleY(float)
       * @return This object, allowing calls to methods in this class to be chained.
       */
      public ViewPropertyAnimator scaleYBy(float value) {
          animatePropertyBy(SCALE_Y, value);
          return this;
      }
  
      /**
       * This method will cause the View's <code>alpha</code> property to be animated to the
       * specified value. Animations already running on the property will be canceled.
       *
       * @param value The value to be animated to.
       * @see View#setAlpha(float)
       * @return This object, allowing calls to methods in this class to be chained.
       */
      public ViewPropertyAnimator alpha(float value) {
          animateProperty(ALPHA, value);
          return this;
      }
  
      /**
       * This method will cause the View's <code>alpha</code> property to be animated by the
       * specified value. Animations already running on the property will be canceled.
       *
       * @param value The amount to be animated by, as an offset from the current value.
       * @see View#setAlpha(float)
       * @return This object, allowing calls to methods in this class to be chained.
       */
      public ViewPropertyAnimator alphaBy(float value) {
          animatePropertyBy(ALPHA, value);
          return this;
      }
  
      /**
       * The View associated with this ViewPropertyAnimator will have its
       * {@link View#setLayerType(int, android.graphics.Paint) layer type} set to
       * {@link View#LAYER_TYPE_HARDWARE} for the duration of the next animation.
       * As stated in the documentation for {@link View#LAYER_TYPE_HARDWARE},
       * the actual type of layer used internally depends on the runtime situation of the
       * view. If the activity and this view are hardware-accelerated, then the layer will be
       * accelerated as well. If the activity or the view is not accelerated, then the layer will
       * effectively be the same as {@link View#LAYER_TYPE_SOFTWARE}.
       *
       * <p>This state is not persistent, either on the View or on this ViewPropertyAnimator: the
       * layer type of the View will be restored when the animation ends to what it was when this
       * method was called, and this setting on ViewPropertyAnimator is only valid for the next
       * animation. Note that calling this method and then independently setting the layer type of
       * the View (by a direct call to {@link View#setLayerType(int, android.graphics.Paint)}) will
       * result in some inconsistency, including having the layer type restored to its pre-withLayer()
       * value when the animation ends.</p>
       *
       * @see View#setLayerType(int, android.graphics.Paint)
       * @return This object, allowing calls to methods in this class to be chained.
       */
      public ViewPropertyAnimator withLayer() {
           mPendingSetupAction= new Runnable() {
              @Override
              public void run() {
                  mView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
                  if (mView.isAttachedToWindow()) {
                      mView.buildLayer();
                  }
              }
          };
          final int currentLayerType = mView.getLayerType();
          mPendingCleanupAction = new Runnable() {
              @Override
              public void run() {
                  mView.setLayerType(currentLayerType, null);
              }
          };
          if (mAnimatorSetupMap == null) {
              mAnimatorSetupMap = new HashMap<Animator, Runnable>();
          }
          if (mAnimatorCleanupMap == null) {
              mAnimatorCleanupMap = new HashMap<Animator, Runnable>();
          }
  
          return this;
      }
  
      /**
       * Specifies an action to take place when the next animation runs. If there is a
       * {@link #setStartDelay(long) startDelay} set on this ViewPropertyAnimator, then the
       * action will run after that startDelay expires, when the actual animation begins.
       * This method, along with {@link #withEndAction(Runnable)}, is intended to help facilitate
       * choreographing ViewPropertyAnimator animations with other animations or actions
       * in the application.
       *
       * @param runnable The action to run when the next animation starts.
       * @return This object, allowing calls to methods in this class to be chained.
       */
      public ViewPropertyAnimator withStartAction(Runnable runnable) {
          mPendingOnStartAction = runnable;
          if (runnable != null && mAnimatorOnStartMap == null) {
              mAnimatorOnStartMap = new HashMap<Animator, Runnable>();
          }
          return this;
      }
  
      /**
       * Specifies an action to take place when the next animation ends. The action is only
       * run if the animation ends normally; if the ViewPropertyAnimator is canceled during
       * that animation, the runnable will not run.
       * This method, along with {@link #withStartAction(Runnable)}, is intended to help facilitate
       * choreographing ViewPropertyAnimator animations with other animations or actions
       * in the application.
       *
       * <p>For example, the following code animates a view to x=200 and then back to 0:</p>
       * <pre>
       *     Runnable endAction = new Runnable() {
       *         public void run() {
       *             view.animate().x(0);
       *         }
       *     };
       *     view.animate().x(200).withEndAction(endAction);
       * </pre>
       *
       * @param runnable The action to run when the next animation ends.
       * @return This object, allowing calls to methods in this class to be chained.
       */
      public ViewPropertyAnimator withEndAction(Runnable runnable) {
          mPendingOnEndAction = runnable;
          if (runnable != null && mAnimatorOnEndMap == null) {
              mAnimatorOnEndMap = new HashMap<Animator, Runnable>();
          }
          return this;
      }
  
      boolean hasActions() {
          return mPendingSetupAction != null
                  || mPendingCleanupAction != null
                  || mPendingOnStartAction != null
                  || mPendingOnEndAction != null;
      }
  
      /**
       * Starts the underlying Animator for a set of properties. We use a single animator that
       * simply runs from 0 to 1, and then use that fractional value to set each property
       * value accordingly.
       */
      private void startAnimation() {
          mView.setHasTransientState(true);
          ValueAnimator animator = ValueAnimator.ofFloat(1.0f);
          ArrayList<NameValuesHolder> nameValueList =
                  (ArrayList<NameValuesHolder>) mPendingAnimations.clone();
          mPendingAnimations.clear();
          int propertyMask = 0;
          int propertyCount = nameValueList.size();
          for (int i = 0; i < propertyCount; ++i) {
              NameValuesHolder nameValuesHolder = nameValueList.get(i);
              propertyMask |= nameValuesHolder.mNameConstant;
          }
          mAnimatorMap.put(animator, new PropertyBundle(propertyMask, nameValueList));
          if (mPendingSetupAction != null) {
              mAnimatorSetupMap.put(animator, mPendingSetupAction);
              mPendingSetupAction = null;
          }
          if (mPendingCleanupAction != null) {
              mAnimatorCleanupMap.put(animator, mPendingCleanupAction);
              mPendingCleanupAction = null;
          }
          if (mPendingOnStartAction != null) {
              mAnimatorOnStartMap.put(animator, mPendingOnStartAction);
              mPendingOnStartAction = null;
          }
          if (mPendingOnEndAction != null) {
              mAnimatorOnEndMap.put(animator, mPendingOnEndAction);
              mPendingOnEndAction = null;
          }
          animator.addUpdateListener(mAnimatorEventListener);
          animator.addListener(mAnimatorEventListener);
          if (mStartDelaySet) {
              animator.setStartDelay(mStartDelay);
          }
          if (mDurationSet) {
              animator.setDuration(mDuration);
          }
          if (mInterpolatorSet) {
              animator.setInterpolator(mInterpolator);
          }
          animator.start();
      }
  
      /**
       * Utility function, called by the various x(), y(), etc. methods. This stores the
       * constant name for the property along with the from/delta values that will be used to
       * calculate and set the property during the animation. This structure is added to the
       * pending animations, awaiting the eventual start() of the underlying animator. A
       * Runnable is posted to start the animation, and any pending such Runnable is canceled
       * (which enables us to end up starting just one animator for all of the properties
       * specified at one time).
       *
       * @param constantName The specifier for the property being animated
       * @param toValue The value to which the property will animate
       */
      private void animateProperty(int constantName, float toValue) {
          float fromValue = getValue(constantName);
          float deltaValue = toValue - fromValue;
          animatePropertyBy(constantName, fromValue, deltaValue);
      }
  
      /**
       * Utility function, called by the various xBy(), yBy(), etc. methods. This method is
       * just like animateProperty(), except the value is an offset from the property's
       * current value, instead of an absolute "to" value.
       *
       * @param constantName The specifier for the property being animated
       * @param byValue The amount by which the property will change
       */
      private void animatePropertyBy(int constantName, float byValue) {
          float fromValue = getValue(constantName);
          animatePropertyBy(constantName, fromValue, byValue);
      }
  
      /**
       * Utility function, called by animateProperty() and animatePropertyBy(), which handles the
       * details of adding a pending animation and posting the request to start the animation.
       *
       * @param constantName The specifier for the property being animated
       * @param startValue The starting value of the property
       * @param byValue The amount by which the property will change
       */
      private void animatePropertyBy(int constantName, float startValue, float byValue) {
          // First, cancel any existing animations on this property
          if (mAnimatorMap.size() > 0) {
              Animator animatorToCancel = null;
              Set<Animator> animatorSet = mAnimatorMap.keySet();
              for (Animator runningAnim : animatorSet) {
                  PropertyBundle bundle = mAnimatorMap.get(runningAnim);
                  if (bundle.cancel(constantName)) {
                      // property was canceled - cancel the animation if it's now empty
                      // Note that it's safe to break out here because every new animation
                      // on a property will cancel a previous animation on that property, so
                      // there can only ever be one such animation running.
                      if (bundle.mPropertyMask == NONE) {
                          // the animation is no longer changing anything - cancel it
                          animatorToCancel = runningAnim;
                          break;
                      }
                  }
              }
              if (animatorToCancel != null) {
                  animatorToCancel.cancel();
              }
          }
  
          NameValuesHolder nameValuePair = new NameValuesHolder(constantName, startValue, byValue);
          mPendingAnimations.add(nameValuePair);
          mView.removeCallbacks(mAnimationStarter);
          mView.postOnAnimation(mAnimationStarter);
      }
  
      /**
       * This method handles setting the property values directly in the View object's fields.
       * propertyConstant tells it which property should be set, value is the value to set
       * the property to.
       *
       * @param propertyConstant The property to be set
       * @param value The value to set the property to
       */
      private void setValue(int propertyConstant, float value) {
          final RenderNode renderNode = mView.mRenderNode;
          switch (propertyConstant) {
              case TRANSLATION_X:
                  renderNode.setTranslationX(value);
                  break;
              case TRANSLATION_Y:
                  renderNode.setTranslationY(value);
                  break;
              case TRANSLATION_Z:
                  renderNode.setTranslationZ(value);
                  break;
              case ROTATION:
                  renderNode.setRotationZ(value);
                  break;
              case ROTATION_X:
                  renderNode.setRotationX(value);
                  break;
              case ROTATION_Y:
                  renderNode.setRotationY(value);
                  break;
              case SCALE_X:
                  renderNode.setScaleX(value);
                  break;
              case SCALE_Y:
                  renderNode.setScaleY(value);
                  break;
              case X:
                  renderNode.setTranslationX(value - mView.mLeft);
                  break;
              case Y:
                  renderNode.setTranslationY(value - mView.mTop);
                  break;
              case Z:
                  renderNode.setTranslationZ(value - renderNode.getElevation());
                  break;
              case ALPHA:
                  mView.setAlphaInternal(value);
                  renderNode.setAlpha(value);
                  break;
          }
      }
  
      /**
       * This method gets the value of the named property from the View object.
       *
       * @param propertyConstant The property whose value should be returned
       * @return float The value of the named property
       */
      private float getValue(int propertyConstant) {
          final RenderNode node = mView.mRenderNode;
          switch (propertyConstant) {
              case TRANSLATION_X:
                  return node.getTranslationX();
              case TRANSLATION_Y:
                  return node.getTranslationY();
              case TRANSLATION_Z:
                  return node.getTranslationZ();
              case ROTATION:
                  return node.getRotationZ();
              case ROTATION_X:
                  return node.getRotationX();
              case ROTATION_Y:
                  return node.getRotationY();
              case SCALE_X:
                  return node.getScaleX();
              case SCALE_Y:
                  return node.getScaleY();
              case X:
                  return mView.mLeft + node.getTranslationX();
              case Y:
                  return mView.mTop + node.getTranslationY();
              case Z:
                  return node.getElevation() + node.getTranslationZ();
              case ALPHA:
                  return mView.getAlpha();
          }
          return 0;
      }
  
      /**
       * Utility class that handles the various Animator events. The only ones we care
       * about are the end event (which we use to clean up the animator map when an animator
       * finishes) and the update event (which we use to calculate the current value of each
       * property and then set it on the view object).
       */
      private class AnimatorEventListener
              implements Animator.AnimatorListener, ValueAnimator.AnimatorUpdateListener {
          @Override
          public void onAnimationStart(Animator animation) {
              if (mAnimatorSetupMap != null) {
                  Runnable r = mAnimatorSetupMap.get(animation);
                  if (r != null) {
                      r.run();
                  }
                  mAnimatorSetupMap.remove(animation);
              }
              if (mAnimatorOnStartMap != null) {
                  Runnable r = mAnimatorOnStartMap.get(animation);
                  if (r != null) {
                      r.run();
                  }
                  mAnimatorOnStartMap.remove(animation);
              }
              if (mListener != null) {
                  mListener.onAnimationStart(animation);
              }
          }
  
          @Override
          public void onAnimationCancel(Animator animation) {
              if (mListener != null) {
                  mListener.onAnimationCancel(animation);
              }
              if (mAnimatorOnEndMap != null) {
                  mAnimatorOnEndMap.remove(animation);
              }
          }
  
          @Override
          public void onAnimationRepeat(Animator animation) {
              if (mListener != null) {
                  mListener.onAnimationRepeat(animation);
              }
          }
  
          @Override
          public void onAnimationEnd(Animator animation) {
              mView.setHasTransientState(false);
              if (mAnimatorCleanupMap != null) {
                  Runnable r = mAnimatorCleanupMap.get(animation);
                  if (r != null) {
                      r.run();
                  }
                  mAnimatorCleanupMap.remove(animation);
              }
              if (mListener != null) {
                  mListener.onAnimationEnd(animation);
              }
              if (mAnimatorOnEndMap != null) {
                  Runnable r = mAnimatorOnEndMap.get(animation);
                  if (r != null) {
                      r.run();
                  }
                  mAnimatorOnEndMap.remove(animation);
              }
              mAnimatorMap.remove(animation);
          }
  
          /**
           * Calculate the current value for each property and set it on the view. Invalidate
           * the view object appropriately, depending on which properties are being animated.
           *
           * @param animation The animator associated with the properties that need to be
           * set. This animator holds the animation fraction which we will use to calculate
           * the current value of each property.
           */
          @Override
          public void onAnimationUpdate(ValueAnimator animation) {
              PropertyBundle propertyBundle = mAnimatorMap.get(animation);
              if (propertyBundle == null) {
                  // Shouldn't happen, but just to play it safe
                  return;
              }
  
              boolean hardwareAccelerated = mView.isHardwareAccelerated();
  
              if (!hardwareAccelerated) {
                  mView.invalidateParentCaches();
              }
              float fraction = animation.getAnimatedFraction();
              int propertyMask = propertyBundle.mPropertyMask;
              if ((propertyMask & TRANSFORM_MASK) != 0) {
                  mView.invalidateViewProperty(hardwareAccelerated, false);
              }
              ArrayList<NameValuesHolder> valueList = propertyBundle.mNameValuesHolder;
              if (valueList != null) {
                  int count = valueList.size();
                  for (int i = 0; i < count; ++i) {
                      NameValuesHolder values = valueList.get(i);
                      float value = values.mFromValue + fraction * values.mDeltaValue;
                      setValue(values.mNameConstant, value);
                  }
              }
              if ((propertyMask & TRANSFORM_MASK) != 0) {
                  if (!hardwareAccelerated) {
                      mView.mPrivateFlags |= View.PFLAG_DRAWN; // force another invalidation
                  }
              }
  
              mView.invalidateViewProperty(false, false);
              if (mUpdateListener != null) {
                  mUpdateListener.onAnimationUpdate(animation);
              }
          }
      }
  }
  