Fields Summary |
---|
private ArrayList | mPlayingSetTracks animations currently being played, so that we know what to
cancel or end when cancel() or end() is called on this AnimatorSet |
private HashMap | mNodeMapContains 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 | mNodesSet 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 | mSortedNodesThe 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 | mNeedsSortFlag 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 | mTerminatedFlag 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 | mStartedIndicates 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 |
Methods Summary |
---|
public boolean | canReverse()
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 void | cancel(){@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.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.
*/
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 void | end(){@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 int | getChangingConfigurations()
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.ArrayList | getChildAnimations()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.
ArrayList<Animator> childList = new ArrayList<Animator>();
for (Node node : mNodes) {
childList.add(node.animation);
}
return childList;
|
public long | getDuration()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 mDuration;
|
public TimeInterpolator | getInterpolator()
return mInterpolator;
|
public long | getStartDelay()The amount of time, in milliseconds, to delay starting the animation after
{@link #start()} is called.
return mStartDelay;
|
public boolean | isRunning()Returns true if any of the child animations of this AnimatorSet have been started and have
not yet ended.
for (Node node : mNodes) {
if (node.animation.isRunning()) {
return true;
}
}
return false;
|
public boolean | isStarted()
return mStarted;
|
public void | pause()
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$Builder | play(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 .
if (anim != null) {
mNeedsSort = true;
return new Builder(anim);
}
return null;
|
public void | playSequentially(Animator items)Sets up this AnimatorSet to play each of the supplied animations when the
previous animation ends.
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 void | playSequentially(java.util.List items)Sets up this AnimatorSet to play each of the supplied animations when the
previous animation ends.
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 void | playTogether(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.
if (items != null) {
mNeedsSort = true;
Builder builder = play(items[0]);
for (int i = 1; i < items.length; ++i) {
builder.with(items[i]);
}
}
|
public void | playTogether(java.util.Collection items)Sets up this AnimatorSet to play all of the supplied animations at the same time.
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 void | resume()
boolean previouslyPaused = mPaused;
super.resume();
if (previouslyPaused && !mPaused) {
if (mDelayAnim != null) {
mDelayAnim.resume();
} else {
for (Node node : mNodes) {
node.animation.resume();
}
}
}
|
public void | reverse()
if (canReverse()) {
for (Node node : mNodes) {
node.animation.reverse();
}
}
|
public android.animation.AnimatorSet | setDuration(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.
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 void | setInterpolator(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.
mInterpolator = interpolator;
|
public void | setStartDelay(long startDelay)The amount of time, in milliseconds, to delay starting the animation after
{@link #start()} is called.
if (mStartDelay > 0) {
mReversible = false;
}
mStartDelay = startDelay;
|
public void | setTarget(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).
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 void | setupEndValues()
for (Node node : mNodes) {
node.animation.setupEndValues();
}
|
public void | setupStartValues()
for (Node node : mNodes) {
node.animation.setupStartValues();
}
|
private void | sortNodes()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 void | start(){@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);
}
}
}
|