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 com.hx.curtain.drawer.nineoldandroids.view;
  
  import java.lang.ref.WeakReference;
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.Set;
  import android.view.View;
  import android.view.animation.Interpolator;
  import com.hx.curtain.drawer.nineoldandroids.animation.Animator;
  import com.hx.curtain.drawer.nineoldandroids.animation.ValueAnimator;
  
  
  class ViewPropertyAnimatorHC extends ViewPropertyAnimator {
  
      /**
       * A WeakReference holding the View whose properties are being animated by this class.
       * This is set at construction time.
       */
      private final WeakReference<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 /*Time*/Interpolator 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
       */
      private Animator.AnimatorListener mListener = null;
  
      /**
       * 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>();
  
      /**
       * 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).
       */
      private static final int NONE           = 0x0000;
      private static final int TRANSLATION_X  = 0x0001;
      private static final int TRANSLATION_Y  = 0x0002;
      private static final int SCALE_X        = 0x0004;
      private static final int SCALE_Y        = 0x0008;
      private static final int ROTATION       = 0x0010;
      private static final int ROTATION_X     = 0x0020;
      private static final int ROTATION_Y     = 0x0040;
      private static final int X              = 0x0080;
      private static final int Y              = 0x0100;
      private static final int ALPHA          = 0x0200;
  
      private static final int TRANSFORM_MASK = TRANSLATION_X | TRANSLATION_Y | SCALE_X | SCALE_Y |
              ROTATION | ROTATION_X | ROTATION_Y | X | Y;
  
      /**
       * 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>();
  
      /**
       * 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.
       */
      private 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
       */
      ViewPropertyAnimatorHC(View view) {
          mView = new WeakReference<View>(view);
      }
  
      /**
       * 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
              return new ValueAnimator().getDuration();
          }
      }
  
      @Override
      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;
          }
      }
  
      @Override
      public ViewPropertyAnimator setStartDelay(long startDelay) {
          if (startDelay < 0) {
              throw new IllegalArgumentException("Animators cannot have negative duration: " +
                      startDelay);
          }
          mStartDelaySet = true;
          mStartDelay = startDelay;
          return this;
      }
  
      @Override
      public ViewPropertyAnimator setInterpolator(/*Time*/Interpolator interpolator) {
          mInterpolatorSet = true;
          mInterpolator = interpolator;
          return this;
      }
  
      @Override
      public ViewPropertyAnimator setListener(Animator.AnimatorListener listener) {
          mListener = listener;
          return this;
      }
  
      @Override
      public void start() {
          startAnimation();
      }
  
      @Override
      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();
          View v = mView.get();
          if (v != null) {
              v.removeCallbacks(mAnimationStarter);
          }
      }
  
      @Override
      public ViewPropertyAnimator x(float value) {
          animateProperty(X, value);
          return this;
      }
  
      @Override
      public ViewPropertyAnimator xBy(float value) {
          animatePropertyBy(X, value);
          return this;
      }
  
      @Override
      public ViewPropertyAnimator y(float value) {
          animateProperty(Y, value);
          return this;
      }
  
      @Override
      public ViewPropertyAnimator yBy(float value) {
          animatePropertyBy(Y, value);
          return this;
      }
  
      @Override
      public ViewPropertyAnimator rotation(float value) {
          animateProperty(ROTATION, value);
          return this;
      }
  
      @Override
      public ViewPropertyAnimator rotationBy(float value) {
          animatePropertyBy(ROTATION, value);
          return this;
      }
  
      @Override
      public ViewPropertyAnimator rotationX(float value) {
          animateProperty(ROTATION_X, value);
          return this;
      }
  
      @Override
      public ViewPropertyAnimator rotationXBy(float value) {
          animatePropertyBy(ROTATION_X, value);
          return this;
      }
  
      @Override
      public ViewPropertyAnimator rotationY(float value) {
          animateProperty(ROTATION_Y, value);
          return this;
      }
  
      @Override
      public ViewPropertyAnimator rotationYBy(float value) {
          animatePropertyBy(ROTATION_Y, value);
          return this;
      }
  
      @Override
      public ViewPropertyAnimator translationX(float value) {
          animateProperty(TRANSLATION_X, value);
          return this;
      }
  
      @Override
      public ViewPropertyAnimator translationXBy(float value) {
          animatePropertyBy(TRANSLATION_X, value);
          return this;
      }
  
      @Override
      public ViewPropertyAnimator translationY(float value) {
          animateProperty(TRANSLATION_Y, value);
          return this;
      }
  
      @Override
      public ViewPropertyAnimator translationYBy(float value) {
          animatePropertyBy(TRANSLATION_Y, value);
          return this;
      }
  
      @Override
      public ViewPropertyAnimator scaleX(float value) {
          animateProperty(SCALE_X, value);
          return this;
      }
  
      @Override
      public ViewPropertyAnimator scaleXBy(float value) {
          animatePropertyBy(SCALE_X, value);
          return this;
      }
  
      @Override
      public ViewPropertyAnimator scaleY(float value) {
          animateProperty(SCALE_Y, value);
          return this;
      }
  
      @Override
      public ViewPropertyAnimator scaleYBy(float value) {
          animatePropertyBy(SCALE_Y, value);
          return this;
      }
  
      @Override
      public ViewPropertyAnimator alpha(float value) {
          animateProperty(ALPHA, value);
          return this;
      }
  
      @Override
      public ViewPropertyAnimator alphaBy(float value) {
          animatePropertyBy(ALPHA, value);
          return this;
      }
  
      /**
       * 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() {
          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));
          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);
          View v = mView.get();
          if (v != null) {
              v.removeCallbacks(mAnimationStarter);
              v.post(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 View.TransformationInfo info = mView.mTransformationInfo;
          View v = mView.get();
          if (v != null) {
              switch (propertyConstant) {
                  case TRANSLATION_X:
                      //info.mTranslationX = value;
                      v.setTranslationX(value);
                      break;
                  case TRANSLATION_Y:
                      //info.mTranslationY = value;
                      v.setTranslationY(value);
                      break;
                  case ROTATION:
                      //info.mRotation = value;
                      v.setRotation(value);
                      break;
                  case ROTATION_X:
                      //info.mRotationX = value;
                      v.setRotationX(value);
                      break;
                  case ROTATION_Y:
                      //info.mRotationY = value;
                      v.setRotationY(value);
                      break;
                  case SCALE_X:
                      //info.mScaleX = value;
                      v.setScaleX(value);
                      break;
                  case SCALE_Y:
                      //info.mScaleY = value;
                      v.setScaleY(value);
                      break;
                  case X:
                      //info.mTranslationX = value - v.mLeft;
                      v.setX(value);
                      break;
                  case Y:
                      //info.mTranslationY = value - v.mTop;
                      v.setY(value);
                      break;
                  case ALPHA:
                      //info.mAlpha = value;
                      v.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 View.TransformationInfo info = mView.mTransformationInfo;
          View v = mView.get();
          if (v != null) {
              switch (propertyConstant) {
                  case TRANSLATION_X:
                      //return info.mTranslationX;
                      return v.getTranslationX();
                  case TRANSLATION_Y:
                      //return info.mTranslationY;
                      return v.getTranslationY();
                  case ROTATION:
                      //return info.mRotation;
                      return v.getRotation();
                  case ROTATION_X:
                      //return info.mRotationX;
                      return v.getRotationX();
                  case ROTATION_Y:
                      //return info.mRotationY;
                      return v.getRotationY();
                  case SCALE_X:
                      //return info.mScaleX;
                      return v.getScaleX();
                  case SCALE_Y:
                      //return info.mScaleY;
                      return v.getScaleY();
                  case X:
                      //return v.mLeft + info.mTranslationX;
                      return v.getX();
                  case Y:
                      //return v.mTop + info.mTranslationY;
                      return v.getY();
                  case ALPHA:
                      //return info.mAlpha;
                      return v.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 (mListener != null) {
                  mListener.onAnimationStart(animation);
              }
          }
  
          @Override
          public void onAnimationCancel(Animator animation) {
              if (mListener != null) {
                  mListener.onAnimationCancel(animation);
              }
          }
  
          @Override
          public void onAnimationRepeat(Animator animation) {
              if (mListener != null) {
                  mListener.onAnimationRepeat(animation);
              }
          }
  
          @Override
          public void onAnimationEnd(Animator animation) {
              if (mListener != null) {
                  mListener.onAnimationEnd(animation);
              }
              mAnimatorMap.remove(animation);
              // If the map is empty, it means all animation are done or canceled, so the listener
              // isn't needed anymore. Not nulling it would cause it to leak any objects used in
              // its implementation
              if (mAnimatorMap.isEmpty()) {
                  mListener = null;
              }
          }
  
          /**
           * 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) {
              // alpha requires slightly different treatment than the other (transform) properties.
              // The logic in setAlpha() is not simply setting mAlpha, plus the invalidation
              // logic is dependent on how the view handles an internal call to onSetAlpha().
              // We track what kinds of properties are set, and how alpha is handled when it is
              // set, and perform the invalidation steps appropriately.
              //boolean alphaHandled = false;
              //mView.invalidateParentCaches();
              float fraction = animation.getAnimatedFraction();
              PropertyBundle propertyBundle = mAnimatorMap.get(animation);
              int propertyMask = propertyBundle.mPropertyMask;
              if ((propertyMask & TRANSFORM_MASK) != 0) {
                  View v = mView.get();
                  if (v != null) {
                      v.invalidate(/*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;
                      //if (values.mNameConstant == ALPHA) {
                      //    alphaHandled = mView.setAlphaNoInvalidation(value);
                      //} else {
                          setValue(values.mNameConstant, value);
                      //}
                  }
              }
              /*if ((propertyMask & TRANSFORM_MASK) != 0) {
                  mView.mTransformationInfo.mMatrixDirty = true;
                  mView.mPrivateFlags |= View.DRAWN; // force another invalidation
              }*/
              // invalidate(false) in all cases except if alphaHandled gets set to true
              // via the call to setAlphaNoInvalidation(), above
              View v = mView.get();
              if (v != null) {
                  v.invalidate(/*alphaHandled*/);
              }
          }
      }
  }
  