/** * {@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; } }
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); } } } }
/** * 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; } }
/** * 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; }
/** * 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; }
/** * 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; }
/** * 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; }