FileDocCategorySizeDatePackage
AnimatorSet.javaAPI DocAndroid 5.1 API50264Thu Mar 12 22:22:08 GMT 2015android.animation

AnimatorSet

public final class AnimatorSet extends Animator
This class plays a set of {@link Animator} objects in the specified order. Animations can be set up to play together, in sequence, or after a specified delay.

There are two different approaches to adding animations to a AnimatorSet: either the {@link AnimatorSet#playTogether(Animator[]) playTogether()} or {@link AnimatorSet#playSequentially(Animator[]) playSequentially()} methods can be called to add a set of animations all at once, or the {@link AnimatorSet#play(Animator)} can be used in conjunction with methods in the {@link AnimatorSet.Builder Builder} class to add animations one by one.

It is possible to set up a AnimatorSet with circular dependencies between its animations. For example, an animation a1 could be set up to start before animation a2, a2 before a3, and a3 before a1. The results of this configuration are undefined, but will typically result in none of the affected animations being played. Because of this (and because circular dependencies do not make logical sense anyway), circular dependencies should be avoided, and the dependency flow of animations should only be in one direction.

Developer Guides

For more information about animating with {@code AnimatorSet}, read the Property Animation developer guide.

Fields Summary
private ArrayList
mPlayingSet
Tracks animations currently being played, so that we know what to cancel or end when cancel() or end() is called on this AnimatorSet
private HashMap
mNodeMap
Contains all nodes, mapped to their respective Animators. When new dependency information is added for an Animator, we want to add it to a single node representing that Animator, not create a new Node if one already exists.
private ArrayList
mNodes
Set of all nodes created for this AnimatorSet. This list is used upon starting the set, and the nodes are placed in sorted order into the sortedNodes collection.
private ArrayList
mSortedNodes
The sorted list of nodes. This is the order in which the animations will be played. The details about when exactly they will be played depend on the dependency relationships of the nodes.
private boolean
mNeedsSort
Flag indicating whether the nodes should be sorted prior to playing. This flag allows us to cache the previous sorted nodes so that if the sequence is replayed with no changes, it does not have to re-sort the nodes again.
private AnimatorSetListener
mSetListener
boolean
mTerminated
Flag indicating that the AnimatorSet has been manually terminated (by calling cancel() or end()). This flag is used to avoid starting other animations when currently-playing child animations of this AnimatorSet end. It also determines whether cancel/end notifications are sent out via the normal AnimatorSetListener mechanism.
private boolean
mStarted
Indicates whether an AnimatorSet has been start()'d, whether or not there is a nonzero startDelay.
private long
mStartDelay
private ValueAnimator
mDelayAnim
private long
mDuration
private TimeInterpolator
mInterpolator
private boolean
mReversible
Constructors Summary
Methods Summary
public booleancanReverse()

hide

        if (!mReversible)  {
            return false;
        }
        // Loop to make sure all the Nodes can reverse.
        for (Node node : mNodes) {
            if (!node.animation.canReverse() || node.animation.getStartDelay() > 0) {
                return false;
            }
        }
        return true;
    
public voidcancel()
{@inheritDoc}

Note that canceling a AnimatorSet also cancels all of the animations that it is responsible for.

        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 android.animation.AnimatorSetclone()

        final AnimatorSet anim = (AnimatorSet) super.clone();
        /*
         * The basic clone() operation copies all items. This doesn't work very well for
         * AnimatorSet, because it will copy references that need to be recreated and state
         * that may not apply. What we need to do now is put the clone in an uninitialized
         * state, with fresh, empty data structures. Then we will build up the nodes list
         * manually, as we clone each Node (and its animation). The clone will then be sorted,
         * and will populate any appropriate lists, when it is started.
         */
        final int nodeCount = mNodes.size();
        anim.mNeedsSort = true;
        anim.mTerminated = false;
        anim.mStarted = false;
        anim.mPlayingSet = new ArrayList<Animator>();
        anim.mNodeMap = new HashMap<Animator, Node>();
        anim.mNodes = new ArrayList<Node>(nodeCount);
        anim.mSortedNodes = new ArrayList<Node>(nodeCount);
        anim.mReversible = mReversible;
        anim.mSetListener = null;

        // Walk through the old nodes list, cloning each node and adding it to the new nodemap.
        // One problem is that the old node dependencies point to nodes in the old AnimatorSet.
        // We need to track the old/new nodes in order to reconstruct the dependencies in the clone.

        for (int n = 0; n < nodeCount; n++) {
            final Node node = mNodes.get(n);
            Node nodeClone = node.clone();
            node.mTmpClone = nodeClone;
            anim.mNodes.add(nodeClone);
            anim.mNodeMap.put(nodeClone.animation, nodeClone);
            // Clear out the dependencies in the clone; we'll set these up manually later
            nodeClone.dependencies = null;
            nodeClone.tmpDependencies = null;
            nodeClone.nodeDependents = null;
            nodeClone.nodeDependencies = null;

            // clear out any listeners that were set up by the AnimatorSet; these will
            // be set up when the clone's nodes are sorted
            final ArrayList<AnimatorListener> cloneListeners = nodeClone.animation.getListeners();
            if (cloneListeners != null) {
                for (int i = cloneListeners.size() - 1; i >= 0; i--) {
                    final AnimatorListener listener = cloneListeners.get(i);
                    if (listener instanceof AnimatorSetListener) {
                        cloneListeners.remove(i);
                    }
                }
            }
        }
        // Now that we've cloned all of the nodes, we're ready to walk through their
        // dependencies, mapping the old dependencies to the new nodes
        for (int n = 0; n < nodeCount; n++) {
            final Node node = mNodes.get(n);
            final Node clone = node.mTmpClone;
            if (node.dependencies != null) {
                clone.dependencies = new ArrayList<Dependency>(node.dependencies.size());
                final int depSize = node.dependencies.size();
                for (int i = 0; i < depSize; i ++) {
                    final Dependency dependency = node.dependencies.get(i);
                    Dependency cloneDependency = new Dependency(dependency.node.mTmpClone,
                            dependency.rule);
                    clone.dependencies.add(cloneDependency);
                }
            }
            if (node.nodeDependents != null) {
                clone.nodeDependents = new ArrayList<Node>(node.nodeDependents.size());
                for (Node dep : node.nodeDependents) {
                    clone.nodeDependents.add(dep.mTmpClone);
                }
            }
            if (node.nodeDependencies != null) {
                clone.nodeDependencies = new ArrayList<Node>(node.nodeDependencies.size());
                for (Node dep : node.nodeDependencies) {
                    clone.nodeDependencies.add(dep.mTmpClone);
                }
            }
        }
        for (int n = 0; n < nodeCount; n++) {
            mNodes.get(n).mTmpClone = null;
        }
        return anim;
    
public voidend()
{@inheritDoc}

Note that ending a AnimatorSet also ends all of the animations that it is responsible for.

        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;
        }
    
public intgetChangingConfigurations()

hide

        int conf = super.getChangingConfigurations();
        final int nodeCount = mNodes.size();
        for (int i = 0; i < nodeCount; i ++) {
            conf |= mNodes.get(i).animation.getChangingConfigurations();
        }
        return conf;
    
public java.util.ArrayListgetChildAnimations()
Returns the current list of child Animator objects controlled by this AnimatorSet. This is a copy of the internal list; modifications to the returned list will not affect the AnimatorSet, although changes to the underlying Animator objects will affect those objects being managed by the AnimatorSet.

return
ArrayList The list of child animations of this AnimatorSet.

        ArrayList<Animator> childList = new ArrayList<Animator>();
        for (Node node : mNodes) {
            childList.add(node.animation);
        }
        return childList;
    
public longgetDuration()
Gets the length of each of the child animations of this AnimatorSet. This value may be less than 0, which indicates that no duration has been set on this AnimatorSet and each of the child animations will use their own duration.

return
The length of the animation, in milliseconds, of each of the child animations of this AnimatorSet.

        return mDuration;
    
public TimeInterpolatorgetInterpolator()

        return mInterpolator;
    
public longgetStartDelay()
The amount of time, in milliseconds, to delay starting the animation after {@link #start()} is called.

return
the number of milliseconds to delay running the animation

        return mStartDelay;
    
public booleanisRunning()
Returns true if any of the child animations of this AnimatorSet have been started and have not yet ended.

return
Whether this AnimatorSet has been started and has not yet ended.

        for (Node node : mNodes) {
            if (node.animation.isRunning()) {
                return true;
            }
        }
        return false;
    
public booleanisStarted()

        return mStarted;
    
public voidpause()

        boolean previouslyPaused = mPaused;
        super.pause();
        if (!previouslyPaused && mPaused) {
            if (mDelayAnim != null) {
                mDelayAnim.pause();
            } else {
                for (Node node : mNodes) {
                    node.animation.pause();
                }
            }
        }
    
public android.animation.AnimatorSet$Builderplay(Animator anim)
This method creates a Builder object, which is used to set up playing constraints. This initial play() method tells the Builder the animation that is the dependency for the succeeding commands to the Builder. For example, calling play(a1).with(a2) sets up the AnimatorSet to play a1 and a2 at the same time, play(a1).before(a2) sets up the AnimatorSet to play a1 first, followed by a2, and play(a1).after(a2) sets up the AnimatorSet to play a2 first, followed by a1.

Note that play() is the only way to tell the Builder the animation upon which the dependency is created, so successive calls to the various functions in Builder will all refer to the initial parameter supplied in play() as the dependency of the other animations. For example, calling play(a1).before(a2).before(a3) will play both a2 and a3 when a1 ends; it does not set up a dependency between a2 and a3.

param
anim The animation that is the dependency used in later calls to the methods in the returned Builder object. A null parameter will result in a null Builder return value.
return
Builder The object that constructs the AnimatorSet based on the dependencies outlined in the calls to play and the other methods in the Builder

        if (anim != null) {
            mNeedsSort = true;
            return new Builder(anim);
        }
        return null;
    
public voidplaySequentially(Animator items)
Sets up this AnimatorSet to play each of the supplied animations when the previous animation ends.

param
items The animations that will be started one after another.

        if (items != null) {
            mNeedsSort = true;
            if (items.length == 1) {
                play(items[0]);
            } else {
                mReversible = false;
                for (int i = 0; i < items.length - 1; ++i) {
                    play(items[i]).before(items[i+1]);
                }
            }
        }
    
public voidplaySequentially(java.util.List items)
Sets up this AnimatorSet to play each of the supplied animations when the previous animation ends.

param
items The animations that will be started one after another.

        if (items != null && items.size() > 0) {
            mNeedsSort = true;
            if (items.size() == 1) {
                play(items.get(0));
            } else {
                mReversible = false;
                for (int i = 0; i < items.size() - 1; ++i) {
                    play(items.get(i)).before(items.get(i+1));
                }
            }
        }
    
public voidplayTogether(Animator items)
Sets up this AnimatorSet to play all of the supplied animations at the same time. This is equivalent to calling {@link #play(Animator)} with the first animator in the set and then {@link Builder#with(Animator)} with each of the other animators. Note that an Animator with a {@link Animator#setStartDelay(long) startDelay} will not actually start until that delay elapses, which means that if the first animator in the list supplied to this constructor has a startDelay, none of the other animators will start until that first animator's startDelay has elapsed.

param
items The animations that will be started simultaneously.

                                                                                                         
        
        if (items != null) {
            mNeedsSort = true;
            Builder builder = play(items[0]);
            for (int i = 1; i < items.length; ++i) {
                builder.with(items[i]);
            }
        }
    
public voidplayTogether(java.util.Collection items)
Sets up this AnimatorSet to play all of the supplied animations at the same time.

param
items The animations that will be started simultaneously.

        if (items != null && items.size() > 0) {
            mNeedsSort = true;
            Builder builder = null;
            for (Animator anim : items) {
                if (builder == null) {
                    builder = play(anim);
                } else {
                    builder.with(anim);
                }
            }
        }
    
public voidresume()

        boolean previouslyPaused = mPaused;
        super.resume();
        if (previouslyPaused && !mPaused) {
            if (mDelayAnim != null) {
                mDelayAnim.resume();
            } else {
                for (Node node : mNodes) {
                    node.animation.resume();
                }
            }
        }
    
public voidreverse()

hide

        if (canReverse()) {
            for (Node node : mNodes) {
                node.animation.reverse();
            }
        }
    
public android.animation.AnimatorSetsetDuration(long duration)
Sets the length of each of the current child animations of this AnimatorSet. By default, each child animation will use its own duration. If the duration is set on the AnimatorSet, then each child animation inherits this duration.

param
duration The length of the animation, in milliseconds, of each of the child animations of this AnimatorSet.

        if (duration < 0) {
            throw new IllegalArgumentException("duration must be a value of zero or greater");
        }
        // Just record the value for now - it will be used later when the AnimatorSet starts
        mDuration = duration;
        return this;
    
public voidsetInterpolator(TimeInterpolator interpolator)
Sets the TimeInterpolator for all current {@link #getChildAnimations() child animations} of this AnimatorSet. The default value is null, which means that no interpolator is set on this AnimatorSet. Setting the interpolator to any non-null value will cause that interpolator to be set on the child animations when the set is started.

param
interpolator the interpolator to be used by each child animation of this AnimatorSet

        mInterpolator = interpolator;
    
public voidsetStartDelay(long startDelay)
The amount of time, in milliseconds, to delay starting the animation after {@link #start()} is called.

param
startDelay The amount of the delay, in milliseconds

        if (mStartDelay > 0) {
            mReversible = false;
        }
        mStartDelay = startDelay;
    
public voidsetTarget(java.lang.Object target)
Sets the target object for all current {@link #getChildAnimations() child animations} of this AnimatorSet that take targets ({@link ObjectAnimator} and AnimatorSet).

param
target The object being animated

        for (Node node : mNodes) {
            Animator animation = node.animation;
            if (animation instanceof AnimatorSet) {
                ((AnimatorSet)animation).setTarget(target);
            } else if (animation instanceof ObjectAnimator) {
                ((ObjectAnimator)animation).setTarget(target);
            }
        }
    
public voidsetupEndValues()

        for (Node node : mNodes) {
            node.animation.setupEndValues();
        }
    
public voidsetupStartValues()

        for (Node node : mNodes) {
            node.animation.setupStartValues();
        }
    
private voidsortNodes()
This method sorts the current set of nodes, if needed. The sort is a simple DependencyGraph sort, which goes like this: - All nodes without dependencies become 'roots' - while roots list is not null - for each root r - add r to sorted list - remove r as a dependency from any other node - any nodes with no dependencies are added to the roots list

        if (mNeedsSort) {
            mSortedNodes.clear();
            ArrayList<Node> roots = new ArrayList<Node>();
            int numNodes = mNodes.size();
            for (int i = 0; i < numNodes; ++i) {
                Node node = mNodes.get(i);
                if (node.dependencies == null || node.dependencies.size() == 0) {
                    roots.add(node);
                }
            }
            ArrayList<Node> tmpRoots = new ArrayList<Node>();
            while (roots.size() > 0) {
                int numRoots = roots.size();
                for (int i = 0; i < numRoots; ++i) {
                    Node root = roots.get(i);
                    mSortedNodes.add(root);
                    if (root.nodeDependents != null) {
                        int numDependents = root.nodeDependents.size();
                        for (int j = 0; j < numDependents; ++j) {
                            Node node = root.nodeDependents.get(j);
                            node.nodeDependencies.remove(root);
                            if (node.nodeDependencies.size() == 0) {
                                tmpRoots.add(node);
                            }
                        }
                    }
                }
                roots.clear();
                roots.addAll(tmpRoots);
                tmpRoots.clear();
            }
            mNeedsSort = false;
            if (mSortedNodes.size() != mNodes.size()) {
                throw new IllegalStateException("Circular dependencies cannot exist"
                        + " in AnimatorSet");
            }
        } else {
            // Doesn't need sorting, but still need to add in the nodeDependencies list
            // because these get removed as the event listeners fire and the dependencies
            // are satisfied
            int numNodes = mNodes.size();
            for (int i = 0; i < numNodes; ++i) {
                Node node = mNodes.get(i);
                if (node.dependencies != null && node.dependencies.size() > 0) {
                    int numDependencies = node.dependencies.size();
                    for (int j = 0; j < numDependencies; ++j) {
                        Dependency dependency = node.dependencies.get(j);
                        if (node.nodeDependencies == null) {
                            node.nodeDependencies = new ArrayList<Node>();
                        }
                        if (!node.nodeDependencies.contains(dependency.node)) {
                            node.nodeDependencies.add(dependency.node);
                        }
                    }
                }
                // nodes are 'done' by default; they become un-done when started, and done
                // again when ended
                node.done = false;
            }
        }
    
public voidstart()
{@inheritDoc}

Starting this AnimatorSet 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.

        mTerminated = false;
        mStarted = true;
        mPaused = false;

        for (Node node : mNodes) {
            node.animation.setAllowRunningAsynchronously(false);
        }

        if (mDuration >= 0) {
            // If the duration was set on this AnimatorSet, pass it along to all child animations
            for (Node node : mNodes) {
                // TODO: don't set the duration of the timing-only nodes created by AnimatorSet to
                // insert "play-after" delays
                node.animation.setDuration(mDuration);
            }
        }
        if (mInterpolator != null) {
            for (Node node : mNodes) {
                node.animation.setInterpolator(mInterpolator);
            }
        }
        // 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 = null;
                }
            });
            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);
                }
            }
        }