summaryrefslogtreecommitdiffstats
path: root/core/java/android/animation/Sequencer.java
blob: 84dbcc145cc16411d3a1a67cb32dd6cd4de44996 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
/*
 * 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.
 *
 * <p>There are two different approaches to adding animations to a <code>Sequencer</code>:
 * 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.</p>
 *
 * <p>It is possible to set up a <code>Sequencer</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.
 */
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 ArrayList<Animatable> mPlayingSet = new ArrayList<Animatable>();

    /**
     * Contains all nodes, mapped to their respective Animatables. When new
     * dependency information is added for an Animatable, we want to add it
     * to a single node representing that Animatable, not create a new Node
     * if one already exists.
     */
    private final HashMap<Animatable, Node> mNodeMap = new HashMap<Animatable, Node>();

    /**
     * Set of all nodes created for this Sequencer. This list is used upon
     * starting the sequencer, and the nodes are placed in sorted order into the
     * sortedNodes collection.
     */
    private final 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 final 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 SequencerAnimatableListener mSequenceListener = null;

    /**
     * Flag indicating that the Sequencer has been canceled (by calling cancel() or end()).
     * This flag is used to avoid starting other animations when currently-playing
     * child animations of this Sequencer end.
     */
    boolean mCanceled = false;

    /**
     * Sets up this Sequencer to play all of the supplied animations at the same time.
     *
     * @param sequenceItems The animations that will be started simultaneously.
     */
    public void playTogether(Animatable... sequenceItems) {
        if (sequenceItems != null) {
            mNeedsSort = true;
            Builder builder = play(sequenceItems[0]);
            for (int i = 1; i < sequenceItems.length; ++i) {
                builder.with(sequenceItems[i]);
            }
        }
    }

    /**
     * Sets up this Sequencer to play each of the supplied animations when the
     * previous animation ends.
     *
     * @param sequenceItems The aniamtions that will be started one after another.
     */
    public void playSequentially(Animatable... sequenceItems) {
        if (sequenceItems != null) {
            mNeedsSort = true;
            if (sequenceItems.length == 1) {
                play(sequenceItems[0]);
            } else {
                for (int i = 0; i < sequenceItems.length - 1; ++i) {
                    play(sequenceItems[i]).before(sequenceItems[i+1]);
                }
            }
        }
    }

    /**
     * Returns the current list of child Animatable objects controlled by this
     * Sequencer. This is a copy of the internal list; modifications to the returned list
     * will not affect the Sequencer, although changes to the underlying Animatable objects
     * will affect those objects being managed by the Sequencer.
     *
     * @return ArrayList<Animatable> The list of child animations of this Sequencer.
     */
    public ArrayList<Animatable> getChildAnimations() {
        ArrayList<Animatable> childList = new ArrayList<Animatable>();
        for (Node node : mNodes) {
            childList.add(node.animation);
        }
        return childList;
    }

    /**
     * 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 Sequence to play
     * <code>a1</code> and <code>a2</code> at the same time,
     * <code>play(a1).before(a2)</code> sets up the Sequence to play
     * <code>a1</code> first, followed by <code>a2</code>, and
     * <code>play(a1).after(a2)</code> sets up the Sequence 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 sequence 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(Animatable anim) {
        if (anim != null) {
            mNeedsSort = true;
            return new Builder(anim);
        }
        return null;
    }

    /**
     * {@inheritDoc}
     *
     * <p>Note that canceling a <code>Sequencer</code> also cancels all of the animations that it is
     * responsible for.</p>
     */
    @SuppressWarnings("unchecked")
    @Override
    public void cancel() {
        mCanceled = true;
        if (mListeners != null) {
            ArrayList<AnimatableListener> tmpListeners =
                    (ArrayList<AnimatableListener>) mListeners.clone();
            for (AnimatableListener listener : tmpListeners) {
                listener.onAnimationCancel(this);
            }
        }
        if (mSortedNodes.size() > 0) {
            for (Node node : mSortedNodes) {
                node.animation.cancel();
            }
        }
    }

    /**
     * {@inheritDoc}
     *
     * <p>Note that ending a <code>Sequencer</code> also ends all of the animations that it is
     * responsible for.</p>
     */
    @Override
    public void end() {
        mCanceled = true;
        if (mSortedNodes.size() > 0) {
            for (Node node : mSortedNodes) {
                node.animation.end();
            }
        }
    }

    /**
     * {@inheritDoc}
     *
     * <p>Starting this <code>Sequencer</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.
     */
    @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.
        ArrayList<Node> nodesToStart = new ArrayList<Node>();
        for (Node node : mSortedNodes) {
            if (mSequenceListener == null) {
                mSequenceListener = new SequencerAnimatableListener(this);
            }
            if (node.dependencies == null || node.dependencies.size() == 0) {
                nodesToStart.add(node);
            } else {
                for (Dependency dependency : node.dependencies) {
                    dependency.node.animation.addListener(
                            new DependencyListener(this, node, dependency.rule));
                }
                node.tmpDependencies = (ArrayList<Dependency>) node.dependencies.clone();
            }
            node.animation.addListener(mSequenceListener);
        }
        // Now that all dependencies are set up, start the animations that should be started.
        for (Node node : nodesToStart) {
            node.animation.start();
            mPlayingSet.add(node.animation);
        }
        if (mListeners != null) {
            ArrayList<AnimatableListener> tmpListeners =
                    (ArrayList<AnimatableListener>) mListeners.clone();
            for (AnimatableListener listener : tmpListeners) {
                listener.onAnimationStart(this);
            }
        }
    }

    /**
     * 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 AnimatableListener {

        private Sequencer mSequencer;

        // 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(Sequencer sequencer, Node node, int rule) {
            this.mSequencer = sequencer;
            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(Animatable animation) {
        }

        /**
         * An end event is received - see if this is an event we are listening for
         */
        public void onAnimationEnd(Animatable animation) {
            if (mRule == Dependency.AFTER) {
                startIfReady(animation);
            }
        }

        /**
         * Ignore repeat events for now
         */
        public void onAnimationRepeat(Animatable animation) {
        }

        /**
         * A start event is received - see if this is an event we are listening for
         */
        public void onAnimationStart(Animatable 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(Animatable dependencyAnimation) {
            if (mSequencer.mCanceled) {
                // if the parent Sequencer was canceled, then don't start any dependent anims
                return;
            }
            Dependency dependencyToRemove = null;
            for (Dependency dependency : mNode.tmpDependencies) {
                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();
                mSequencer.mPlayingSet.add(mNode.animation);
            }
        }

    }

    private class SequencerAnimatableListener implements AnimatableListener {

        private Sequencer mSequencer;

        SequencerAnimatableListener(Sequencer sequencer) {
            mSequencer = sequencer;
        }

        public void onAnimationCancel(Animatable animation) {
            if (mPlayingSet.size() == 0) {
                if (mListeners != null) {
                    for (AnimatableListener listener : mListeners) {
                        listener.onAnimationCancel(mSequencer);
                    }
                }
            }
        }

        @SuppressWarnings("unchecked")
        public void onAnimationEnd(Animatable animation) {
            animation.removeListener(this);
            mPlayingSet.remove(animation);
            Node animNode = mSequencer.mNodeMap.get(animation);
            animNode.done = true;
            ArrayList<Node> sortedNodes = mSequencer.mSortedNodes;
            int numNodes = sortedNodes.size();
            int nodeIndex = sortedNodes.indexOf(animNode);
            boolean allDone = true;
            for (int i = nodeIndex + 1; i < numNodes; ++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
                // sequencer has ended
                if (mListeners != null) {
                    ArrayList<AnimatableListener> tmpListeners =
                            (ArrayList<AnimatableListener>) mListeners.clone();
                    for (AnimatableListener listener : tmpListeners) {
                        listener.onAnimationEnd(mSequencer);
                    }
                }
            }
        }

        // Nothing to do
        public void onAnimationRepeat(Animatable animation) {
        }

        // Nothing to do
        public void onAnimationStart(Animatable 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>();
            for (Node node : mNodes) {
                if (node.dependencies == null || node.dependencies.size() == 0) {
                    roots.add(node);
                }
            }
            ArrayList<Node> tmpRoots = new ArrayList<Node>();
            while (roots.size() > 0) {
                for (Node root : roots) {
                    mSortedNodes.add(root);
                    if (root.nodeDependents != null) {
                        for (Node node : root.nodeDependents) {
                            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 Sequencer");
            }
        } 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
            for (Node node : mNodes) {
                if (node.dependencies != null && node.dependencies.size() > 0) {
                    for (Dependency dependency : node.dependencies) {
                        if (node.nodeDependencies == null) {
                            node.nodeDependencies = new ArrayList<Node>();
                        }
                        if (!node.nodeDependencies.contains(dependency.node)) {
                            node.nodeDependencies.add(dependency.node);
                        }
                    }
                }
                node.done = false; // also reset done flag
            }
        }
    }

    /**
     * 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 Animatable 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 {
        public Animatable 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 sequencer is launched in the future. So we create a copy of the dependency
         * list when the sequencer 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 Sequencer 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 Sequencer.
         */
        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(Animatable 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);
        }
    }

    /**
     * The <code>Builder</code> object is a utility class to facilitate adding animations to a
     * <code>Sequencer</code> along with the relationships between the various animations. The
     * intention of the <code>Builder</code> methods, along with the {@link
     * Sequencer#play(Animatable) play()} method of <code>Sequencer</code> 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.
     * <p/>
     * <p>The <code>Builder</code> object cannot be constructed directly, but is rather constructed
     * internally via a call to {@link Sequencer#play(Animatable)}.</p>
     * <p/>
     * <p>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:</p>
     * <pre>
     *     Sequencer s = new Sequencer();
     *     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(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.</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 Sequencer#play(Animatable)} 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>
     *   Sequencer s = new Sequencer();
     *   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>
     *   Sequencer s = new Sequencer();
     *   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 sequences 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 Sequencer and passed into the constructor of Builder.
         */
        private Node mCurrentNode;

        /**
         * package-private constructor. Builders are only constructed by Sequencer, 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(Animatable 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 Sequencer#play(Animatable)} call that created this <code>Builder</code> 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 <code>Builder</code> 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 <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 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 <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 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);
        }

    }

}