AnimatorSet.java revision d45204ba092312630a0e516ea7c247e594ce893b
1/* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.animation; 18 19import java.util.ArrayList; 20import java.util.Collection; 21import java.util.HashMap; 22import java.util.Iterator; 23import java.util.List; 24 25/** 26 * This class plays a set of {@link Animator} objects in the specified order. Animations 27 * can be set up to play together, in sequence, or after a specified delay. 28 * 29 * <p>There are two different approaches to adding animations to a <code>AnimatorSet</code>: 30 * either the {@link AnimatorSet#playTogether(Animator[]) playTogether()} or 31 * {@link AnimatorSet#playSequentially(Animator[]) playSequentially()} methods can be called to add 32 * a set of animations all at once, or the {@link AnimatorSet#play(Animator)} can be 33 * used in conjunction with methods in the {@link AnimatorSet.Builder Builder} 34 * class to add animations 35 * one by one.</p> 36 * 37 * <p>It is possible to set up a <code>AnimatorSet</code> with circular dependencies between 38 * its animations. For example, an animation a1 could be set up to start before animation a2, a2 39 * before a3, and a3 before a1. The results of this configuration are undefined, but will typically 40 * result in none of the affected animations being played. Because of this (and because 41 * circular dependencies do not make logical sense anyway), circular dependencies 42 * should be avoided, and the dependency flow of animations should only be in one direction. 43 */ 44public final class AnimatorSet extends Animator { 45 46 /** 47 * Internal variables 48 * NOTE: This object implements the clone() method, making a deep copy of any referenced 49 * objects. As other non-trivial fields are added to this class, make sure to add logic 50 * to clone() to make deep copies of them. 51 */ 52 53 /** 54 * Tracks animations currently being played, so that we know what to 55 * cancel or end when cancel() or end() is called on this AnimatorSet 56 */ 57 private ArrayList<Animator> mPlayingSet = new ArrayList<Animator>(); 58 59 /** 60 * Contains all nodes, mapped to their respective Animators. When new 61 * dependency information is added for an Animator, we want to add it 62 * to a single node representing that Animator, not create a new Node 63 * if one already exists. 64 */ 65 private HashMap<Animator, Node> mNodeMap = new HashMap<Animator, Node>(); 66 67 /** 68 * Set of all nodes created for this AnimatorSet. This list is used upon 69 * starting the set, and the nodes are placed in sorted order into the 70 * sortedNodes collection. 71 */ 72 private ArrayList<Node> mNodes = new ArrayList<Node>(); 73 74 /** 75 * The sorted list of nodes. This is the order in which the animations will 76 * be played. The details about when exactly they will be played depend 77 * on the dependency relationships of the nodes. 78 */ 79 private ArrayList<Node> mSortedNodes = new ArrayList<Node>(); 80 81 /** 82 * Flag indicating whether the nodes should be sorted prior to playing. This 83 * flag allows us to cache the previous sorted nodes so that if the sequence 84 * is replayed with no changes, it does not have to re-sort the nodes again. 85 */ 86 private boolean mNeedsSort = true; 87 88 private AnimatorSetListener mSetListener = null; 89 90 /** 91 * Flag indicating that the AnimatorSet has been manually 92 * terminated (by calling cancel() or end()). 93 * This flag is used to avoid starting other animations when currently-playing 94 * child animations of this AnimatorSet end. It also determines whether cancel/end 95 * notifications are sent out via the normal AnimatorSetListener mechanism. 96 */ 97 boolean mTerminated = false; 98 99 // The amount of time in ms to delay starting the animation after start() is called 100 private long mStartDelay = 0; 101 102 // Animator used for a nonzero startDelay 103 private ValueAnimator mDelayAnim = null; 104 105 106 // How long the child animations should last in ms. The default value is negative, which 107 // simply means that there is no duration set on the AnimatorSet. When a real duration is 108 // set, it is passed along to the child animations. 109 private long mDuration = -1; 110 111 112 /** 113 * Sets up this AnimatorSet to play all of the supplied animations at the same time. 114 * 115 * @param items The animations that will be started simultaneously. 116 */ 117 public void playTogether(Animator... items) { 118 if (items != null) { 119 mNeedsSort = true; 120 Builder builder = play(items[0]); 121 for (int i = 1; i < items.length; ++i) { 122 builder.with(items[i]); 123 } 124 } 125 } 126 127 /** 128 * Sets up this AnimatorSet to play all of the supplied animations at the same time. 129 * 130 * @param items The animations that will be started simultaneously. 131 */ 132 public void playTogether(Collection<Animator> items) { 133 if (items != null && items.size() > 0) { 134 mNeedsSort = true; 135 Builder builder = null; 136 for (Animator anim : items) { 137 if (builder == null) { 138 builder = play(anim); 139 } else { 140 builder.with(anim); 141 } 142 } 143 } 144 } 145 146 /** 147 * Sets up this AnimatorSet to play each of the supplied animations when the 148 * previous animation ends. 149 * 150 * @param items The animations that will be started one after another. 151 */ 152 public void playSequentially(Animator... items) { 153 if (items != null) { 154 mNeedsSort = true; 155 if (items.length == 1) { 156 play(items[0]); 157 } else { 158 for (int i = 0; i < items.length - 1; ++i) { 159 play(items[i]).before(items[i+1]); 160 } 161 } 162 } 163 } 164 165 /** 166 * Sets up this AnimatorSet to play each of the supplied animations when the 167 * previous animation ends. 168 * 169 * @param items The animations that will be started one after another. 170 */ 171 public void playSequentially(List<Animator> items) { 172 if (items != null && items.size() > 0) { 173 mNeedsSort = true; 174 if (items.size() == 1) { 175 play(items.get(0)); 176 } else { 177 for (int i = 0; i < items.size() - 1; ++i) { 178 play(items.get(i)).before(items.get(i+1)); 179 } 180 } 181 } 182 } 183 184 /** 185 * Returns the current list of child Animator objects controlled by this 186 * AnimatorSet. This is a copy of the internal list; modifications to the returned list 187 * will not affect the AnimatorSet, although changes to the underlying Animator objects 188 * will affect those objects being managed by the AnimatorSet. 189 * 190 * @return ArrayList<Animator> The list of child animations of this AnimatorSet. 191 */ 192 public ArrayList<Animator> getChildAnimations() { 193 ArrayList<Animator> childList = new ArrayList<Animator>(); 194 for (Node node : mNodes) { 195 childList.add(node.animation); 196 } 197 return childList; 198 } 199 200 /** 201 * Sets the target object for all current {@link #getChildAnimations() child animations} 202 * of this AnimatorSet that take targets ({@link ObjectAnimator} and 203 * AnimatorSet). 204 * 205 * @param target The object being animated 206 */ 207 @Override 208 public void setTarget(Object target) { 209 for (Node node : mNodes) { 210 Animator animation = node.animation; 211 if (animation instanceof AnimatorSet) { 212 ((AnimatorSet)animation).setTarget(target); 213 } else if (animation instanceof ObjectAnimator) { 214 ((ObjectAnimator)animation).setTarget(target); 215 } 216 } 217 } 218 219 /** 220 * Sets the TimeInterpolator for all current {@link #getChildAnimations() child animations} 221 * of this AnimatorSet. 222 * 223 * @param interpolator the interpolator to be used by each child animation of this AnimatorSet 224 */ 225 @Override 226 public void setInterpolator(TimeInterpolator interpolator) { 227 for (Node node : mNodes) { 228 node.animation.setInterpolator(interpolator); 229 } 230 } 231 232 /** 233 * This method creates a <code>Builder</code> object, which is used to 234 * set up playing constraints. This initial <code>play()</code> method 235 * tells the <code>Builder</code> the animation that is the dependency for 236 * the succeeding commands to the <code>Builder</code>. For example, 237 * calling <code>play(a1).with(a2)</code> sets up the AnimatorSet to play 238 * <code>a1</code> and <code>a2</code> at the same time, 239 * <code>play(a1).before(a2)</code> sets up the AnimatorSet to play 240 * <code>a1</code> first, followed by <code>a2</code>, and 241 * <code>play(a1).after(a2)</code> sets up the AnimatorSet to play 242 * <code>a2</code> first, followed by <code>a1</code>. 243 * 244 * <p>Note that <code>play()</code> is the only way to tell the 245 * <code>Builder</code> the animation upon which the dependency is created, 246 * so successive calls to the various functions in <code>Builder</code> 247 * will all refer to the initial parameter supplied in <code>play()</code> 248 * as the dependency of the other animations. For example, calling 249 * <code>play(a1).before(a2).before(a3)</code> will play both <code>a2</code> 250 * and <code>a3</code> when a1 ends; it does not set up a dependency between 251 * <code>a2</code> and <code>a3</code>.</p> 252 * 253 * @param anim The animation that is the dependency used in later calls to the 254 * methods in the returned <code>Builder</code> object. A null parameter will result 255 * in a null <code>Builder</code> return value. 256 * @return Builder The object that constructs the AnimatorSet based on the dependencies 257 * outlined in the calls to <code>play</code> and the other methods in the 258 * <code>Builder</code object. 259 */ 260 public Builder play(Animator anim) { 261 if (anim != null) { 262 mNeedsSort = true; 263 return new Builder(anim); 264 } 265 return null; 266 } 267 268 /** 269 * {@inheritDoc} 270 * 271 * <p>Note that canceling a <code>AnimatorSet</code> also cancels all of the animations that it is 272 * responsible for.</p> 273 */ 274 @SuppressWarnings("unchecked") 275 @Override 276 public void cancel() { 277 mTerminated = true; 278 if (isRunning()) { 279 ArrayList<AnimatorListener> tmpListeners = null; 280 if (mListeners != null) { 281 tmpListeners = (ArrayList<AnimatorListener>) mListeners.clone(); 282 for (AnimatorListener listener : tmpListeners) { 283 listener.onAnimationCancel(this); 284 } 285 } 286 if (mDelayAnim != null && mDelayAnim.isRunning()) { 287 // If we're currently in the startDelay period, just cancel that animator and 288 // send out the end event to all listeners 289 mDelayAnim.cancel(); 290 } else if (mSortedNodes.size() > 0) { 291 for (Node node : mSortedNodes) { 292 node.animation.cancel(); 293 } 294 } 295 if (tmpListeners != null) { 296 for (AnimatorListener listener : tmpListeners) { 297 listener.onAnimationEnd(this); 298 } 299 } 300 } 301 } 302 303 /** 304 * {@inheritDoc} 305 * 306 * <p>Note that ending a <code>AnimatorSet</code> also ends all of the animations that it is 307 * responsible for.</p> 308 */ 309 @Override 310 public void end() { 311 mTerminated = true; 312 if (isRunning()) { 313 if (mSortedNodes.size() != mNodes.size()) { 314 // hasn't been started yet - sort the nodes now, then end them 315 sortNodes(); 316 for (Node node : mSortedNodes) { 317 if (mSetListener == null) { 318 mSetListener = new AnimatorSetListener(this); 319 } 320 node.animation.addListener(mSetListener); 321 } 322 } 323 if (mDelayAnim != null) { 324 mDelayAnim.cancel(); 325 } 326 if (mSortedNodes.size() > 0) { 327 for (Node node : mSortedNodes) { 328 node.animation.end(); 329 } 330 } 331 if (mListeners != null) { 332 ArrayList<AnimatorListener> tmpListeners = 333 (ArrayList<AnimatorListener>) mListeners.clone(); 334 for (AnimatorListener listener : tmpListeners) { 335 listener.onAnimationEnd(this); 336 } 337 } 338 } 339 } 340 341 /** 342 * Returns true if any of the child animations of this AnimatorSet have been started and have not 343 * yet ended. 344 * @return Whether this AnimatorSet has been started and has not yet ended. 345 */ 346 @Override 347 public boolean isRunning() { 348 for (Node node : mNodes) { 349 if (node.animation.isRunning()) { 350 return true; 351 } 352 } 353 return false; 354 } 355 356 /** 357 * The amount of time, in milliseconds, to delay starting the animation after 358 * {@link #start()} is called. 359 * 360 * @return the number of milliseconds to delay running the animation 361 */ 362 @Override 363 public long getStartDelay() { 364 return mStartDelay; 365 } 366 367 /** 368 * The amount of time, in milliseconds, to delay starting the animation after 369 * {@link #start()} is called. 370 371 * @param startDelay The amount of the delay, in milliseconds 372 */ 373 @Override 374 public void setStartDelay(long startDelay) { 375 mStartDelay = startDelay; 376 } 377 378 /** 379 * Gets the length of each of the child animations of this AnimatorSet. This value may 380 * be less than 0, which indicates that no duration has been set on this AnimatorSet 381 * and each of the child animations will use their own duration. 382 * 383 * @return The length of the animation, in milliseconds, of each of the child 384 * animations of this AnimatorSet. 385 */ 386 @Override 387 public long getDuration() { 388 return mDuration; 389 } 390 391 /** 392 * Sets the length of each of the current child animations of this AnimatorSet. By default, 393 * each child animation will use its own duration. If the duration is set on the AnimatorSet, 394 * then each child animation inherits this duration. 395 * 396 * @param duration The length of the animation, in milliseconds, of each of the child 397 * animations of this AnimatorSet. 398 */ 399 @Override 400 public AnimatorSet setDuration(long duration) { 401 if (duration < 0) { 402 throw new IllegalArgumentException("duration must be a value of zero or greater"); 403 } 404 for (Node node : mNodes) { 405 // TODO: don't set the duration of the timing-only nodes created by AnimatorSet to 406 // insert "play-after" delays 407 node.animation.setDuration(duration); 408 } 409 mDuration = duration; 410 return this; 411 } 412 413 @Override 414 public void setupStartValues() { 415 for (Node node : mNodes) { 416 node.animation.setupStartValues(); 417 } 418 } 419 420 @Override 421 public void setupEndValues() { 422 for (Node node : mNodes) { 423 node.animation.setupEndValues(); 424 } 425 } 426 427 /** 428 * {@inheritDoc} 429 * 430 * <p>Starting this <code>AnimatorSet</code> will, in turn, start the animations for which 431 * it is responsible. The details of when exactly those animations are started depends on 432 * the dependency relationships that have been set up between the animations. 433 */ 434 @SuppressWarnings("unchecked") 435 @Override 436 public void start() { 437 mTerminated = false; 438 439 // First, sort the nodes (if necessary). This will ensure that sortedNodes 440 // contains the animation nodes in the correct order. 441 sortNodes(); 442 443 int numSortedNodes = mSortedNodes.size(); 444 for (int i = 0; i < numSortedNodes; ++i) { 445 Node node = mSortedNodes.get(i); 446 // First, clear out the old listeners 447 ArrayList<AnimatorListener> oldListeners = node.animation.getListeners(); 448 if (oldListeners != null && oldListeners.size() > 0) { 449 final ArrayList<AnimatorListener> clonedListeners = new 450 ArrayList<AnimatorListener>(oldListeners); 451 452 for (AnimatorListener listener : clonedListeners) { 453 if (listener instanceof DependencyListener || 454 listener instanceof AnimatorSetListener) { 455 node.animation.removeListener(listener); 456 } 457 } 458 } 459 } 460 461 // nodesToStart holds the list of nodes to be started immediately. We don't want to 462 // start the animations in the loop directly because we first need to set up 463 // dependencies on all of the nodes. For example, we don't want to start an animation 464 // when some other animation also wants to start when the first animation begins. 465 final ArrayList<Node> nodesToStart = new ArrayList<Node>(); 466 for (int i = 0; i < numSortedNodes; ++i) { 467 Node node = mSortedNodes.get(i); 468 if (mSetListener == null) { 469 mSetListener = new AnimatorSetListener(this); 470 } 471 if (node.dependencies == null || node.dependencies.size() == 0) { 472 nodesToStart.add(node); 473 } else { 474 int numDependencies = node.dependencies.size(); 475 for (int j = 0; j < numDependencies; ++j) { 476 Dependency dependency = node.dependencies.get(j); 477 dependency.node.animation.addListener( 478 new DependencyListener(this, node, dependency.rule)); 479 } 480 node.tmpDependencies = (ArrayList<Dependency>) node.dependencies.clone(); 481 } 482 node.animation.addListener(mSetListener); 483 } 484 // Now that all dependencies are set up, start the animations that should be started. 485 if (mStartDelay <= 0) { 486 for (Node node : nodesToStart) { 487 node.animation.start(); 488 mPlayingSet.add(node.animation); 489 } 490 } else { 491 // TODO: Need to cancel out of the delay appropriately 492 mDelayAnim = ValueAnimator.ofFloat(0f, 1f); 493 mDelayAnim.setDuration(mStartDelay); 494 mDelayAnim.addListener(new AnimatorListenerAdapter() { 495 boolean canceled = false; 496 public void onAnimationCancel(Animator anim) { 497 canceled = true; 498 } 499 public void onAnimationEnd(Animator anim) { 500 if (!canceled) { 501 int numNodes = nodesToStart.size(); 502 for (int i = 0; i < numNodes; ++i) { 503 Node node = nodesToStart.get(i); 504 node.animation.start(); 505 mPlayingSet.add(node.animation); 506 } 507 } 508 } 509 }); 510 mDelayAnim.start(); 511 } 512 if (mListeners != null) { 513 ArrayList<AnimatorListener> tmpListeners = 514 (ArrayList<AnimatorListener>) mListeners.clone(); 515 int numListeners = tmpListeners.size(); 516 for (int i = 0; i < numListeners; ++i) { 517 tmpListeners.get(i).onAnimationStart(this); 518 if (mNodes.size() == 0) { 519 // Handle unusual case where empty AnimatorSet is started - should send out 520 // end event immediately since the event will not be sent out at all otherwise 521 tmpListeners.get(i).onAnimationEnd(this); 522 } 523 } 524 } 525 } 526 527 @Override 528 public AnimatorSet clone() { 529 final AnimatorSet anim = (AnimatorSet) super.clone(); 530 /* 531 * The basic clone() operation copies all items. This doesn't work very well for 532 * AnimatorSet, because it will copy references that need to be recreated and state 533 * that may not apply. What we need to do now is put the clone in an uninitialized 534 * state, with fresh, empty data structures. Then we will build up the nodes list 535 * manually, as we clone each Node (and its animation). The clone will then be sorted, 536 * and will populate any appropriate lists, when it is started. 537 */ 538 anim.mNeedsSort = true; 539 anim.mTerminated = false; 540 anim.mPlayingSet = new ArrayList<Animator>(); 541 anim.mNodeMap = new HashMap<Animator, Node>(); 542 anim.mNodes = new ArrayList<Node>(); 543 anim.mSortedNodes = new ArrayList<Node>(); 544 545 // Walk through the old nodes list, cloning each node and adding it to the new nodemap. 546 // One problem is that the old node dependencies point to nodes in the old AnimatorSet. 547 // We need to track the old/new nodes in order to reconstruct the dependencies in the clone. 548 HashMap<Node, Node> nodeCloneMap = new HashMap<Node, Node>(); // <old, new> 549 for (Node node : mNodes) { 550 Node nodeClone = node.clone(); 551 nodeCloneMap.put(node, nodeClone); 552 anim.mNodes.add(nodeClone); 553 anim.mNodeMap.put(nodeClone.animation, nodeClone); 554 // Clear out the dependencies in the clone; we'll set these up manually later 555 nodeClone.dependencies = null; 556 nodeClone.tmpDependencies = null; 557 nodeClone.nodeDependents = null; 558 nodeClone.nodeDependencies = null; 559 // clear out any listeners that were set up by the AnimatorSet; these will 560 // be set up when the clone's nodes are sorted 561 ArrayList<AnimatorListener> cloneListeners = nodeClone.animation.getListeners(); 562 if (cloneListeners != null) { 563 ArrayList<AnimatorListener> listenersToRemove = null; 564 for (AnimatorListener listener : cloneListeners) { 565 if (listener instanceof AnimatorSetListener) { 566 if (listenersToRemove == null) { 567 listenersToRemove = new ArrayList<AnimatorListener>(); 568 } 569 listenersToRemove.add(listener); 570 } 571 } 572 if (listenersToRemove != null) { 573 for (AnimatorListener listener : listenersToRemove) { 574 cloneListeners.remove(listener); 575 } 576 } 577 } 578 } 579 // Now that we've cloned all of the nodes, we're ready to walk through their 580 // dependencies, mapping the old dependencies to the new nodes 581 for (Node node : mNodes) { 582 Node nodeClone = nodeCloneMap.get(node); 583 if (node.dependencies != null) { 584 for (Dependency dependency : node.dependencies) { 585 Node clonedDependencyNode = nodeCloneMap.get(dependency.node); 586 Dependency cloneDependency = new Dependency(clonedDependencyNode, 587 dependency.rule); 588 nodeClone.addDependency(cloneDependency); 589 } 590 } 591 } 592 593 return anim; 594 } 595 596 /** 597 * This class is the mechanism by which animations are started based on events in other 598 * animations. If an animation has multiple dependencies on other animations, then 599 * all dependencies must be satisfied before the animation is started. 600 */ 601 private static class DependencyListener implements AnimatorListener { 602 603 private AnimatorSet mAnimatorSet; 604 605 // The node upon which the dependency is based. 606 private Node mNode; 607 608 // The Dependency rule (WITH or AFTER) that the listener should wait for on 609 // the node 610 private int mRule; 611 612 public DependencyListener(AnimatorSet animatorSet, Node node, int rule) { 613 this.mAnimatorSet = animatorSet; 614 this.mNode = node; 615 this.mRule = rule; 616 } 617 618 /** 619 * Ignore cancel events for now. We may want to handle this eventually, 620 * to prevent follow-on animations from running when some dependency 621 * animation is canceled. 622 */ 623 public void onAnimationCancel(Animator animation) { 624 } 625 626 /** 627 * An end event is received - see if this is an event we are listening for 628 */ 629 public void onAnimationEnd(Animator animation) { 630 if (mRule == Dependency.AFTER) { 631 startIfReady(animation); 632 } 633 } 634 635 /** 636 * Ignore repeat events for now 637 */ 638 public void onAnimationRepeat(Animator animation) { 639 } 640 641 /** 642 * A start event is received - see if this is an event we are listening for 643 */ 644 public void onAnimationStart(Animator animation) { 645 if (mRule == Dependency.WITH) { 646 startIfReady(animation); 647 } 648 } 649 650 /** 651 * Check whether the event received is one that the node was waiting for. 652 * If so, mark it as complete and see whether it's time to start 653 * the animation. 654 * @param dependencyAnimation the animation that sent the event. 655 */ 656 private void startIfReady(Animator dependencyAnimation) { 657 if (mAnimatorSet.mTerminated) { 658 // if the parent AnimatorSet was canceled, then don't start any dependent anims 659 return; 660 } 661 Dependency dependencyToRemove = null; 662 int numDependencies = mNode.tmpDependencies.size(); 663 for (int i = 0; i < numDependencies; ++i) { 664 Dependency dependency = mNode.tmpDependencies.get(i); 665 if (dependency.rule == mRule && 666 dependency.node.animation == dependencyAnimation) { 667 // rule fired - remove the dependency and listener and check to 668 // see whether it's time to start the animation 669 dependencyToRemove = dependency; 670 dependencyAnimation.removeListener(this); 671 break; 672 } 673 } 674 mNode.tmpDependencies.remove(dependencyToRemove); 675 if (mNode.tmpDependencies.size() == 0) { 676 // all dependencies satisfied: start the animation 677 mNode.animation.start(); 678 mAnimatorSet.mPlayingSet.add(mNode.animation); 679 } 680 } 681 682 } 683 684 private class AnimatorSetListener implements AnimatorListener { 685 686 private AnimatorSet mAnimatorSet; 687 688 AnimatorSetListener(AnimatorSet animatorSet) { 689 mAnimatorSet = animatorSet; 690 } 691 692 public void onAnimationCancel(Animator animation) { 693 if (!mTerminated) { 694 // Listeners are already notified of the AnimatorSet canceling in cancel(). 695 // The logic below only kicks in when animations end normally 696 if (mPlayingSet.size() == 0) { 697 if (mListeners != null) { 698 int numListeners = mListeners.size(); 699 for (int i = 0; i < numListeners; ++i) { 700 mListeners.get(i).onAnimationCancel(mAnimatorSet); 701 } 702 } 703 } 704 } 705 } 706 707 @SuppressWarnings("unchecked") 708 public void onAnimationEnd(Animator animation) { 709 animation.removeListener(this); 710 mPlayingSet.remove(animation); 711 Node animNode = mAnimatorSet.mNodeMap.get(animation); 712 animNode.done = true; 713 if (!mTerminated) { 714 // Listeners are already notified of the AnimatorSet ending in cancel() or 715 // end(); the logic below only kicks in when animations end normally 716 ArrayList<Node> sortedNodes = mAnimatorSet.mSortedNodes; 717 boolean allDone = true; 718 int numSortedNodes = sortedNodes.size(); 719 for (int i = 0; i < numSortedNodes; ++i) { 720 if (!sortedNodes.get(i).done) { 721 allDone = false; 722 break; 723 } 724 } 725 if (allDone) { 726 // If this was the last child animation to end, then notify listeners that this 727 // AnimatorSet has ended 728 if (mListeners != null) { 729 ArrayList<AnimatorListener> tmpListeners = 730 (ArrayList<AnimatorListener>) mListeners.clone(); 731 int numListeners = tmpListeners.size(); 732 for (int i = 0; i < numListeners; ++i) { 733 tmpListeners.get(i).onAnimationEnd(mAnimatorSet); 734 } 735 } 736 } 737 } 738 } 739 740 // Nothing to do 741 public void onAnimationRepeat(Animator animation) { 742 } 743 744 // Nothing to do 745 public void onAnimationStart(Animator animation) { 746 } 747 748 } 749 750 /** 751 * This method sorts the current set of nodes, if needed. The sort is a simple 752 * DependencyGraph sort, which goes like this: 753 * - All nodes without dependencies become 'roots' 754 * - while roots list is not null 755 * - for each root r 756 * - add r to sorted list 757 * - remove r as a dependency from any other node 758 * - any nodes with no dependencies are added to the roots list 759 */ 760 private void sortNodes() { 761 if (mNeedsSort) { 762 mSortedNodes.clear(); 763 ArrayList<Node> roots = new ArrayList<Node>(); 764 int numNodes = mNodes.size(); 765 for (int i = 0; i < numNodes; ++i) { 766 Node node = mNodes.get(i); 767 if (node.dependencies == null || node.dependencies.size() == 0) { 768 roots.add(node); 769 } 770 } 771 ArrayList<Node> tmpRoots = new ArrayList<Node>(); 772 while (roots.size() > 0) { 773 int numRoots = roots.size(); 774 for (int i = 0; i < numRoots; ++i) { 775 Node root = roots.get(i); 776 mSortedNodes.add(root); 777 if (root.nodeDependents != null) { 778 int numDependents = root.nodeDependents.size(); 779 for (int j = 0; j < numDependents; ++j) { 780 Node node = root.nodeDependents.get(j); 781 node.nodeDependencies.remove(root); 782 if (node.nodeDependencies.size() == 0) { 783 tmpRoots.add(node); 784 } 785 } 786 } 787 } 788 roots.clear(); 789 roots.addAll(tmpRoots); 790 tmpRoots.clear(); 791 } 792 mNeedsSort = false; 793 if (mSortedNodes.size() != mNodes.size()) { 794 throw new IllegalStateException("Circular dependencies cannot exist" 795 + " in AnimatorSet"); 796 } 797 } else { 798 // Doesn't need sorting, but still need to add in the nodeDependencies list 799 // because these get removed as the event listeners fire and the dependencies 800 // are satisfied 801 int numNodes = mNodes.size(); 802 for (int i = 0; i < numNodes; ++i) { 803 Node node = mNodes.get(i); 804 if (node.dependencies != null && node.dependencies.size() > 0) { 805 int numDependencies = node.dependencies.size(); 806 for (int j = 0; j < numDependencies; ++j) { 807 Dependency dependency = node.dependencies.get(j); 808 if (node.nodeDependencies == null) { 809 node.nodeDependencies = new ArrayList<Node>(); 810 } 811 if (!node.nodeDependencies.contains(dependency.node)) { 812 node.nodeDependencies.add(dependency.node); 813 } 814 } 815 } 816 // nodes are 'done' by default; they become un-done when started, and done 817 // again when ended 818 node.done = false; 819 } 820 } 821 } 822 823 /** 824 * Dependency holds information about the node that some other node is 825 * dependent upon and the nature of that dependency. 826 * 827 */ 828 private static class Dependency { 829 static final int WITH = 0; // dependent node must start with this dependency node 830 static final int AFTER = 1; // dependent node must start when this dependency node finishes 831 832 // The node that the other node with this Dependency is dependent upon 833 public Node node; 834 835 // The nature of the dependency (WITH or AFTER) 836 public int rule; 837 838 public Dependency(Node node, int rule) { 839 this.node = node; 840 this.rule = rule; 841 } 842 } 843 844 /** 845 * A Node is an embodiment of both the Animator that it wraps as well as 846 * any dependencies that are associated with that Animation. This includes 847 * both dependencies upon other nodes (in the dependencies list) as 848 * well as dependencies of other nodes upon this (in the nodeDependents list). 849 */ 850 private static class Node implements Cloneable { 851 public Animator animation; 852 853 /** 854 * These are the dependencies that this node's animation has on other 855 * nodes. For example, if this node's animation should begin with some 856 * other animation ends, then there will be an item in this node's 857 * dependencies list for that other animation's node. 858 */ 859 public ArrayList<Dependency> dependencies = null; 860 861 /** 862 * tmpDependencies is a runtime detail. We use the dependencies list for sorting. 863 * But we also use the list to keep track of when multiple dependencies are satisfied, 864 * but removing each dependency as it is satisfied. We do not want to remove 865 * the dependency itself from the list, because we need to retain that information 866 * if the AnimatorSet is launched in the future. So we create a copy of the dependency 867 * list when the AnimatorSet starts and use this tmpDependencies list to track the 868 * list of satisfied dependencies. 869 */ 870 public ArrayList<Dependency> tmpDependencies = null; 871 872 /** 873 * nodeDependencies is just a list of the nodes that this Node is dependent upon. 874 * This information is used in sortNodes(), to determine when a node is a root. 875 */ 876 public ArrayList<Node> nodeDependencies = null; 877 878 /** 879 * nodeDepdendents is the list of nodes that have this node as a dependency. This 880 * is a utility field used in sortNodes to facilitate removing this node as a 881 * dependency when it is a root node. 882 */ 883 public ArrayList<Node> nodeDependents = null; 884 885 /** 886 * Flag indicating whether the animation in this node is finished. This flag 887 * is used by AnimatorSet to check, as each animation ends, whether all child animations 888 * are done and it's time to send out an end event for the entire AnimatorSet. 889 */ 890 public boolean done = false; 891 892 /** 893 * Constructs the Node with the animation that it encapsulates. A Node has no 894 * dependencies by default; dependencies are added via the addDependency() 895 * method. 896 * 897 * @param animation The animation that the Node encapsulates. 898 */ 899 public Node(Animator animation) { 900 this.animation = animation; 901 } 902 903 /** 904 * Add a dependency to this Node. The dependency includes information about the 905 * node that this node is dependency upon and the nature of the dependency. 906 * @param dependency 907 */ 908 public void addDependency(Dependency dependency) { 909 if (dependencies == null) { 910 dependencies = new ArrayList<Dependency>(); 911 nodeDependencies = new ArrayList<Node>(); 912 } 913 dependencies.add(dependency); 914 if (!nodeDependencies.contains(dependency.node)) { 915 nodeDependencies.add(dependency.node); 916 } 917 Node dependencyNode = dependency.node; 918 if (dependencyNode.nodeDependents == null) { 919 dependencyNode.nodeDependents = new ArrayList<Node>(); 920 } 921 dependencyNode.nodeDependents.add(this); 922 } 923 924 @Override 925 public Node clone() { 926 try { 927 Node node = (Node) super.clone(); 928 node.animation = (Animator) animation.clone(); 929 return node; 930 } catch (CloneNotSupportedException e) { 931 throw new AssertionError(); 932 } 933 } 934 } 935 936 /** 937 * The <code>Builder</code> object is a utility class to facilitate adding animations to a 938 * <code>AnimatorSet</code> along with the relationships between the various animations. The 939 * intention of the <code>Builder</code> methods, along with the {@link 940 * AnimatorSet#play(Animator) play()} method of <code>AnimatorSet</code> is to make it possible to 941 * express the dependency relationships of animations in a natural way. Developers can also use 942 * the {@link AnimatorSet#playTogether(Animator[]) playTogether()} and {@link 943 * AnimatorSet#playSequentially(Animator[]) playSequentially()} methods if these suit the need, 944 * but it might be easier in some situations to express the AnimatorSet of animations in pairs. 945 * <p/> 946 * <p>The <code>Builder</code> object cannot be constructed directly, but is rather constructed 947 * internally via a call to {@link AnimatorSet#play(Animator)}.</p> 948 * <p/> 949 * <p>For example, this sets up a AnimatorSet to play anim1 and anim2 at the same time, anim3 to 950 * play when anim2 finishes, and anim4 to play when anim3 finishes:</p> 951 * <pre> 952 * AnimatorSet s = new AnimatorSet(); 953 * s.play(anim1).with(anim2); 954 * s.play(anim2).before(anim3); 955 * s.play(anim4).after(anim3); 956 * </pre> 957 * <p/> 958 * <p>Note in the example that both {@link Builder#before(Animator)} and {@link 959 * Builder#after(Animator)} are used. These are just different ways of expressing the same 960 * relationship and are provided to make it easier to say things in a way that is more natural, 961 * depending on the situation.</p> 962 * <p/> 963 * <p>It is possible to make several calls into the same <code>Builder</code> object to express 964 * multiple relationships. However, note that it is only the animation passed into the initial 965 * {@link AnimatorSet#play(Animator)} method that is the dependency in any of the successive 966 * calls to the <code>Builder</code> object. For example, the following code starts both anim2 967 * and anim3 when anim1 ends; there is no direct dependency relationship between anim2 and 968 * anim3: 969 * <pre> 970 * AnimatorSet s = new AnimatorSet(); 971 * s.play(anim1).before(anim2).before(anim3); 972 * </pre> 973 * If the desired result is to play anim1 then anim2 then anim3, this code expresses the 974 * relationship correctly:</p> 975 * <pre> 976 * AnimatorSet s = new AnimatorSet(); 977 * s.play(anim1).before(anim2); 978 * s.play(anim2).before(anim3); 979 * </pre> 980 * <p/> 981 * <p>Note that it is possible to express relationships that cannot be resolved and will not 982 * result in sensible results. For example, <code>play(anim1).after(anim1)</code> makes no 983 * sense. In general, circular dependencies like this one (or more indirect ones where a depends 984 * on b, which depends on c, which depends on a) should be avoided. Only create AnimatorSets 985 * that can boil down to a simple, one-way relationship of animations starting with, before, and 986 * after other, different, animations.</p> 987 */ 988 public class Builder { 989 990 /** 991 * This tracks the current node being processed. It is supplied to the play() method 992 * of AnimatorSet and passed into the constructor of Builder. 993 */ 994 private Node mCurrentNode; 995 996 /** 997 * package-private constructor. Builders are only constructed by AnimatorSet, when the 998 * play() method is called. 999 * 1000 * @param anim The animation that is the dependency for the other animations passed into 1001 * the other methods of this Builder object. 1002 */ 1003 Builder(Animator anim) { 1004 mCurrentNode = mNodeMap.get(anim); 1005 if (mCurrentNode == null) { 1006 mCurrentNode = new Node(anim); 1007 mNodeMap.put(anim, mCurrentNode); 1008 mNodes.add(mCurrentNode); 1009 } 1010 } 1011 1012 /** 1013 * Sets up the given animation to play at the same time as the animation supplied in the 1014 * {@link AnimatorSet#play(Animator)} call that created this <code>Builder</code> object. 1015 * 1016 * @param anim The animation that will play when the animation supplied to the 1017 * {@link AnimatorSet#play(Animator)} method starts. 1018 */ 1019 public Builder with(Animator anim) { 1020 Node node = mNodeMap.get(anim); 1021 if (node == null) { 1022 node = new Node(anim); 1023 mNodeMap.put(anim, node); 1024 mNodes.add(node); 1025 } 1026 Dependency dependency = new Dependency(mCurrentNode, Dependency.WITH); 1027 node.addDependency(dependency); 1028 return this; 1029 } 1030 1031 /** 1032 * Sets up the given animation to play when the animation supplied in the 1033 * {@link AnimatorSet#play(Animator)} call that created this <code>Builder</code> object 1034 * ends. 1035 * 1036 * @param anim The animation that will play when the animation supplied to the 1037 * {@link AnimatorSet#play(Animator)} method ends. 1038 */ 1039 public Builder before(Animator anim) { 1040 Node node = mNodeMap.get(anim); 1041 if (node == null) { 1042 node = new Node(anim); 1043 mNodeMap.put(anim, node); 1044 mNodes.add(node); 1045 } 1046 Dependency dependency = new Dependency(mCurrentNode, Dependency.AFTER); 1047 node.addDependency(dependency); 1048 return this; 1049 } 1050 1051 /** 1052 * Sets up the given animation to play when the animation supplied in the 1053 * {@link AnimatorSet#play(Animator)} call that created this <code>Builder</code> object 1054 * to start when the animation supplied in this method call ends. 1055 * 1056 * @param anim The animation whose end will cause the animation supplied to the 1057 * {@link AnimatorSet#play(Animator)} method to play. 1058 */ 1059 public Builder after(Animator anim) { 1060 Node node = mNodeMap.get(anim); 1061 if (node == null) { 1062 node = new Node(anim); 1063 mNodeMap.put(anim, node); 1064 mNodes.add(node); 1065 } 1066 Dependency dependency = new Dependency(node, Dependency.AFTER); 1067 mCurrentNode.addDependency(dependency); 1068 return this; 1069 } 1070 1071 /** 1072 * Sets up the animation supplied in the 1073 * {@link AnimatorSet#play(Animator)} call that created this <code>Builder</code> object 1074 * to play when the given amount of time elapses. 1075 * 1076 * @param delay The number of milliseconds that should elapse before the 1077 * animation starts. 1078 */ 1079 public Builder after(long delay) { 1080 // setup dummy ValueAnimator just to run the clock 1081 ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f); 1082 anim.setDuration(delay); 1083 after(anim); 1084 return this; 1085 } 1086 1087 } 1088 1089} 1090