2 * Copyright (C) 2010 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package android.animation;
19 import android.app.ActivityThread;
20 import android.app.Application;
21 import android.os.Build;
22 import android.util.ArrayMap;
23 import android.util.Log;
25 import java.util.ArrayList;
26 import java.util.Collection;
27 import java.util.List;
30 * This class plays a set of {@link Animator} objects in the specified order. Animations
31 * can be set up to play together, in sequence, or after a specified delay.
33 * <p>There are two different approaches to adding animations to a <code>AnimatorSet</code>:
34 * either the {@link AnimatorSet#playTogether(Animator[]) playTogether()} or
35 * {@link AnimatorSet#playSequentially(Animator[]) playSequentially()} methods can be called to add
36 * a set of animations all at once, or the {@link AnimatorSet#play(Animator)} can be
37 * used in conjunction with methods in the {@link AnimatorSet.Builder Builder}
38 * class to add animations
41 * <p>It is possible to set up a <code>AnimatorSet</code> with circular dependencies between
42 * its animations. For example, an animation a1 could be set up to start before animation a2, a2
43 * before a3, and a3 before a1. The results of this configuration are undefined, but will typically
44 * result in none of the affected animations being played. Because of this (and because
45 * circular dependencies do not make logical sense anyway), circular dependencies
46 * should be avoided, and the dependency flow of animations should only be in one direction.
48 * <div class="special reference">
49 * <h3>Developer Guides</h3>
50 * <p>For more information about animating with {@code AnimatorSet}, read the
51 * <a href="{@docRoot}guide/topics/graphics/prop-animation.html#choreography">Property
52 * Animation</a> developer guide.</p>
55 public final class AnimatorSet extends Animator {
57 private static final String TAG = "AnimatorSet";
60 * NOTE: This object implements the clone() method, making a deep copy of any referenced
61 * objects. As other non-trivial fields are added to this class, make sure to add logic
62 * to clone() to make deep copies of them.
66 * Tracks animations currently being played, so that we know what to
67 * cancel or end when cancel() or end() is called on this AnimatorSet
69 private ArrayList<Animator> mPlayingSet = new ArrayList<Animator>();
72 * Contains all nodes, mapped to their respective Animators. When new
73 * dependency information is added for an Animator, we want to add it
74 * to a single node representing that Animator, not create a new Node
75 * if one already exists.
77 private ArrayMap<Animator, Node> mNodeMap = new ArrayMap<Animator, Node>();
80 * Set of all nodes created for this AnimatorSet. This list is used upon
81 * starting the set, and the nodes are placed in sorted order into the
82 * sortedNodes collection.
84 private ArrayList<Node> mNodes = new ArrayList<Node>();
87 * Animator Listener that tracks the lifecycle of each Animator in the set. It will be added
88 * to each Animator before they start and removed after they end.
90 private AnimatorSetListener mSetListener = new AnimatorSetListener(this);
93 * Flag indicating that the AnimatorSet has been manually
94 * terminated (by calling cancel() or end()).
95 * This flag is used to avoid starting other animations when currently-playing
96 * child animations of this AnimatorSet end. It also determines whether cancel/end
97 * notifications are sent out via the normal AnimatorSetListener mechanism.
99 private boolean mTerminated = false;
102 * Tracks whether any change has been made to the AnimatorSet, which is then used to
103 * determine whether the dependency graph should be re-constructed.
105 private boolean mDependencyDirty = false;
108 * Indicates whether an AnimatorSet has been start()'d, whether or
109 * not there is a nonzero startDelay.
111 private boolean mStarted = false;
113 // The amount of time in ms to delay starting the animation after start() is called
114 private long mStartDelay = 0;
116 // Animator used for a nonzero startDelay
117 private ValueAnimator mDelayAnim = ValueAnimator.ofFloat(0f, 1f).setDuration(0);
119 // Root of the dependency tree of all the animators in the set. In this tree, parent-child
120 // relationship captures the order of animation (i.e. parent and child will play sequentially),
121 // and sibling relationship indicates "with" relationship, as sibling animators start at the
123 private Node mRootNode = new Node(mDelayAnim);
125 // How long the child animations should last in ms. The default value is negative, which
126 // simply means that there is no duration set on the AnimatorSet. When a real duration is
127 // set, it is passed along to the child animations.
128 private long mDuration = -1;
130 // Records the interpolator for the set. Null value indicates that no interpolator
131 // was set on this AnimatorSet, so it should not be passed down to the children.
132 private TimeInterpolator mInterpolator = null;
134 // Whether the AnimatorSet can be reversed.
135 private boolean mReversible = true;
136 // The total duration of finishing all the Animators in the set.
137 private long mTotalDuration = 0;
139 // In pre-N releases, calling end() before start() on an animator set is no-op. But that is not
140 // consistent with the behavior for other animator types. In order to keep the behavior
141 // consistent within Animation framework, when end() is called without start(), we will start
142 // the animator set and immediately end it for N and forward.
143 private final boolean mShouldIgnoreEndWithoutStart;
145 public AnimatorSet() {
147 mNodeMap.put(mDelayAnim, mRootNode);
148 mNodes.add(mRootNode);
149 // Set the flag to ignore calling end() without start() for pre-N releases
150 Application app = ActivityThread.currentApplication();
151 if (app == null || app.getApplicationInfo() == null) {
152 mShouldIgnoreEndWithoutStart = true;
153 } else if (app.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) {
154 mShouldIgnoreEndWithoutStart = true;
156 mShouldIgnoreEndWithoutStart = false;
161 * Sets up this AnimatorSet to play all of the supplied animations at the same time.
162 * This is equivalent to calling {@link #play(Animator)} with the first animator in the
163 * set and then {@link Builder#with(Animator)} with each of the other animators. Note that
164 * an Animator with a {@link Animator#setStartDelay(long) startDelay} will not actually
165 * start until that delay elapses, which means that if the first animator in the list
166 * supplied to this constructor has a startDelay, none of the other animators will start
167 * until that first animator's startDelay has elapsed.
169 * @param items The animations that will be started simultaneously.
171 public void playTogether(Animator... items) {
173 Builder builder = play(items[0]);
174 for (int i = 1; i < items.length; ++i) {
175 builder.with(items[i]);
181 * Sets up this AnimatorSet to play all of the supplied animations at the same time.
183 * @param items The animations that will be started simultaneously.
185 public void playTogether(Collection<Animator> items) {
186 if (items != null && items.size() > 0) {
187 Builder builder = null;
188 for (Animator anim : items) {
189 if (builder == null) {
190 builder = play(anim);
199 * Sets up this AnimatorSet to play each of the supplied animations when the
200 * previous animation ends.
202 * @param items The animations that will be started one after another.
204 public void playSequentially(Animator... items) {
206 if (items.length == 1) {
210 for (int i = 0; i < items.length - 1; ++i) {
211 play(items[i]).before(items[i + 1]);
218 * Sets up this AnimatorSet to play each of the supplied animations when the
219 * previous animation ends.
221 * @param items The animations that will be started one after another.
223 public void playSequentially(List<Animator> items) {
224 if (items != null && items.size() > 0) {
225 if (items.size() == 1) {
229 for (int i = 0; i < items.size() - 1; ++i) {
230 play(items.get(i)).before(items.get(i + 1));
237 * Returns the current list of child Animator objects controlled by this
238 * AnimatorSet. This is a copy of the internal list; modifications to the returned list
239 * will not affect the AnimatorSet, although changes to the underlying Animator objects
240 * will affect those objects being managed by the AnimatorSet.
242 * @return ArrayList<Animator> The list of child animations of this AnimatorSet.
244 public ArrayList<Animator> getChildAnimations() {
245 ArrayList<Animator> childList = new ArrayList<Animator>();
246 int size = mNodes.size();
247 for (int i = 0; i < size; i++) {
248 Node node = mNodes.get(i);
249 if (node != mRootNode) {
250 childList.add(node.mAnimation);
257 * Sets the target object for all current {@link #getChildAnimations() child animations}
258 * of this AnimatorSet that take targets ({@link ObjectAnimator} and
261 * @param target The object being animated
264 public void setTarget(Object target) {
265 int size = mNodes.size();
266 for (int i = 0; i < size; i++) {
267 Node node = mNodes.get(i);
268 Animator animation = node.mAnimation;
269 if (animation instanceof AnimatorSet) {
270 ((AnimatorSet)animation).setTarget(target);
271 } else if (animation instanceof ObjectAnimator) {
272 ((ObjectAnimator)animation).setTarget(target);
281 public int getChangingConfigurations() {
282 int conf = super.getChangingConfigurations();
283 final int nodeCount = mNodes.size();
284 for (int i = 0; i < nodeCount; i ++) {
285 conf |= mNodes.get(i).mAnimation.getChangingConfigurations();
291 * Sets the TimeInterpolator for all current {@link #getChildAnimations() child animations}
292 * of this AnimatorSet. The default value is null, which means that no interpolator
293 * is set on this AnimatorSet. Setting the interpolator to any non-null value
294 * will cause that interpolator to be set on the child animations
295 * when the set is started.
297 * @param interpolator the interpolator to be used by each child animation of this AnimatorSet
300 public void setInterpolator(TimeInterpolator interpolator) {
301 mInterpolator = interpolator;
305 public TimeInterpolator getInterpolator() {
306 return mInterpolator;
310 * This method creates a <code>Builder</code> object, which is used to
311 * set up playing constraints. This initial <code>play()</code> method
312 * tells the <code>Builder</code> the animation that is the dependency for
313 * the succeeding commands to the <code>Builder</code>. For example,
314 * calling <code>play(a1).with(a2)</code> sets up the AnimatorSet to play
315 * <code>a1</code> and <code>a2</code> at the same time,
316 * <code>play(a1).before(a2)</code> sets up the AnimatorSet to play
317 * <code>a1</code> first, followed by <code>a2</code>, and
318 * <code>play(a1).after(a2)</code> sets up the AnimatorSet to play
319 * <code>a2</code> first, followed by <code>a1</code>.
321 * <p>Note that <code>play()</code> is the only way to tell the
322 * <code>Builder</code> the animation upon which the dependency is created,
323 * so successive calls to the various functions in <code>Builder</code>
324 * will all refer to the initial parameter supplied in <code>play()</code>
325 * as the dependency of the other animations. For example, calling
326 * <code>play(a1).before(a2).before(a3)</code> will play both <code>a2</code>
327 * and <code>a3</code> when a1 ends; it does not set up a dependency between
328 * <code>a2</code> and <code>a3</code>.</p>
330 * @param anim The animation that is the dependency used in later calls to the
331 * methods in the returned <code>Builder</code> object. A null parameter will result
332 * in a null <code>Builder</code> return value.
333 * @return Builder The object that constructs the AnimatorSet based on the dependencies
334 * outlined in the calls to <code>play</code> and the other methods in the
335 * <code>Builder</code object.
337 public Builder play(Animator anim) {
339 return new Builder(anim);
347 * <p>Note that canceling a <code>AnimatorSet</code> also cancels all of the animations that it
348 * is responsible for.</p>
350 @SuppressWarnings("unchecked")
352 public void cancel() {
355 ArrayList<AnimatorListener> tmpListeners = null;
356 if (mListeners != null) {
357 tmpListeners = (ArrayList<AnimatorListener>) mListeners.clone();
358 int size = tmpListeners.size();
359 for (int i = 0; i < size; i++) {
360 tmpListeners.get(i).onAnimationCancel(this);
363 ArrayList<Animator> playingSet = new ArrayList<>(mPlayingSet);
364 int setSize = playingSet.size();
365 for (int i = 0; i < setSize; i++) {
366 playingSet.get(i).cancel();
368 if (tmpListeners != null) {
369 int size = tmpListeners.size();
370 for (int i = 0; i < size; i++) {
371 tmpListeners.get(i).onAnimationEnd(this);
381 * <p>Note that ending a <code>AnimatorSet</code> also ends all of the animations that it is
382 * responsible for.</p>
386 if (mShouldIgnoreEndWithoutStart && !isStarted()) {
391 endRemainingAnimations();
393 if (mListeners != null) {
394 ArrayList<AnimatorListener> tmpListeners =
395 (ArrayList<AnimatorListener>) mListeners.clone();
396 for (int i = 0; i < tmpListeners.size(); i++) {
397 tmpListeners.get(i).onAnimationEnd(this);
404 * Iterate the animations that haven't finished or haven't started, and end them.
406 private void endRemainingAnimations() {
407 ArrayList<Animator> remainingList = new ArrayList<Animator>(mNodes.size());
408 remainingList.addAll(mPlayingSet);
411 while (index < remainingList.size()) {
412 Animator anim = remainingList.get(index);
415 Node node = mNodeMap.get(anim);
416 if (node.mChildNodes != null) {
417 int childSize = node.mChildNodes.size();
418 for (int i = 0; i < childSize; i++) {
419 Node child = node.mChildNodes.get(i);
420 if (child.mLatestParent != node) {
423 remainingList.add(child.mAnimation);
431 * Returns true if any of the child animations of this AnimatorSet have been started and have
432 * not yet ended. Child animations will not be started until the AnimatorSet has gone past
433 * its initial delay set through {@link #setStartDelay(long)}.
435 * @return Whether this AnimatorSet has gone past the initial delay, and at least one child
436 * animation has been started and not yet ended.
439 public boolean isRunning() {
440 int size = mNodes.size();
441 for (int i = 0; i < size; i++) {
442 Node node = mNodes.get(i);
443 if (node != mRootNode && node.mAnimation.isStarted()) {
451 public boolean isStarted() {
456 * The amount of time, in milliseconds, to delay starting the animation after
457 * {@link #start()} is called.
459 * @return the number of milliseconds to delay running the animation
462 public long getStartDelay() {
467 * The amount of time, in milliseconds, to delay starting the animation after
468 * {@link #start()} is called. Note that the start delay should always be non-negative. Any
469 * negative start delay will be clamped to 0 on N and above.
471 * @param startDelay The amount of the delay, in milliseconds
474 public void setStartDelay(long startDelay) {
475 // Clamp start delay to non-negative range.
476 if (startDelay < 0) {
477 Log.w(TAG, "Start delay should always be non-negative");
480 long delta = startDelay - mStartDelay;
484 mStartDelay = startDelay;
485 if (mStartDelay > 0) {
488 if (!mDependencyDirty) {
489 // Dependency graph already constructed, update all the nodes' start/end time
490 int size = mNodes.size();
491 for (int i = 0; i < size; i++) {
492 Node node = mNodes.get(i);
493 if (node == mRootNode) {
494 node.mEndTime = mStartDelay;
496 node.mStartTime = node.mStartTime == DURATION_INFINITE ?
497 DURATION_INFINITE : node.mStartTime + delta;
498 node.mEndTime = node.mEndTime == DURATION_INFINITE ?
499 DURATION_INFINITE : node.mEndTime + delta;
502 // Update total duration, if necessary.
503 if (mTotalDuration != DURATION_INFINITE) {
504 mTotalDuration += delta;
510 * Gets the length of each of the child animations of this AnimatorSet. This value may
511 * be less than 0, which indicates that no duration has been set on this AnimatorSet
512 * and each of the child animations will use their own duration.
514 * @return The length of the animation, in milliseconds, of each of the child
515 * animations of this AnimatorSet.
518 public long getDuration() {
523 * Sets the length of each of the current child animations of this AnimatorSet. By default,
524 * each child animation will use its own duration. If the duration is set on the AnimatorSet,
525 * then each child animation inherits this duration.
527 * @param duration The length of the animation, in milliseconds, of each of the child
528 * animations of this AnimatorSet.
531 public AnimatorSet setDuration(long duration) {
533 throw new IllegalArgumentException("duration must be a value of zero or greater");
535 mDependencyDirty = true;
536 // Just record the value for now - it will be used later when the AnimatorSet starts
537 mDuration = duration;
542 public void setupStartValues() {
543 int size = mNodes.size();
544 for (int i = 0; i < size; i++) {
545 Node node = mNodes.get(i);
546 if (node != mRootNode) {
547 node.mAnimation.setupStartValues();
553 public void setupEndValues() {
554 int size = mNodes.size();
555 for (int i = 0; i < size; i++) {
556 Node node = mNodes.get(i);
557 if (node != mRootNode) {
558 node.mAnimation.setupEndValues();
564 public void pause() {
565 boolean previouslyPaused = mPaused;
567 if (!previouslyPaused && mPaused) {
568 if (mDelayAnim.isStarted()) {
569 // If delay hasn't passed, pause the start delay animator.
572 int size = mNodes.size();
573 for (int i = 0; i < size; i++) {
574 Node node = mNodes.get(i);
575 if (node != mRootNode) {
576 node.mAnimation.pause();
584 public void resume() {
585 boolean previouslyPaused = mPaused;
587 if (previouslyPaused && !mPaused) {
588 if (mDelayAnim.isStarted()) {
589 // If start delay hasn't passed, resume the previously paused start delay animator
592 int size = mNodes.size();
593 for (int i = 0; i < size; i++) {
594 Node node = mNodes.get(i);
595 if (node != mRootNode) {
596 node.mAnimation.resume();
606 * <p>Starting this <code>AnimatorSet</code> will, in turn, start the animations for which
607 * it is responsible. The details of when exactly those animations are started depends on
608 * the dependency relationships that have been set up between the animations.
610 @SuppressWarnings("unchecked")
612 public void start() {
617 int size = mNodes.size();
618 for (int i = 0; i < size; i++) {
619 Node node = mNodes.get(i);
621 node.mAnimation.setAllowRunningAsynchronously(false);
624 if (mInterpolator != null) {
625 for (int i = 0; i < size; i++) {
626 Node node = mNodes.get(i);
627 node.mAnimation.setInterpolator(mInterpolator);
631 updateAnimatorsDuration();
632 createDependencyGraph();
634 // Now that all dependencies are set up, start the animations that should be started.
635 boolean setIsEmpty = false;
636 if (mStartDelay > 0) {
638 } else if (mNodes.size() > 1) {
639 // No delay, but there are other animators in the set
640 onChildAnimatorEnded(mDelayAnim);
642 // Set is empty, no delay, no other animation. Skip to end in this case
646 if (mListeners != null) {
647 ArrayList<AnimatorListener> tmpListeners =
648 (ArrayList<AnimatorListener>) mListeners.clone();
649 int numListeners = tmpListeners.size();
650 for (int i = 0; i < numListeners; ++i) {
651 tmpListeners.get(i).onAnimationStart(this);
655 // In the case of empty AnimatorSet, we will trigger the onAnimationEnd() right away.
656 onChildAnimatorEnded(mDelayAnim);
660 private void updateAnimatorsDuration() {
661 if (mDuration >= 0) {
662 // If the duration was set on this AnimatorSet, pass it along to all child animations
663 int size = mNodes.size();
664 for (int i = 0; i < size; i++) {
665 Node node = mNodes.get(i);
666 // TODO: don't set the duration of the timing-only nodes created by AnimatorSet to
667 // insert "play-after" delays
668 node.mAnimation.setDuration(mDuration);
671 mDelayAnim.setDuration(mStartDelay);
674 void start(final Node node) {
675 final Animator anim = node.mAnimation;
676 mPlayingSet.add(anim);
677 anim.addListener(mSetListener);
682 public AnimatorSet clone() {
683 final AnimatorSet anim = (AnimatorSet) super.clone();
685 * The basic clone() operation copies all items. This doesn't work very well for
686 * AnimatorSet, because it will copy references that need to be recreated and state
687 * that may not apply. What we need to do now is put the clone in an uninitialized
688 * state, with fresh, empty data structures. Then we will build up the nodes list
689 * manually, as we clone each Node (and its animation). The clone will then be sorted,
690 * and will populate any appropriate lists, when it is started.
692 final int nodeCount = mNodes.size();
693 anim.mTerminated = false;
694 anim.mStarted = false;
695 anim.mPlayingSet = new ArrayList<Animator>();
696 anim.mNodeMap = new ArrayMap<Animator, Node>();
697 anim.mNodes = new ArrayList<Node>(nodeCount);
698 anim.mReversible = mReversible;
699 anim.mSetListener = new AnimatorSetListener(anim);
701 // Walk through the old nodes list, cloning each node and adding it to the new nodemap.
702 // One problem is that the old node dependencies point to nodes in the old AnimatorSet.
703 // We need to track the old/new nodes in order to reconstruct the dependencies in the clone.
705 for (int n = 0; n < nodeCount; n++) {
706 final Node node = mNodes.get(n);
707 Node nodeClone = node.clone();
708 node.mTmpClone = nodeClone;
709 anim.mNodes.add(nodeClone);
710 anim.mNodeMap.put(nodeClone.mAnimation, nodeClone);
712 // clear out any listeners that were set up by the AnimatorSet
713 final ArrayList<AnimatorListener> cloneListeners = nodeClone.mAnimation.getListeners();
714 if (cloneListeners != null) {
715 for (int i = cloneListeners.size() - 1; i >= 0; i--) {
716 final AnimatorListener listener = cloneListeners.get(i);
717 if (listener instanceof AnimatorSetListener) {
718 cloneListeners.remove(i);
724 anim.mRootNode = mRootNode.mTmpClone;
725 anim.mDelayAnim = (ValueAnimator) anim.mRootNode.mAnimation;
727 // Now that we've cloned all of the nodes, we're ready to walk through their
728 // dependencies, mapping the old dependencies to the new nodes
729 for (int i = 0; i < nodeCount; i++) {
730 Node node = mNodes.get(i);
731 // Update dependencies for node's clone
732 node.mTmpClone.mLatestParent = node.mLatestParent == null ?
733 null : node.mLatestParent.mTmpClone;
734 int size = node.mChildNodes == null ? 0 : node.mChildNodes.size();
735 for (int j = 0; j < size; j++) {
736 node.mTmpClone.mChildNodes.set(j, node.mChildNodes.get(j).mTmpClone);
738 size = node.mSiblings == null ? 0 : node.mSiblings.size();
739 for (int j = 0; j < size; j++) {
740 node.mTmpClone.mSiblings.set(j, node.mSiblings.get(j).mTmpClone);
742 size = node.mParents == null ? 0 : node.mParents.size();
743 for (int j = 0; j < size; j++) {
744 node.mTmpClone.mParents.set(j, node.mParents.get(j).mTmpClone);
748 for (int n = 0; n < nodeCount; n++) {
749 mNodes.get(n).mTmpClone = null;
755 private static class AnimatorSetListener implements AnimatorListener {
757 private AnimatorSet mAnimatorSet;
759 AnimatorSetListener(AnimatorSet animatorSet) {
760 mAnimatorSet = animatorSet;
763 public void onAnimationCancel(Animator animation) {
765 if (!mAnimatorSet.mTerminated) {
766 // Listeners are already notified of the AnimatorSet canceling in cancel().
767 // The logic below only kicks in when animations end normally
768 if (mAnimatorSet.mPlayingSet.size() == 0) {
769 ArrayList<AnimatorListener> listeners = mAnimatorSet.mListeners;
770 if (listeners != null) {
771 int numListeners = listeners.size();
772 for (int i = 0; i < numListeners; ++i) {
773 listeners.get(i).onAnimationCancel(mAnimatorSet);
780 @SuppressWarnings("unchecked")
781 public void onAnimationEnd(Animator animation) {
782 animation.removeListener(this);
783 mAnimatorSet.mPlayingSet.remove(animation);
784 mAnimatorSet.onChildAnimatorEnded(animation);
788 public void onAnimationRepeat(Animator animation) {
792 public void onAnimationStart(Animator animation) {
797 private void onChildAnimatorEnded(Animator animation) {
798 Node animNode = mNodeMap.get(animation);
799 animNode.mEnded = true;
802 List<Node> children = animNode.mChildNodes;
803 // Start children animations, if any.
804 int childrenSize = children == null ? 0 : children.size();
805 for (int i = 0; i < childrenSize; i++) {
806 if (children.get(i).mLatestParent == animNode) {
807 start(children.get(i));
810 // Listeners are already notified of the AnimatorSet ending in cancel() or
811 // end(); the logic below only kicks in when animations end normally
812 boolean allDone = true;
813 // Traverse the tree and find if there's any unfinished node
814 int size = mNodes.size();
815 for (int i = 0; i < size; i++) {
816 if (!mNodes.get(i).mEnded) {
822 // If this was the last child animation to end, then notify listeners that this
823 // AnimatorSet has ended
824 if (mListeners != null) {
825 ArrayList<AnimatorListener> tmpListeners =
826 (ArrayList<AnimatorListener>) mListeners.clone();
827 int numListeners = tmpListeners.size();
828 for (int i = 0; i < numListeners; ++i) {
829 tmpListeners.get(i).onAnimationEnd(this);
839 * AnimatorSet is only reversible when the set contains no sequential animation, and no child
840 * animators have a start delay.
844 public boolean canReverse() {
848 // Loop to make sure all the Nodes can reverse.
849 int size = mNodes.size();
850 for (int i = 0; i < size; i++) {
851 Node node = mNodes.get(i);
852 if (!node.mAnimation.canReverse() || node.mAnimation.getStartDelay() > 0) {
863 public void reverse() {
865 int size = mNodes.size();
866 for (int i = 0; i < size; i++) {
867 Node node = mNodes.get(i);
868 node.mAnimation.reverse();
874 public String toString() {
875 String returnVal = "AnimatorSet@" + Integer.toHexString(hashCode()) + "{";
876 int size = mNodes.size();
877 for (int i = 0; i < size; i++) {
878 Node node = mNodes.get(i);
879 returnVal += "\n " + node.mAnimation.toString();
881 return returnVal + "\n}";
884 private void printChildCount() {
885 // Print out the child count through a level traverse.
886 ArrayList<Node> list = new ArrayList<>(mNodes.size());
888 Log.d(TAG, "Current tree: ");
890 while (index < list.size()) {
891 int listSize = list.size();
892 StringBuilder builder = new StringBuilder();
893 for (; index < listSize; index++) {
894 Node node = list.get(index);
896 if (node.mChildNodes != null) {
897 for (int i = 0; i < node.mChildNodes.size(); i++) {
898 Node child = node.mChildNodes.get(i);
899 if (child.mLatestParent == node) {
908 Log.d(TAG, builder.toString());
912 private void createDependencyGraph() {
913 if (!mDependencyDirty) {
914 // Check whether any duration of the child animations has changed
915 boolean durationChanged = false;
916 for (int i = 0; i < mNodes.size(); i++) {
917 Animator anim = mNodes.get(i).mAnimation;
918 if (mNodes.get(i).mTotalDuration != anim.getTotalDuration()) {
919 durationChanged = true;
923 if (!durationChanged) {
928 mDependencyDirty = false;
929 // Traverse all the siblings and make sure they have all the parents
930 int size = mNodes.size();
931 for (int i = 0; i < size; i++) {
932 mNodes.get(i).mParentsAdded = false;
934 for (int i = 0; i < size; i++) {
935 Node node = mNodes.get(i);
936 if (node.mParentsAdded) {
940 node.mParentsAdded = true;
941 if (node.mSiblings == null) {
945 // Find all the siblings
946 findSiblings(node, node.mSiblings);
947 node.mSiblings.remove(node);
949 // Get parents from all siblings
950 int siblingSize = node.mSiblings.size();
951 for (int j = 0; j < siblingSize; j++) {
952 node.addParents(node.mSiblings.get(j).mParents);
955 // Now make sure all siblings share the same set of parents
956 for (int j = 0; j < siblingSize; j++) {
957 Node sibling = node.mSiblings.get(j);
958 sibling.addParents(node.mParents);
959 sibling.mParentsAdded = true;
963 for (int i = 0; i < size; i++) {
964 Node node = mNodes.get(i);
965 if (node != mRootNode && node.mParents == null) {
966 node.addParent(mRootNode);
970 // Do a DFS on the tree
971 ArrayList<Node> visited = new ArrayList<Node>(mNodes.size());
972 // Assign start/end time
973 mRootNode.mStartTime = 0;
974 mRootNode.mEndTime = mDelayAnim.getDuration();
975 updatePlayTime(mRootNode, visited);
978 for (int i = 0; i < size; i++) {
979 Node node = mNodes.get(i);
980 node.mTotalDuration = node.mAnimation.getTotalDuration();
981 if (node.mEndTime == DURATION_INFINITE) {
982 maxEndTime = DURATION_INFINITE;
985 maxEndTime = node.mEndTime > maxEndTime ? node.mEndTime : maxEndTime;
988 mTotalDuration = maxEndTime;
992 * Based on parent's start/end time, calculate children's start/end time. If cycle exists in
993 * the graph, all the nodes on the cycle will be marked to start at {@link #DURATION_INFINITE},
994 * meaning they will ever play.
996 private void updatePlayTime(Node parent, ArrayList<Node> visited) {
997 if (parent.mChildNodes == null) {
998 if (parent == mRootNode) {
999 // All the animators are in a cycle
1000 for (int i = 0; i < mNodes.size(); i++) {
1001 Node node = mNodes.get(i);
1002 if (node != mRootNode) {
1003 node.mStartTime = DURATION_INFINITE;
1004 node.mEndTime = DURATION_INFINITE;
1011 visited.add(parent);
1012 int childrenSize = parent.mChildNodes.size();
1013 for (int i = 0; i < childrenSize; i++) {
1014 Node child = parent.mChildNodes.get(i);
1015 int index = visited.indexOf(child);
1017 // Child has been visited, cycle found. Mark all the nodes in the cycle.
1018 for (int j = index; j < visited.size(); j++) {
1019 visited.get(j).mLatestParent = null;
1020 visited.get(j).mStartTime = DURATION_INFINITE;
1021 visited.get(j).mEndTime = DURATION_INFINITE;
1023 child.mStartTime = DURATION_INFINITE;
1024 child.mEndTime = DURATION_INFINITE;
1025 child.mLatestParent = null;
1026 Log.w(TAG, "Cycle found in AnimatorSet: " + this);
1030 if (child.mStartTime != DURATION_INFINITE) {
1031 if (parent.mEndTime == DURATION_INFINITE) {
1032 child.mLatestParent = parent;
1033 child.mStartTime = DURATION_INFINITE;
1034 child.mEndTime = DURATION_INFINITE;
1036 if (parent.mEndTime >= child.mStartTime) {
1037 child.mLatestParent = parent;
1038 child.mStartTime = parent.mEndTime;
1041 long duration = child.mAnimation.getTotalDuration();
1042 child.mEndTime = duration == DURATION_INFINITE ?
1043 DURATION_INFINITE : child.mStartTime + duration;
1046 updatePlayTime(child, visited);
1048 visited.remove(parent);
1051 // Recursively find all the siblings
1052 private void findSiblings(Node node, ArrayList<Node> siblings) {
1053 if (!siblings.contains(node)) {
1055 if (node.mSiblings == null) {
1058 for (int i = 0; i < node.mSiblings.size(); i++) {
1059 findSiblings(node.mSiblings.get(i), siblings);
1066 * TODO: For animatorSet defined in XML, we can use a flag to indicate what the play order
1067 * if defined (i.e. sequential or together), then we can use the flag instead of calculating
1068 * dynamically. Note that when AnimatorSet is empty this method returns true.
1069 * @return whether all the animators in the set are supposed to play together
1071 public boolean shouldPlayTogether() {
1072 updateAnimatorsDuration();
1073 createDependencyGraph();
1074 // All the child nodes are set out to play right after the delay animation
1075 return mRootNode.mChildNodes == null || mRootNode.mChildNodes.size() == mNodes.size() - 1;
1079 public long getTotalDuration() {
1080 updateAnimatorsDuration();
1081 createDependencyGraph();
1082 return mTotalDuration;
1085 private Node getNodeForAnimation(Animator anim) {
1086 Node node = mNodeMap.get(anim);
1088 node = new Node(anim);
1089 mNodeMap.put(anim, node);
1096 * A Node is an embodiment of both the Animator that it wraps as well as
1097 * any dependencies that are associated with that Animation. This includes
1098 * both dependencies upon other nodes (in the dependencies list) as
1099 * well as dependencies of other nodes upon this (in the nodeDependents list).
1101 private static class Node implements Cloneable {
1102 Animator mAnimation;
1105 * Child nodes are the nodes associated with animations that will be played immediately
1106 * after current node.
1108 ArrayList<Node> mChildNodes = null;
1111 * Temporary field to hold the clone in AnimatorSet#clone. Cleaned after clone is complete
1113 private Node mTmpClone = null;
1116 * Flag indicating whether the animation in this node is finished. This flag
1117 * is used by AnimatorSet to check, as each animation ends, whether all child animations
1118 * are mEnded and it's time to send out an end event for the entire AnimatorSet.
1120 boolean mEnded = false;
1123 * Nodes with animations that are defined to play simultaneously with the animation
1124 * associated with this current node.
1126 ArrayList<Node> mSiblings;
1129 * Parent nodes are the nodes with animations preceding current node's animation. Parent
1130 * nodes here are derived from user defined animation sequence.
1132 ArrayList<Node> mParents;
1135 * Latest parent is the parent node associated with a animation that finishes after all
1136 * the other parents' animations.
1138 Node mLatestParent = null;
1140 boolean mParentsAdded = false;
1141 long mStartTime = 0;
1143 long mTotalDuration = 0;
1146 * Constructs the Node with the animation that it encapsulates. A Node has no
1147 * dependencies by default; dependencies are added via the addDependency()
1150 * @param animation The animation that the Node encapsulates.
1152 public Node(Animator animation) {
1153 this.mAnimation = animation;
1157 public Node clone() {
1159 Node node = (Node) super.clone();
1160 node.mAnimation = mAnimation.clone();
1161 if (mChildNodes != null) {
1162 node.mChildNodes = new ArrayList<>(mChildNodes);
1164 if (mSiblings != null) {
1165 node.mSiblings = new ArrayList<>(mSiblings);
1167 if (mParents != null) {
1168 node.mParents = new ArrayList<>(mParents);
1170 node.mEnded = false;
1172 } catch (CloneNotSupportedException e) {
1173 throw new AssertionError();
1177 void addChild(Node node) {
1178 if (mChildNodes == null) {
1179 mChildNodes = new ArrayList<>();
1181 if (!mChildNodes.contains(node)) {
1182 mChildNodes.add(node);
1183 node.addParent(this);
1187 public void addSibling(Node node) {
1188 if (mSiblings == null) {
1189 mSiblings = new ArrayList<Node>();
1191 if (!mSiblings.contains(node)) {
1192 mSiblings.add(node);
1193 node.addSibling(this);
1197 public void addParent(Node node) {
1198 if (mParents == null) {
1199 mParents = new ArrayList<Node>();
1201 if (!mParents.contains(node)) {
1203 node.addChild(this);
1207 public void addParents(ArrayList<Node> parents) {
1208 if (parents == null) {
1211 int size = parents.size();
1212 for (int i = 0; i < size; i++) {
1213 addParent(parents.get(i));
1219 * The <code>Builder</code> object is a utility class to facilitate adding animations to a
1220 * <code>AnimatorSet</code> along with the relationships between the various animations. The
1221 * intention of the <code>Builder</code> methods, along with the {@link
1222 * AnimatorSet#play(Animator) play()} method of <code>AnimatorSet</code> is to make it possible
1223 * to express the dependency relationships of animations in a natural way. Developers can also
1224 * use the {@link AnimatorSet#playTogether(Animator[]) playTogether()} and {@link
1225 * AnimatorSet#playSequentially(Animator[]) playSequentially()} methods if these suit the need,
1226 * but it might be easier in some situations to express the AnimatorSet of animations in pairs.
1228 * <p>The <code>Builder</code> object cannot be constructed directly, but is rather constructed
1229 * internally via a call to {@link AnimatorSet#play(Animator)}.</p>
1231 * <p>For example, this sets up a AnimatorSet to play anim1 and anim2 at the same time, anim3 to
1232 * play when anim2 finishes, and anim4 to play when anim3 finishes:</p>
1234 * AnimatorSet s = new AnimatorSet();
1235 * s.play(anim1).with(anim2);
1236 * s.play(anim2).before(anim3);
1237 * s.play(anim4).after(anim3);
1240 * <p>Note in the example that both {@link Builder#before(Animator)} and {@link
1241 * Builder#after(Animator)} are used. These are just different ways of expressing the same
1242 * relationship and are provided to make it easier to say things in a way that is more natural,
1243 * depending on the situation.</p>
1245 * <p>It is possible to make several calls into the same <code>Builder</code> object to express
1246 * multiple relationships. However, note that it is only the animation passed into the initial
1247 * {@link AnimatorSet#play(Animator)} method that is the dependency in any of the successive
1248 * calls to the <code>Builder</code> object. For example, the following code starts both anim2
1249 * and anim3 when anim1 ends; there is no direct dependency relationship between anim2 and
1252 * AnimatorSet s = new AnimatorSet();
1253 * s.play(anim1).before(anim2).before(anim3);
1255 * If the desired result is to play anim1 then anim2 then anim3, this code expresses the
1256 * relationship correctly:</p>
1258 * AnimatorSet s = new AnimatorSet();
1259 * s.play(anim1).before(anim2);
1260 * s.play(anim2).before(anim3);
1263 * <p>Note that it is possible to express relationships that cannot be resolved and will not
1264 * result in sensible results. For example, <code>play(anim1).after(anim1)</code> makes no
1265 * sense. In general, circular dependencies like this one (or more indirect ones where a depends
1266 * on b, which depends on c, which depends on a) should be avoided. Only create AnimatorSets
1267 * that can boil down to a simple, one-way relationship of animations starting with, before, and
1268 * after other, different, animations.</p>
1270 public class Builder {
1273 * This tracks the current node being processed. It is supplied to the play() method
1274 * of AnimatorSet and passed into the constructor of Builder.
1276 private Node mCurrentNode;
1279 * package-private constructor. Builders are only constructed by AnimatorSet, when the
1280 * play() method is called.
1282 * @param anim The animation that is the dependency for the other animations passed into
1283 * the other methods of this Builder object.
1285 Builder(Animator anim) {
1286 mDependencyDirty = true;
1287 mCurrentNode = getNodeForAnimation(anim);
1291 * Sets up the given animation to play at the same time as the animation supplied in the
1292 * {@link AnimatorSet#play(Animator)} call that created this <code>Builder</code> object.
1294 * @param anim The animation that will play when the animation supplied to the
1295 * {@link AnimatorSet#play(Animator)} method starts.
1297 public Builder with(Animator anim) {
1298 Node node = getNodeForAnimation(anim);
1299 mCurrentNode.addSibling(node);
1304 * Sets up the given animation to play when the animation supplied in the
1305 * {@link AnimatorSet#play(Animator)} call that created this <code>Builder</code> object
1308 * @param anim The animation that will play when the animation supplied to the
1309 * {@link AnimatorSet#play(Animator)} method ends.
1311 public Builder before(Animator anim) {
1312 mReversible = false;
1313 Node node = getNodeForAnimation(anim);
1314 mCurrentNode.addChild(node);
1319 * Sets up the given animation to play when the animation supplied in the
1320 * {@link AnimatorSet#play(Animator)} call that created this <code>Builder</code> object
1321 * to start when the animation supplied in this method call ends.
1323 * @param anim The animation whose end will cause the animation supplied to the
1324 * {@link AnimatorSet#play(Animator)} method to play.
1326 public Builder after(Animator anim) {
1327 mReversible = false;
1328 Node node = getNodeForAnimation(anim);
1329 mCurrentNode.addParent(node);
1334 * Sets up the animation supplied in the
1335 * {@link AnimatorSet#play(Animator)} call that created this <code>Builder</code> object
1336 * to play when the given amount of time elapses.
1338 * @param delay The number of milliseconds that should elapse before the
1341 public Builder after(long delay) {
1342 // setup dummy ValueAnimator just to run the clock
1343 ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
1344 anim.setDuration(delay);