/**
  * {@inheritDoc}
  *
  * <p>Note that canceling a <code>AnimatorSet</code> also cancels all of the animations that it is
  * responsible for.
  */
 @Override
 public void cancel() {
   mTerminated = true;
   if (isStarted()) {
     ArrayList<AnimatorListener> tmpListeners = null;
     if (mListeners != null) {
       tmpListeners = (ArrayList<AnimatorListener>) mListeners.clone();
       for (AnimatorListener listener : tmpListeners) {
         listener.onAnimationCancel(this);
       }
     }
     if (mDelayAnim != null && mDelayAnim.isRunning()) {
       // If we're currently in the startDelay period, just cancel that animator and
       // send out the end event to all listeners
       mDelayAnim.cancel();
     } else if (mSortedNodes.size() > 0) {
       for (Node node : mSortedNodes) {
         node.animation.cancel();
       }
     }
     if (tmpListeners != null) {
       for (AnimatorListener listener : tmpListeners) {
         listener.onAnimationEnd(this);
       }
     }
     mStarted = false;
   }
 }
Example #2
0
 public ValueAnimator clone() {
   final ValueAnimator anim = (ValueAnimator) super.clone();
   if (mUpdateListeners != null) {
     ArrayList<AnimatorUpdateListener> oldListeners = mUpdateListeners;
     anim.mUpdateListeners = new ArrayList<AnimatorUpdateListener>();
     int numListeners = oldListeners.size();
     for (int i = 0; i < numListeners; ++i) {
       anim.mUpdateListeners.add(oldListeners.get(i));
     }
   }
   anim.mSeekTime = -1;
   anim.mPlayingBackwards = false;
   anim.mCurrentIteration = 0;
   anim.mInitialized = false;
   anim.mPlayingState = STOPPED;
   anim.mStartedDelay = false;
   PropertyValuesHolder[] oldValues = mValues;
   if (oldValues != null) {
     int numValues = oldValues.length;
     anim.mValues = new PropertyValuesHolder[numValues];
     anim.mValuesMap = new HashMap<String, PropertyValuesHolder>(numValues);
     for (int i = 0; i < numValues; ++i) {
       PropertyValuesHolder newValuesHolder = oldValues[i].clone();
       anim.mValues[i] = newValuesHolder;
       anim.mValuesMap.put(newValuesHolder.getPropertyName(), newValuesHolder);
     }
   }
   return anim;
 }
 /**
  * Sets up the animation supplied in the {@link
  * com.actionbarsherlock.internal.nineoldandroids.animation.AnimatorSet#play(Animator)} call
  * that created this <code>Builder</code> object to play when the given amount of time elapses.
  *
  * @param delay The number of milliseconds that should elapse before the animation starts.
  */
 public Builder after(long delay) {
   // setup dummy ValueAnimator just to run the clock
   ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
   anim.setDuration(delay);
   after(anim);
   return this;
 }
 /**
  * {@inheritDoc}
  *
  * <p>Note that ending a <code>AnimatorSet</code> also ends all of the animations that it is
  * responsible for.
  */
 @Override
 public void end() {
   mTerminated = true;
   if (isStarted()) {
     if (mSortedNodes.size() != mNodes.size()) {
       // hasn't been started yet - sort the nodes now, then end them
       sortNodes();
       for (Node node : mSortedNodes) {
         if (mSetListener == null) {
           mSetListener = new AnimatorSetListener(this);
         }
         node.animation.addListener(mSetListener);
       }
     }
     if (mDelayAnim != null) {
       mDelayAnim.cancel();
     }
     if (mSortedNodes.size() > 0) {
       for (Node node : mSortedNodes) {
         node.animation.end();
       }
     }
     if (mListeners != null) {
       ArrayList<AnimatorListener> tmpListeners = (ArrayList<AnimatorListener>) mListeners.clone();
       for (AnimatorListener listener : tmpListeners) {
         listener.onAnimationEnd(this);
       }
     }
     mStarted = false;
   }
 }
  /**
   * {@inheritDoc}
   *
   * <p>Starting this <code>AnimatorSet</code> will, in turn, start the animations for which it is
   * responsible. The details of when exactly those animations are started depends on the dependency
   * relationships that have been set up between the animations.
   */
  @Override
  public void start() {
    mTerminated = false;
    mStarted = true;

    // First, sort the nodes (if necessary). This will ensure that sortedNodes
    // contains the animation nodes in the correct order.
    sortNodes();

    int numSortedNodes = mSortedNodes.size();
    for (int i = 0; i < numSortedNodes; ++i) {
      Node node = mSortedNodes.get(i);
      // First, clear out the old listeners
      ArrayList<AnimatorListener> oldListeners = node.animation.getListeners();
      if (oldListeners != null && oldListeners.size() > 0) {
        final ArrayList<AnimatorListener> clonedListeners =
            new ArrayList<AnimatorListener>(oldListeners);

        for (AnimatorListener listener : clonedListeners) {
          if (listener instanceof DependencyListener || listener instanceof AnimatorSetListener) {
            node.animation.removeListener(listener);
          }
        }
      }
    }

    // nodesToStart holds the list of nodes to be started immediately. We don't want to
    // start the animations in the loop directly because we first need to set up
    // dependencies on all of the nodes. For example, we don't want to start an animation
    // when some other animation also wants to start when the first animation begins.
    final ArrayList<Node> nodesToStart = new ArrayList<Node>();
    for (int i = 0; i < numSortedNodes; ++i) {
      Node node = mSortedNodes.get(i);
      if (mSetListener == null) {
        mSetListener = new AnimatorSetListener(this);
      }
      if (node.dependencies == null || node.dependencies.size() == 0) {
        nodesToStart.add(node);
      } else {
        int numDependencies = node.dependencies.size();
        for (int j = 0; j < numDependencies; ++j) {
          Dependency dependency = node.dependencies.get(j);
          dependency.node.animation.addListener(
              new DependencyListener(this, node, dependency.rule));
        }
        node.tmpDependencies = (ArrayList<Dependency>) node.dependencies.clone();
      }
      node.animation.addListener(mSetListener);
    }
    // Now that all dependencies are set up, start the animations that should be started.
    if (mStartDelay <= 0) {
      for (Node node : nodesToStart) {
        node.animation.start();
        mPlayingSet.add(node.animation);
      }
    } else {
      mDelayAnim = ValueAnimator.ofFloat(0f, 1f);
      mDelayAnim.setDuration(mStartDelay);
      mDelayAnim.addListener(
          new AnimatorListenerAdapter() {
            boolean canceled = false;

            public void onAnimationCancel(Animator anim) {
              canceled = true;
            }

            public void onAnimationEnd(Animator anim) {
              if (!canceled) {
                int numNodes = nodesToStart.size();
                for (int i = 0; i < numNodes; ++i) {
                  Node node = nodesToStart.get(i);
                  node.animation.start();
                  mPlayingSet.add(node.animation);
                }
              }
            }
          });
      mDelayAnim.start();
    }
    if (mListeners != null) {
      ArrayList<AnimatorListener> tmpListeners = (ArrayList<AnimatorListener>) mListeners.clone();
      int numListeners = tmpListeners.size();
      for (int i = 0; i < numListeners; ++i) {
        tmpListeners.get(i).onAnimationStart(this);
      }
    }
    if (mNodes.size() == 0 && mStartDelay == 0) {
      // Handle unusual case where empty AnimatorSet is started - should send out
      // end event immediately since the event will not be sent out at all otherwise
      mStarted = false;
      if (mListeners != null) {
        ArrayList<AnimatorListener> tmpListeners = (ArrayList<AnimatorListener>) mListeners.clone();
        int numListeners = tmpListeners.size();
        for (int i = 0; i < numListeners; ++i) {
          tmpListeners.get(i).onAnimationEnd(this);
        }
      }
    }
  }
Example #6
0
    /**
     * There are only two messages that we care about: ANIMATION_START and ANIMATION_FRAME. The
     * START message is sent when an animation's start() method is called. It cannot start
     * synchronously when start() is called because the call may be on the wrong thread, and it
     * would also not be synchronized with other animations because it would not start on a common
     * timing pulse. So each animation sends a START message to the handler, which causes the
     * handler to place the animation on the active animations queue and start processing frames for
     * that animation. The FRAME message is the one that is sent over and over while there are any
     * active animations to process.
     */
    public void handleMessage(Message msg) {
      boolean callAgain = true;
      ArrayList<ValueAnimator> animations = sAnimations.get();
      ArrayList<ValueAnimator> delayedAnims = sDelayedAnims.get();
      switch (msg.what) {
          // TODO: should we avoid sending frame message when starting if we
          // were already running?
        case ANIMATION_START:
          ArrayList<ValueAnimator> pendingAnimations = sPendingAnimations.get();
          if (animations.size() > 0 || delayedAnims.size() > 0) {
            callAgain = false;
          }
          // pendingAnims holds any animations that have requested to be started
          // We're going to clear sPendingAnimations, but starting animation may
          // cause more to be added to the pending list (for example, if one animation
          // starting triggers another starting). So we loop until sPendingAnimations
          // is empty.
          while (pendingAnimations.size() > 0) {
            ArrayList<ValueAnimator> pendingCopy =
                (ArrayList<ValueAnimator>) pendingAnimations.clone();
            pendingAnimations.clear();
            int count = pendingCopy.size();
            for (int i = 0; i < count; ++i) {
              ValueAnimator anim = pendingCopy.get(i);
              // If the animation has a startDelay, place it on the delayed list
              if (anim.mStartDelay == 0) {
                anim.startAnimation();
              } else {
                delayedAnims.add(anim);
              }
            }
          }
          // fall through to process first frame of new animations
        case ANIMATION_FRAME:
          // currentTime holds the common time for all animations processed
          // during this frame
          long currentTime = AnimationUtils.currentAnimationTimeMillis();
          ArrayList<ValueAnimator> readyAnims = sReadyAnims.get();
          ArrayList<ValueAnimator> endingAnims = sEndingAnims.get();

          // First, process animations currently sitting on the delayed queue, adding
          // them to the active animations if they are ready
          int numDelayedAnims = delayedAnims.size();
          for (int i = 0; i < numDelayedAnims; ++i) {
            ValueAnimator anim = delayedAnims.get(i);
            if (anim.delayedAnimationFrame(currentTime)) {
              readyAnims.add(anim);
            }
          }
          int numReadyAnims = readyAnims.size();
          if (numReadyAnims > 0) {
            for (int i = 0; i < numReadyAnims; ++i) {
              ValueAnimator anim = readyAnims.get(i);
              anim.startAnimation();
              anim.mRunning = true;
              delayedAnims.remove(anim);
            }
            readyAnims.clear();
          }

          // Now process all active animations. The return value from animationFrame()
          // tells the handler whether it should now be ended
          int numAnims = animations.size();
          int i = 0;
          while (i < numAnims) {
            ValueAnimator anim = animations.get(i);
            if (anim.animationFrame(currentTime)) {
              endingAnims.add(anim);
            }
            if (animations.size() == numAnims) {
              ++i;
            } else {
              // An animation might be canceled or ended by client code
              // during the animation frame. Check to see if this happened by
              // seeing whether the current index is the same as it was before
              // calling animationFrame(). Another approach would be to copy
              // animations to a temporary list and process that list instead,
              // but that entails garbage and processing overhead that would
              // be nice to avoid.
              --numAnims;
              endingAnims.remove(anim);
            }
          }
          if (endingAnims.size() > 0) {
            for (i = 0; i < endingAnims.size(); ++i) {
              endingAnims.get(i).endAnimation();
            }
            endingAnims.clear();
          }

          // If there are still active or delayed animations, call the handler again
          // after the frameDelay
          if (callAgain && (!animations.isEmpty() || !delayedAnims.isEmpty())) {
            sendEmptyMessageDelayed(
                ANIMATION_FRAME,
                Math.max(
                    0, sFrameDelay - (AnimationUtils.currentAnimationTimeMillis() - currentTime)));
          }
          break;
      }
    }
Example #7
0
 /**
  * Constructs and returns a ValueAnimator that animates between Object values. A single value
  * implies that that value is the one being animated to. However, this is not typically useful in
  * a ValueAnimator object because there is no way for the object to determine the starting value
  * for the animation (unlike ObjectAnimator, which can derive that value from the target object
  * and property being animated). Therefore, there should typically be two or more values.
  *
  * <p>Since ValueAnimator does not know how to animate between arbitrary Objects, this factory
  * method also takes a TypeEvaluator object that the ValueAnimator will use to perform that
  * interpolation.
  *
  * @param evaluator A TypeEvaluator that will be called on each animation frame to provide the
  *     ncessry interpolation between the Object values to derive the animated value.
  * @param values A set of values that the animation will animate between over time.
  * @return A ValueAnimator object that is set up to animate between the given values.
  */
 public static ValueAnimator ofObject(TypeEvaluator evaluator, Object... values) {
   ValueAnimator anim = new ValueAnimator();
   anim.setObjectValues(values);
   anim.setEvaluator(evaluator);
   return anim;
 }
Example #8
0
 /**
  * Constructs and returns a ValueAnimator that animates between the values specified in the
  * PropertyValuesHolder objects.
  *
  * @param values A set of PropertyValuesHolder objects whose values will be animated between over
  *     time.
  * @return A ValueAnimator object that is set up to animate between the given values.
  */
 public static ValueAnimator ofPropertyValuesHolder(PropertyValuesHolder... values) {
   ValueAnimator anim = new ValueAnimator();
   anim.setValues(values);
   return anim;
 }
Example #9
0
 /**
  * Constructs and returns a ValueAnimator that animates between float values. A single value
  * implies that that value is the one being animated to. However, this is not typically useful in
  * a ValueAnimator object because there is no way for the object to determine the starting value
  * for the animation (unlike ObjectAnimator, which can derive that value from the target object
  * and property being animated). Therefore, there should typically be two or more values.
  *
  * @param values A set of values that the animation will animate between over time.
  * @return A ValueAnimator object that is set up to animate between the given values.
  */
 public static ValueAnimator ofFloat(float... values) {
   ValueAnimator anim = new ValueAnimator();
   anim.setFloatValues(values);
   return anim;
 }
Example #10
0
 /**
  * Constructs and returns a ValueAnimator that animates between int values. A single value implies
  * that that value is the one being animated to. However, this is not typically useful in a
  * ValueAnimator object because there is no way for the object to determine the starting value for
  * the animation (unlike ObjectAnimator, which can derive that value from the target object and
  * property being animated). Therefore, there should typically be two or more values.
  *
  * @param values A set of values that the animation will animate between over time.
  * @return A ValueAnimator object that is set up to animate between the given values.
  */
 public static ValueAnimator ofInt(int... values) {
   ValueAnimator anim = new ValueAnimator();
   anim.setIntValues(values);
   return anim;
 }