Project: 2Degrees-Toolbox
Code Examples
/*
 * Copyright (C) 2010 The Android Open Source Project 
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at 
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0 
 * 
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and 
 * limitations under the License. 
 */
 
package com.actionbarsherlock.internal.nineoldandroids.animation; 
 
import java.util.ArrayList; 
import java.util.Collection; 
import java.util.HashMap; 
import java.util.List; 
 
import android.view.animation.Interpolator; 
 
/**
 * 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. 
 * 
 * <p>There are two different approaches to adding animations to a <code>AnimatorSet</code>: 
 * 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.</p> 
 * 
 * <p>It is possible to set up a <code>AnimatorSet</code> 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. 
 */
 
@SuppressWarnings("unchecked"
public final class AnimatorSet extends Animator { 
 
    /**
     * Internal variables 
     * NOTE: This object implements the clone() method, making a deep copy of any referenced 
     * objects. As other non-trivial fields are added to this class, make sure to add logic 
     * to clone() to make deep copies of them. 
     */
 
 
    /**
     * Tracks animations currently being played, so that we know what to 
     * cancel or end when cancel() or end() is called on this AnimatorSet 
     */
 
    private ArrayList<Animator> mPlayingSet = new ArrayList<Animator>(); 
 
    /**
     * 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 HashMap<Animator, Node> mNodeMap = new HashMap<Animator, Node>(); 
 
    /**
     * 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<Node> mNodes = new ArrayList<Node>(); 
 
    /**
     * 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 ArrayList<Node> mSortedNodes = new ArrayList<Node>(); 
 
    /**
     * 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 boolean mNeedsSort = true
 
    private AnimatorSetListener mSetListener = null
 
    /**
     * 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. 
     */
 
    boolean mTerminated = false
 
    /**
     * Indicates whether an AnimatorSet has been start()'d, whether or 
     * not there is a nonzero startDelay. 
     */
 
    private boolean mStarted = false
 
    // The amount of time in ms to delay starting the animation after start() is called 
    private long mStartDelay = 0
 
    // Animator used for a nonzero startDelay 
    private ValueAnimator mDelayAnim = null
 
 
    // How long the child animations should last in ms. The default value is negative, which 
    // simply means that there is no duration set on the AnimatorSet. When a real duration is 
    // set, it is passed along to the child animations. 
    private long mDuration = -1
 
 
    /**
     * Sets up this AnimatorSet to play all of the supplied animations at the same time. 
     * 
     * @param items The animations that will be started simultaneously. 
     */
 
    public void playTogether(Animator... items) { 
        if (items != null) { 
            mNeedsSort = true
            Builder builder = play(items[0]); 
            for (int i = 1; i < items.length; ++i) { 
                builder.with(items[i]); 
            } 
        } 
    } 
 
    /**
     * Sets up this AnimatorSet to play all of the supplied animations at the same time. 
     * 
     * @param items The animations that will be started simultaneously. 
     */
 
    public void playTogether(Collection<Animator> items) { 
        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); 
                } 
            } 
        } 
    } 
 
    /**
     * 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. 
     */
 
    public void playSequentially(Animator... items) { 
        if (items != null) { 
            mNeedsSort = true
            if (items.length == 1) { 
                play(items[0]); 
            } else { 
                for (int i = 0; i < items.length - 1; ++i) { 
                    play(items[i]).before(items[i+1]); 
                } 
            } 
        } 
    } 
 
    /**
     * 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. 
     */
 
    public void playSequentially(List<Animator> items) { 
        if (items != null && items.size() > 0) { 
            mNeedsSort = true
            if (items.size() == 1) { 
                play(items.get(0)); 
            } else { 
                for (int i = 0; i < items.size() - 1; ++i) { 
                    play(items.get(i)).before(items.get(i+1)); 
                } 
            } 
        } 
    } 
 
    /**
     * 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<Animator> The list of child animations of this AnimatorSet. 
     */
 
    public ArrayList<Animator> getChildAnimations() { 
        ArrayList<Animator> childList = new ArrayList<Animator>(); 
        for (Node node : mNodes) { 
            childList.add(node.animation); 
        } 
        return childList; 
    } 
 
    /**
     * 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 
     */
 
    @Override 
    public void setTarget(Object target) { 
        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); 
            } 
        } 
    } 
 
    /**
     * Sets the TimeInterpolator for all current {@link #getChildAnimations() child animations} 
     * of this AnimatorSet. 
     * 
     * @param interpolator the interpolator to be used by each child animation of this AnimatorSet 
     */
 
    @Override 
    public void setInterpolator(/*Time*/Interpolator interpolator) { 
        for (Node node : mNodes) { 
            node.animation.setInterpolator(interpolator); 
        } 
    } 
 
    /**
     * This method creates a <code>Builder</code> object, which is used to 
     * set up playing constraints. This initial <code>play()</code> method 
     * tells the <code>Builder</code> the animation that is the dependency for 
     * the succeeding commands to the <code>Builder</code>. For example, 
     * calling <code>play(a1).with(a2)</code> sets up the AnimatorSet to play 
     * <code>a1</code> and <code>a2</code> at the same time, 
     * <code>play(a1).before(a2)</code> sets up the AnimatorSet to play 
     * <code>a1</code> first, followed by <code>a2</code>, and 
     * <code>play(a1).after(a2)</code> sets up the AnimatorSet to play 
     * <code>a2</code> first, followed by <code>a1</code>. 
     * 
     * <p>Note that <code>play()</code> is the only way to tell the 
     * <code>Builder</code> the animation upon which the dependency is created, 
     * so successive calls to the various functions in <code>Builder</code> 
     * will all refer to the initial parameter supplied in <code>play()</code> 
     * as the dependency of the other animations. For example, calling 
     * <code>play(a1).before(a2).before(a3)</code> will play both <code>a2</code> 
     * and <code>a3</code> when a1 ends; it does not set up a dependency between 
     * <code>a2</code> and <code>a3</code>.</p> 
     * 
     * @param anim The animation that is the dependency used in later calls to the 
     * methods in the returned <code>Builder</code> object. A null parameter will result 
     * in a null <code>Builder</code> return value. 
     * @return Builder The object that constructs the AnimatorSet based on the dependencies 
     * outlined in the calls to <code>play</code> and the other methods in the 
     * <code>Builder</code object. 
     */
 
    public Builder play(Animator anim) { 
        if (anim != null) { 
            mNeedsSort = true
            return new Builder(anim); 
        } 
        return null
    } 
 
    /**
     * {@inheritDoc} 
     * 
     * <p>Note that canceling a <code>AnimatorSet</code> also cancels all of the animations that it 
     * is responsible for.</p> 
     */
 
    @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
        } 
    } 
 
    /**
     * {@inheritDoc} 
     * 
     * <p>Note that ending a <code>AnimatorSet</code> also ends all of the animations that it is 
     * responsible for.</p> 
     */
 
    @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
        } 
    } 
 
    /**
     * 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. 
     */
 
    @Override 
    public boolean isRunning() { 
        for (Node node : mNodes) { 
            if (node.animation.isRunning()) { 
                return true
            } 
        } 
        return false
    } 
 
    @Override 
    public boolean isStarted() { 
        return mStarted; 
    } 
 
    /**
     * 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 
     */
 
    @Override 
    public long getStartDelay() { 
        return mStartDelay; 
    } 
 
    /**
     * 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 
     */
 
    @Override 
    public void setStartDelay(long startDelay) { 
        mStartDelay = startDelay; 
    } 
 
    /**
     * 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. 
     */
 
    @Override 
    public long getDuration() { 
        return mDuration; 
    } 
 
    /**
     * 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. 
     */
 
    @Override 
    public AnimatorSet setDuration(long duration) { 
        if (duration < 0) { 
            throw new IllegalArgumentException("duration must be a value of zero or greater"); 
        } 
        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(duration); 
        } 
        mDuration = duration; 
        return this
    } 
 
    @Override 
    public void setupStartValues() { 
        for (Node node : mNodes) { 
            node.animation.setupStartValues(); 
        } 
    } 
 
    @Override 
    public void setupEndValues() { 
        for (Node node : mNodes) { 
            node.animation.setupEndValues(); 
        } 
    } 
 
    /**
     * {@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(0f1f); 
            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); 
                } 
            } 
        } 
    } 
 
    @Override 
    public AnimatorSet clone() { 
        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. 
         */
 
        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>(); 
        anim.mSortedNodes = new ArrayList<Node>(); 
 
        // 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. 
        HashMap<Node, Node> nodeCloneMap = new HashMap<Node, Node>(); // <old, new> 
        for (Node node : mNodes) { 
            Node nodeClone = node.clone(); 
            nodeCloneMap.put(node, 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 
            ArrayList<AnimatorListener> cloneListeners = nodeClone.animation.getListeners(); 
            if (cloneListeners != null) { 
                ArrayList<AnimatorListener> listenersToRemove = null
                for (AnimatorListener listener : cloneListeners) { 
                    if (listener instanceof AnimatorSetListener) { 
                        if (listenersToRemove == null) { 
                            listenersToRemove = new ArrayList<AnimatorListener>(); 
                        } 
                        listenersToRemove.add(listener); 
                    } 
                } 
                if (listenersToRemove != null) { 
                    for (AnimatorListener listener : listenersToRemove) { 
                        cloneListeners.remove(listener); 
                    } 
                } 
            } 
        } 
        // 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 (Node node : mNodes) { 
            Node nodeClone = nodeCloneMap.get(node); 
            if (node.dependencies != null) { 
                for (Dependency dependency : node.dependencies) { 
                    Node clonedDependencyNode = nodeCloneMap.get(dependency.node); 
                    Dependency cloneDependency = new Dependency(clonedDependencyNode, 
                            dependency.rule); 
                    nodeClone.addDependency(cloneDependency); 
                } 
            } 
        } 
 
        return anim; 
    } 
 
    /**
     * This class is the mechanism by which animations are started based on events in other 
     * animations. If an animation has multiple dependencies on other animations, then 
     * all dependencies must be satisfied before the animation is started. 
     */
 
    private static class DependencyListener implements AnimatorListener { 
 
        private AnimatorSet mAnimatorSet; 
 
        // The node upon which the dependency is based. 
        private Node mNode; 
 
        // The Dependency rule (WITH or AFTER) that the listener should wait for on 
        // the node 
        private int mRule; 
 
        public DependencyListener(AnimatorSet animatorSet, Node node, int rule) { 
            this.mAnimatorSet = animatorSet; 
            this.mNode = node; 
            this.mRule = rule; 
        } 
 
        /**
         * Ignore cancel events for now. We may want to handle this eventually, 
         * to prevent follow-on animations from running when some dependency 
         * animation is canceled. 
         */
 
        public void onAnimationCancel(Animator animation) { 
        } 
 
        /**
         * An end event is received - see if this is an event we are listening for 
         */
 
        public void onAnimationEnd(Animator animation) { 
            if (mRule == Dependency.AFTER) { 
                startIfReady(animation); 
            } 
        } 
 
        /**
         * Ignore repeat events for now 
         */
 
        public void onAnimationRepeat(Animator animation) { 
        } 
 
        /**
         * A start event is received - see if this is an event we are listening for 
         */
 
        public void onAnimationStart(Animator animation) { 
            if (mRule == Dependency.WITH) { 
                startIfReady(animation); 
            } 
        } 
 
        /**
         * Check whether the event received is one that the node was waiting for. 
         * If so, mark it as complete and see whether it's time to start 
         * the animation. 
         * @param dependencyAnimation the animation that sent the event. 
         */
 
        private void startIfReady(Animator dependencyAnimation) { 
            if (mAnimatorSet.mTerminated) { 
                // if the parent AnimatorSet was canceled, then don't start any dependent anims 
                return
            } 
            Dependency dependencyToRemove = null
            int numDependencies = mNode.tmpDependencies.size(); 
            for (int i = 0; i < numDependencies; ++i) { 
                Dependency dependency = mNode.tmpDependencies.get(i); 
                if (dependency.rule == mRule && 
                        dependency.node.animation == dependencyAnimation) { 
                    // rule fired - remove the dependency and listener and check to 
                    // see whether it's time to start the animation 
                    dependencyToRemove = dependency; 
                    dependencyAnimation.removeListener(this); 
                    break
                } 
            } 
            mNode.tmpDependencies.remove(dependencyToRemove); 
            if (mNode.tmpDependencies.size() == 0) { 
                // all dependencies satisfied: start the animation 
                mNode.animation.start(); 
                mAnimatorSet.mPlayingSet.add(mNode.animation); 
            } 
        } 
 
    } 
 
    private class AnimatorSetListener implements AnimatorListener { 
 
        private AnimatorSet mAnimatorSet; 
 
        AnimatorSetListener(AnimatorSet animatorSet) { 
            mAnimatorSet = animatorSet; 
        } 
 
        public void onAnimationCancel(Animator animation) { 
            if (!mTerminated) { 
                // Listeners are already notified of the AnimatorSet canceling in cancel(). 
                // The logic below only kicks in when animations end normally 
                if (mPlayingSet.size() == 0) { 
                    if (mListeners != null) { 
                        int numListeners = mListeners.size(); 
                        for (int i = 0; i < numListeners; ++i) { 
                            mListeners.get(i).onAnimationCancel(mAnimatorSet); 
                        } 
                    } 
                } 
            } 
        } 
 
        public void onAnimationEnd(Animator animation) { 
            animation.removeListener(this); 
            mPlayingSet.remove(animation); 
            Node animNode = mAnimatorSet.mNodeMap.get(animation); 
            animNode.done = true
            if (!mTerminated) { 
                // Listeners are already notified of the AnimatorSet ending in cancel() or 
                // end(); the logic below only kicks in when animations end normally 
                ArrayList<Node> sortedNodes = mAnimatorSet.mSortedNodes; 
                boolean allDone = true
                int numSortedNodes = sortedNodes.size(); 
                for (int i = 0; i < numSortedNodes; ++i) { 
                    if (!sortedNodes.get(i).done) { 
                        allDone = false
                        break
                    } 
                } 
                if (allDone) { 
                    // If this was the last child animation to end, then notify listeners that this 
                    // AnimatorSet has ended 
                    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(mAnimatorSet); 
                        } 
                    } 
                    mAnimatorSet.mStarted = false
                } 
            } 
        } 
 
        // Nothing to do 
        public void onAnimationRepeat(Animator animation) { 
        } 
 
        // Nothing to do 
        public void onAnimationStart(Animator animation) { 
        } 
 
    } 
 
    /**
     * 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 
     */
 
    private void sortNodes() { 
        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
            } 
        } 
    } 
 
    /**
     * Dependency holds information about the node that some other node is 
     * dependent upon and the nature of that dependency. 
     * 
     */
 
    private static class Dependency { 
        static final int WITH = 0// dependent node must start with this dependency node 
        static final int AFTER = 1// dependent node must start when this dependency node finishes 
 
        // The node that the other node with this Dependency is dependent upon 
        public Node node; 
 
        // The nature of the dependency (WITH or AFTER) 
        public int rule; 
 
        public Dependency(Node node, int rule) { 
            this.node = node; 
            this.rule = rule; 
        } 
    } 
 
    /**
     * A Node is an embodiment of both the Animator that it wraps as well as 
     * any dependencies that are associated with that Animation. This includes 
     * both dependencies upon other nodes (in the dependencies list) as 
     * well as dependencies of other nodes upon this (in the nodeDependents list). 
     */
 
    private static class Node implements Cloneable { 
        public Animator animation; 
 
        /**
         *  These are the dependencies that this node's animation has on other 
         *  nodes. For example, if this node's animation should begin with some 
         *  other animation ends, then there will be an item in this node's 
         *  dependencies list for that other animation's node. 
         */
 
        public ArrayList<Dependency> dependencies = null
 
        /**
         * tmpDependencies is a runtime detail. We use the dependencies list for sorting. 
         * But we also use the list to keep track of when multiple dependencies are satisfied, 
         * but removing each dependency as it is satisfied. We do not want to remove 
         * the dependency itself from the list, because we need to retain that information 
         * if the AnimatorSet is launched in the future. So we create a copy of the dependency 
         * list when the AnimatorSet starts and use this tmpDependencies list to track the 
         * list of satisfied dependencies. 
         */
 
        public ArrayList<Dependency> tmpDependencies = null
 
        /**
         * nodeDependencies is just a list of the nodes that this Node is dependent upon. 
         * This information is used in sortNodes(), to determine when a node is a root. 
         */
 
        public ArrayList<Node> nodeDependencies = null
 
        /**
         * nodeDepdendents is the list of nodes that have this node as a dependency. This 
         * is a utility field used in sortNodes to facilitate removing this node as a 
         * dependency when it is a root node. 
         */
 
        public ArrayList<Node> nodeDependents = null
 
        /**
         * Flag indicating whether the animation in this node is finished. This flag 
         * is used by AnimatorSet to check, as each animation ends, whether all child animations 
         * are done and it's time to send out an end event for the entire AnimatorSet. 
         */
 
        public boolean done = false
 
        /**
         * Constructs the Node with the animation that it encapsulates. A Node has no 
         * dependencies by default; dependencies are added via the addDependency() 
         * method. 
         * 
         * @param animation The animation that the Node encapsulates. 
         */
 
        public Node(Animator animation) { 
            this.animation = animation; 
        } 
 
        /**
         * Add a dependency to this Node. The dependency includes information about the 
         * node that this node is dependency upon and the nature of the dependency. 
         * @param dependency 
         */
 
        public void addDependency(Dependency dependency) { 
            if (dependencies == null) { 
                dependencies = new ArrayList<Dependency>(); 
                nodeDependencies = new ArrayList<Node>(); 
            } 
            dependencies.add(dependency); 
            if (!nodeDependencies.contains(dependency.node)) { 
                nodeDependencies.add(dependency.node); 
            } 
            Node dependencyNode = dependency.node; 
            if (dependencyNode.nodeDependents == null) { 
                dependencyNode.nodeDependents = new ArrayList<Node>(); 
            } 
            dependencyNode.nodeDependents.add(this); 
        } 
 
        @Override 
        public Node clone() { 
            try { 
                Node node = (Node) super.clone(); 
                node.animation = animation.clone(); 
                return node; 
            } catch (CloneNotSupportedException e) { 
               throw new AssertionError(); 
            } 
        } 
    } 
 
    /**
     * The <code>Builder</code> object is a utility class to facilitate adding animations to a 
     * <code>AnimatorSet</code> along with the relationships between the various animations. The 
     * intention of the <code>Builder</code> methods, along with the {@link 
     * AnimatorSet#play(Animator) play()} method of <code>AnimatorSet</code> is to make it possible 
     * to express the dependency relationships of animations in a natural way. Developers can also 
     * use the {@link AnimatorSet#playTogether(Animator[]) playTogether()} and {@link 
     * AnimatorSet#playSequentially(Animator[]) playSequentially()} methods if these suit the need, 
     * but it might be easier in some situations to express the AnimatorSet of animations in pairs. 
     * <p/> 
     * <p>The <code>Builder</code> object cannot be constructed directly, but is rather constructed 
     * internally via a call to {@link AnimatorSet#play(Animator)}.</p> 
     * <p/> 
     * <p>For example, this sets up a AnimatorSet to play anim1 and anim2 at the same time, anim3 to 
     * play when anim2 finishes, and anim4 to play when anim3 finishes:</p> 
     * <pre> 
     *     AnimatorSet s = new AnimatorSet(); 
     *     s.play(anim1).with(anim2); 
     *     s.play(anim2).before(anim3); 
     *     s.play(anim4).after(anim3); 
     * </pre> 
     * <p/> 
     * <p>Note in the example that both {@link Builder#before(Animator)} and {@link 
     * Builder#after(Animator)} are used. These are just different ways of expressing the same 
     * relationship and are provided to make it easier to say things in a way that is more natural, 
     * depending on the situation.</p> 
     * <p/> 
     * <p>It is possible to make several calls into the same <code>Builder</code> object to express 
     * multiple relationships. However, note that it is only the animation passed into the initial 
     * {@link AnimatorSet#play(Animator)} method that is the dependency in any of the successive 
     * calls to the <code>Builder</code> object. For example, the following code starts both anim2 
     * and anim3 when anim1 ends; there is no direct dependency relationship between anim2 and 
     * anim3: 
     * <pre> 
     *   AnimatorSet s = new AnimatorSet(); 
     *   s.play(anim1).before(anim2).before(anim3); 
     * </pre> 
     * If the desired result is to play anim1 then anim2 then anim3, this code expresses the 
     * relationship correctly:</p> 
     * <pre> 
     *   AnimatorSet s = new AnimatorSet(); 
     *   s.play(anim1).before(anim2); 
     *   s.play(anim2).before(anim3); 
     * </pre> 
     * <p/> 
     * <p>Note that it is possible to express relationships that cannot be resolved and will not 
     * result in sensible results. For example, <code>play(anim1).after(anim1)</code> makes no 
     * sense. In general, circular dependencies like this one (or more indirect ones where a depends 
     * on b, which depends on c, which depends on a) should be avoided. Only create AnimatorSets 
     * that can boil down to a simple, one-way relationship of animations starting with, before, and 
     * after other, different, animations.</p> 
     */
 
    public class Builder { 
 
        /**
         * This tracks the current node being processed. It is supplied to the play() method 
         * of AnimatorSet and passed into the constructor of Builder. 
         */
 
        private Node mCurrentNode; 
 
        /**
         * package-private constructor. Builders are only constructed by AnimatorSet, when the 
         * play() method is called. 
         * 
         * @param anim The animation that is the dependency for the other animations passed into 
         * the other methods of this Builder object. 
         */
 
        Builder(Animator anim) { 
            mCurrentNode = mNodeMap.get(anim); 
            if (mCurrentNode == null) { 
                mCurrentNode = new Node(anim); 
                mNodeMap.put(anim, mCurrentNode); 
                mNodes.add(mCurrentNode); 
            } 
        } 
 
        /**
         * Sets up the given animation to play at the same time as the animation supplied in the 
         * {@link AnimatorSet#play(Animator)} call that created this <code>Builder</code> object. 
         * 
         * @param anim The animation that will play when the animation supplied to the 
         * {@link AnimatorSet#play(Animator)} method starts. 
         */
 
        public Builder with(Animator anim) { 
            Node node = mNodeMap.get(anim); 
            if (node == null) { 
                node = new Node(anim); 
                mNodeMap.put(anim, node); 
                mNodes.add(node); 
            } 
            Dependency dependency = new Dependency(mCurrentNode, Dependency.WITH); 
            node.addDependency(dependency); 
            return this
        } 
 
        /**
         * Sets up the given animation to play when the animation supplied in the 
         * {@link AnimatorSet#play(Animator)} call that created this <code>Builder</code> object 
         * ends. 
         * 
         * @param anim The animation that will play when the animation supplied to the 
         * {@link AnimatorSet#play(Animator)} method ends. 
         */
 
        public Builder before(Animator anim) { 
            Node node = mNodeMap.get(anim); 
            if (node == null) { 
                node = new Node(anim); 
                mNodeMap.put(anim, node); 
                mNodes.add(node); 
            } 
            Dependency dependency = new Dependency(mCurrentNode, Dependency.AFTER); 
            node.addDependency(dependency); 
            return this
        } 
 
        /**
         * Sets up the given animation to play when the animation supplied in the 
         * {@link AnimatorSet#play(Animator)} call that created this <code>Builder</code> object 
         * to start when the animation supplied in this method call ends. 
         * 
         * @param anim The animation whose end will cause the animation supplied to the 
         * {@link AnimatorSet#play(Animator)} method to play. 
         */
 
        public Builder after(Animator anim) { 
            Node node = mNodeMap.get(anim); 
            if (node == null) { 
                node = new Node(anim); 
                mNodeMap.put(anim, node); 
                mNodes.add(node); 
            } 
            Dependency dependency = new Dependency(node, Dependency.AFTER); 
            mCurrentNode.addDependency(dependency); 
            return this
        } 
 
        /**
         * Sets up the animation supplied in the 
         * {@link 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(0f1f); 
            anim.setDuration(delay); 
            after(anim); 
            return this
        } 
 
    } 
 
}