/* * 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 android.animation; import android.content.Context; import android.content.res.TypedArray; import android.util.AttributeSet; import android.view.animation.AnimationUtils; import java.util.ArrayList; import java.util.HashMap; /** * This class plays a set of {@link Animatable} 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 Sequencer
:
* either the {@link Sequencer#playTogether(Animatable[]) playTogether()} or
* {@link Sequencer#playSequentially(Animatable[]) playSequentially()} methods can be called to add
* a set of animations all at once, or the {@link Sequencer#play(Animatable)} can be
* used in conjunction with methods in the {@link android.animation.Sequencer.Builder Builder}
* class to add animations
* one by one.
It is possible to set up a Note that Sequencer
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.
*/
public final class Sequencer extends Animatable {
/**
* Tracks animations currently being played, so that we know what to
* cancel or end when cancel() or end() is called on this Sequencer
*/
private final ArrayListBuilder
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 Sequence to play
* a1
and a2
at the same time,
* play(a1).before(a2)
sets up the Sequence to play
* a1
first, followed by a2
, and
* play(a1).after(a2)
sets up the Sequence to play
* a2
first, followed by a1
.
*
* 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
.Builder
object. A null parameter will result
* in a null Builder
return value.
* @return Builder The object that constructs the sequence based on the dependencies
* outlined in the calls to play
and the other methods in the
* Builder
Note that canceling a Sequencer
also cancels all of the animations that it is
* responsible for.
Note that ending a Sequencer
also ends all of the animations that it is
* responsible for.
Starting this The For example, this sets up a Sequencer to play anim1 and anim2 at the same time, anim3 to
* play when anim2 finishes, and anim4 to play when anim3 finishes: Note in the example that both {@link Builder#before(Animatable)} and {@link
* Builder#after(Animatable)} 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. It is possible to make several calls into the same Sequencer
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.
*/
@SuppressWarnings("unchecked")
@Override
public void start() {
mCanceled = false;
// First, sort the nodes (if necessary). This will ensure that sortedNodes
// contains the animation nodes in the correct order.
sortNodes();
// 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.
ArrayListBuilder
object is a utility class to facilitate adding animations to a
* Sequencer
along with the relationships between the various animations. The
* intention of the Builder
methods, along with the {@link
* Sequencer#play(Animatable) play()} method of Sequencer
is to make it possible to
* express the dependency relationships of animations in a natural way. Developers can also use
* the {@link Sequencer#playTogether(Animatable[]) playTogether()} and {@link
* Sequencer#playSequentially(Animatable[]) playSequentially()} methods if these suit the need,
* but it might be easier in some situations to express the sequence of animations in pairs.
*
* Builder
object cannot be constructed directly, but is rather constructed
* internally via a call to {@link Sequencer#play(Animatable)}.
* Sequencer s = new Sequencer();
* s.play(anim1).with(anim2);
* s.play(anim2).before(anim3);
* s.play(anim4).after(anim3);
*
*
* Builder
object to express
* multiple relationships. However, note that it is only the animation passed into the initial
* {@link Sequencer#play(Animatable)} method that is the dependency in any of the successive
* calls to the Builder
object. For example, the following code starts both anim2
* and anim3 when anim1 ends; there is no direct dependency relationship between anim2 and
* anim3:
*
* Sequencer s = new Sequencer();
* s.play(anim1).before(anim2).before(anim3);
*
* If the desired result is to play anim1 then anim2 then anim3, this code expresses the
* relationship correctly:
* Sequencer s = new Sequencer(); * s.play(anim1).before(anim2); * s.play(anim2).before(anim3); ** *
Note that it is possible to express relationships that cannot be resolved and will not
* result in sensible results. For example, play(anim1).after(anim1)
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 sequences that
* can boil down to a simple, one-way relationship of animations starting with, before, and
* after other, different, animations.
Builder
object.
*
* @param anim The animation that will play when the animation supplied to the
* {@link Sequencer#play(Animatable)} method starts.
*/
public void with(Animatable 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);
}
/**
* Sets up the given animation to play when the animation supplied in the
* {@link Sequencer#play(Animatable)} call that created this Builder
object
* ends.
*
* @param anim The animation that will play when the animation supplied to the
* {@link Sequencer#play(Animatable)} method ends.
*/
public void before(Animatable 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);
}
/**
* Sets up the given animation to play when the animation supplied in the
* {@link Sequencer#play(Animatable)} call that created this Builder
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 Sequencer#play(Animatable)} method to play.
*/
public void after(Animatable 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);
}
/**
* Sets up the animation supplied in the
* {@link Sequencer#play(Animatable)} call that created this Builder
object
* to play when the given amount of time elapses.
*
* @param delay The number of milliseconds that should elapse before the
* animation starts.
*/
public void after(long delay) {
// setup dummy Animator just to run the clock
Animator anim = new Animator(delay, 0f, 1f);
Node node = new Node(anim);
mNodes.add(node);
Dependency dependency = new Dependency(node, Dependency.AFTER);
mCurrentNode.addDependency(dependency);
}
}
}