AnimatorSet.java revision 1f6c3fb286d3672f25707ddd224be211eb24c98e
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 android.app.ActivityThread; 20import android.app.Application; 21import android.os.Build; 22import android.os.Looper; 23import android.util.AndroidRuntimeException; 24import android.util.ArrayMap; 25import android.util.Log; 26import android.view.animation.Animation; 27 28import java.util.ArrayList; 29import java.util.Collection; 30import java.util.Comparator; 31import java.util.List; 32 33/** 34 * This class plays a set of {@link Animator} objects in the specified order. Animations 35 * can be set up to play together, in sequence, or after a specified delay. 36 * 37 * <p>There are two different approaches to adding animations to a <code>AnimatorSet</code>: 38 * either the {@link AnimatorSet#playTogether(Animator[]) playTogether()} or 39 * {@link AnimatorSet#playSequentially(Animator[]) playSequentially()} methods can be called to add 40 * a set of animations all at once, or the {@link AnimatorSet#play(Animator)} can be 41 * used in conjunction with methods in the {@link AnimatorSet.Builder Builder} 42 * class to add animations 43 * one by one.</p> 44 * 45 * <p>It is possible to set up a <code>AnimatorSet</code> with circular dependencies between 46 * its animations. For example, an animation a1 could be set up to start before animation a2, a2 47 * before a3, and a3 before a1. The results of this configuration are undefined, but will typically 48 * result in none of the affected animations being played. Because of this (and because 49 * circular dependencies do not make logical sense anyway), circular dependencies 50 * should be avoided, and the dependency flow of animations should only be in one direction. 51 * 52 * <div class="special reference"> 53 * <h3>Developer Guides</h3> 54 * <p>For more information about animating with {@code AnimatorSet}, read the 55 * <a href="{@docRoot}guide/topics/graphics/prop-animation.html#choreography">Property 56 * Animation</a> developer guide.</p> 57 * </div> 58 */ 59public final class AnimatorSet extends Animator implements AnimationHandler.AnimationFrameCallback { 60 61 private static final String TAG = "AnimatorSet"; 62 /** 63 * Internal variables 64 * NOTE: This object implements the clone() method, making a deep copy of any referenced 65 * objects. As other non-trivial fields are added to this class, make sure to add logic 66 * to clone() to make deep copies of them. 67 */ 68 69 /** 70 * Tracks animations currently being played, so that we know what to 71 * cancel or end when cancel() or end() is called on this AnimatorSet 72 */ 73 private ArrayList<Node> mPlayingSet = new ArrayList<Node>(); 74 75 /** 76 * Contains all nodes, mapped to their respective Animators. When new 77 * dependency information is added for an Animator, we want to add it 78 * to a single node representing that Animator, not create a new Node 79 * if one already exists. 80 */ 81 private ArrayMap<Animator, Node> mNodeMap = new ArrayMap<Animator, Node>(); 82 83 /** 84 * Contains the start and end events of all the nodes. All these events are sorted in this list. 85 */ 86 private ArrayList<AnimationEvent> mEvents = new ArrayList<>(); 87 88 /** 89 * Set of all nodes created for this AnimatorSet. This list is used upon 90 * starting the set, and the nodes are placed in sorted order into the 91 * sortedNodes collection. 92 */ 93 private ArrayList<Node> mNodes = new ArrayList<Node>(); 94 95 /** 96 * Tracks whether any change has been made to the AnimatorSet, which is then used to 97 * determine whether the dependency graph should be re-constructed. 98 */ 99 private boolean mDependencyDirty = false; 100 101 /** 102 * Indicates whether an AnimatorSet has been start()'d, whether or 103 * not there is a nonzero startDelay. 104 */ 105 private boolean mStarted = false; 106 107 // The amount of time in ms to delay starting the animation after start() is called 108 private long mStartDelay = 0; 109 110 // Animator used for a nonzero startDelay 111 private ValueAnimator mDelayAnim = ValueAnimator.ofFloat(0f, 1f).setDuration(0); 112 113 // Root of the dependency tree of all the animators in the set. In this tree, parent-child 114 // relationship captures the order of animation (i.e. parent and child will play sequentially), 115 // and sibling relationship indicates "with" relationship, as sibling animators start at the 116 // same time. 117 private Node mRootNode = new Node(mDelayAnim); 118 119 // How long the child animations should last in ms. The default value is negative, which 120 // simply means that there is no duration set on the AnimatorSet. When a real duration is 121 // set, it is passed along to the child animations. 122 private long mDuration = -1; 123 124 // Records the interpolator for the set. Null value indicates that no interpolator 125 // was set on this AnimatorSet, so it should not be passed down to the children. 126 private TimeInterpolator mInterpolator = null; 127 128 // The total duration of finishing all the Animators in the set. 129 private long mTotalDuration = 0; 130 131 // In pre-N releases, calling end() before start() on an animator set is no-op. But that is not 132 // consistent with the behavior for other animator types. In order to keep the behavior 133 // consistent within Animation framework, when end() is called without start(), we will start 134 // the animator set and immediately end it for N and forward. 135 private final boolean mShouldIgnoreEndWithoutStart; 136 137 // In pre-O releases, calling start() doesn't reset all the animators values to start values. 138 // As a result, the start of the animation is inconsistent with what setCurrentPlayTime(0) would 139 // look like on O. Also it is inconsistent with what reverse() does on O, as reverse would 140 // advance all the animations to the right beginning values for before starting to reverse. 141 // From O and forward, we will add an additional step of resetting the animation values (unless 142 // the animation was previously seeked and therefore doesn't start from the beginning). 143 private final boolean mShouldResetValuesAtStart; 144 145 // The time, in milliseconds, when last frame of the animation came in. -1 when the animation is 146 // not running. 147 private long mLastFrameTime = -1; 148 149 // The time, in milliseconds, when the first frame of the animation came in. 150 // -1 when the animation is not running. 151 private long mFirstFrame = -1; 152 153 // The time, in milliseconds, when the first frame of the animation came in. 154 // -1 when the animation is not running. 155 private int mLastEventId = -1; 156 157 // Indicates whether the animation is reversing. 158 private boolean mReversing = false; 159 160 // Indicates whether the animation should register frame callbacks. If false, the animation will 161 // passively wait for an AnimatorSet to pulse it. 162 private boolean mSelfPulse = true; 163 164 // SeekState stores the last seeked play time as well as seek direction. 165 private SeekState mSeekState = new SeekState(); 166 167 // Indicates where children animators are all initialized with their start values captured. 168 private boolean mChildrenInitialized = false; 169 170 /** 171 * Set on the next frame after pause() is called, used to calculate a new startTime 172 * or delayStartTime which allows the animator set to continue from the point at which 173 * it was paused. If negative, has not yet been set. 174 */ 175 private long mPauseTime = -1; 176 177 // This is to work around a bug in b/34736819. This needs to be removed once play team 178 // fixes their side. 179 private AnimatorListenerAdapter mDummyListener = new AnimatorListenerAdapter() {}; 180 181 public AnimatorSet() { 182 super(); 183 mNodeMap.put(mDelayAnim, mRootNode); 184 mNodes.add(mRootNode); 185 // Set the flag to ignore calling end() without start() for pre-N releases 186 Application app = ActivityThread.currentApplication(); 187 if (app == null || app.getApplicationInfo() == null) { 188 mShouldIgnoreEndWithoutStart = true; 189 mShouldResetValuesAtStart = false; 190 } else { 191 if (app.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) { 192 mShouldIgnoreEndWithoutStart = true; 193 } else { 194 mShouldIgnoreEndWithoutStart = false; 195 } 196 197 if (app.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.O) { 198 mShouldResetValuesAtStart = false; 199 } else { 200 mShouldResetValuesAtStart = true; 201 } 202 } 203 } 204 205 /** 206 * Sets up this AnimatorSet to play all of the supplied animations at the same time. 207 * This is equivalent to calling {@link #play(Animator)} with the first animator in the 208 * set and then {@link Builder#with(Animator)} with each of the other animators. Note that 209 * an Animator with a {@link Animator#setStartDelay(long) startDelay} will not actually 210 * start until that delay elapses, which means that if the first animator in the list 211 * supplied to this constructor has a startDelay, none of the other animators will start 212 * until that first animator's startDelay has elapsed. 213 * 214 * @param items The animations that will be started simultaneously. 215 */ 216 public void playTogether(Animator... items) { 217 if (items != null) { 218 Builder builder = play(items[0]); 219 for (int i = 1; i < items.length; ++i) { 220 builder.with(items[i]); 221 } 222 } 223 } 224 225 /** 226 * Sets up this AnimatorSet to play all of the supplied animations at the same time. 227 * 228 * @param items The animations that will be started simultaneously. 229 */ 230 public void playTogether(Collection<Animator> items) { 231 if (items != null && items.size() > 0) { 232 Builder builder = null; 233 for (Animator anim : items) { 234 if (builder == null) { 235 builder = play(anim); 236 } else { 237 builder.with(anim); 238 } 239 } 240 } 241 } 242 243 /** 244 * Sets up this AnimatorSet to play each of the supplied animations when the 245 * previous animation ends. 246 * 247 * @param items The animations that will be started one after another. 248 */ 249 public void playSequentially(Animator... items) { 250 if (items != null) { 251 if (items.length == 1) { 252 play(items[0]); 253 } else { 254 for (int i = 0; i < items.length - 1; ++i) { 255 play(items[i]).before(items[i + 1]); 256 } 257 } 258 } 259 } 260 261 /** 262 * Sets up this AnimatorSet to play each of the supplied animations when the 263 * previous animation ends. 264 * 265 * @param items The animations that will be started one after another. 266 */ 267 public void playSequentially(List<Animator> items) { 268 if (items != null && items.size() > 0) { 269 if (items.size() == 1) { 270 play(items.get(0)); 271 } else { 272 for (int i = 0; i < items.size() - 1; ++i) { 273 play(items.get(i)).before(items.get(i + 1)); 274 } 275 } 276 } 277 } 278 279 /** 280 * Returns the current list of child Animator objects controlled by this 281 * AnimatorSet. This is a copy of the internal list; modifications to the returned list 282 * will not affect the AnimatorSet, although changes to the underlying Animator objects 283 * will affect those objects being managed by the AnimatorSet. 284 * 285 * @return ArrayList<Animator> The list of child animations of this AnimatorSet. 286 */ 287 public ArrayList<Animator> getChildAnimations() { 288 ArrayList<Animator> childList = new ArrayList<Animator>(); 289 int size = mNodes.size(); 290 for (int i = 0; i < size; i++) { 291 Node node = mNodes.get(i); 292 if (node != mRootNode) { 293 childList.add(node.mAnimation); 294 } 295 } 296 return childList; 297 } 298 299 /** 300 * Sets the target object for all current {@link #getChildAnimations() child animations} 301 * of this AnimatorSet that take targets ({@link ObjectAnimator} and 302 * AnimatorSet). 303 * 304 * @param target The object being animated 305 */ 306 @Override 307 public void setTarget(Object target) { 308 int size = mNodes.size(); 309 for (int i = 0; i < size; i++) { 310 Node node = mNodes.get(i); 311 Animator animation = node.mAnimation; 312 if (animation instanceof AnimatorSet) { 313 ((AnimatorSet)animation).setTarget(target); 314 } else if (animation instanceof ObjectAnimator) { 315 ((ObjectAnimator)animation).setTarget(target); 316 } 317 } 318 } 319 320 /** 321 * @hide 322 */ 323 @Override 324 public int getChangingConfigurations() { 325 int conf = super.getChangingConfigurations(); 326 final int nodeCount = mNodes.size(); 327 for (int i = 0; i < nodeCount; i ++) { 328 conf |= mNodes.get(i).mAnimation.getChangingConfigurations(); 329 } 330 return conf; 331 } 332 333 /** 334 * Sets the TimeInterpolator for all current {@link #getChildAnimations() child animations} 335 * of this AnimatorSet. The default value is null, which means that no interpolator 336 * is set on this AnimatorSet. Setting the interpolator to any non-null value 337 * will cause that interpolator to be set on the child animations 338 * when the set is started. 339 * 340 * @param interpolator the interpolator to be used by each child animation of this AnimatorSet 341 */ 342 @Override 343 public void setInterpolator(TimeInterpolator interpolator) { 344 mInterpolator = interpolator; 345 } 346 347 @Override 348 public TimeInterpolator getInterpolator() { 349 return mInterpolator; 350 } 351 352 /** 353 * This method creates a <code>Builder</code> object, which is used to 354 * set up playing constraints. This initial <code>play()</code> method 355 * tells the <code>Builder</code> the animation that is the dependency for 356 * the succeeding commands to the <code>Builder</code>. For example, 357 * calling <code>play(a1).with(a2)</code> sets up the AnimatorSet to play 358 * <code>a1</code> and <code>a2</code> at the same time, 359 * <code>play(a1).before(a2)</code> sets up the AnimatorSet to play 360 * <code>a1</code> first, followed by <code>a2</code>, and 361 * <code>play(a1).after(a2)</code> sets up the AnimatorSet to play 362 * <code>a2</code> first, followed by <code>a1</code>. 363 * 364 * <p>Note that <code>play()</code> is the only way to tell the 365 * <code>Builder</code> the animation upon which the dependency is created, 366 * so successive calls to the various functions in <code>Builder</code> 367 * will all refer to the initial parameter supplied in <code>play()</code> 368 * as the dependency of the other animations. For example, calling 369 * <code>play(a1).before(a2).before(a3)</code> will play both <code>a2</code> 370 * and <code>a3</code> when a1 ends; it does not set up a dependency between 371 * <code>a2</code> and <code>a3</code>.</p> 372 * 373 * @param anim The animation that is the dependency used in later calls to the 374 * methods in the returned <code>Builder</code> object. A null parameter will result 375 * in a null <code>Builder</code> return value. 376 * @return Builder The object that constructs the AnimatorSet based on the dependencies 377 * outlined in the calls to <code>play</code> and the other methods in the 378 * <code>Builder</code object. 379 */ 380 public Builder play(Animator anim) { 381 if (anim != null) { 382 return new Builder(anim); 383 } 384 return null; 385 } 386 387 /** 388 * {@inheritDoc} 389 * 390 * <p>Note that canceling a <code>AnimatorSet</code> also cancels all of the animations that it 391 * is responsible for.</p> 392 */ 393 @SuppressWarnings("unchecked") 394 @Override 395 public void cancel() { 396 if (Looper.myLooper() == null) { 397 throw new AndroidRuntimeException("Animators may only be run on Looper threads"); 398 } 399 if (isStarted()) { 400 ArrayList<AnimatorListener> tmpListeners = null; 401 if (mListeners != null) { 402 tmpListeners = (ArrayList<AnimatorListener>) mListeners.clone(); 403 int size = tmpListeners.size(); 404 for (int i = 0; i < size; i++) { 405 tmpListeners.get(i).onAnimationCancel(this); 406 } 407 } 408 ArrayList<Node> playingSet = new ArrayList<>(mPlayingSet); 409 int setSize = playingSet.size(); 410 for (int i = 0; i < setSize; i++) { 411 playingSet.get(i).mAnimation.cancel(); 412 } 413 mPlayingSet.clear(); 414 endAnimation(); 415 } 416 } 417 418 /** 419 * {@inheritDoc} 420 * 421 * <p>Note that ending a <code>AnimatorSet</code> also ends all of the animations that it is 422 * responsible for.</p> 423 */ 424 @Override 425 public void end() { 426 if (Looper.myLooper() == null) { 427 throw new AndroidRuntimeException("Animators may only be run on Looper threads"); 428 } 429 if (mShouldIgnoreEndWithoutStart && !isStarted()) { 430 return; 431 } 432 if (isStarted()) { 433 // Iterate the animations that haven't finished or haven't started, and end them. 434 if (mReversing) { 435 // Between start() and first frame, mLastEventId would be unset (i.e. -1) 436 mLastEventId = mLastEventId == -1 ? mEvents.size() : mLastEventId; 437 for (int j = mLastEventId - 1; j >= 0; j--) { 438 AnimationEvent event = mEvents.get(j); 439 if (event.mEvent == AnimationEvent.ANIMATION_END) { 440 event.mNode.mAnimation.reverse(); 441 } else if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) { 442 event.mNode.mAnimation.end(); 443 } 444 } 445 } else { 446 for (int j = mLastEventId + 1; j < mEvents.size(); j++) { 447 AnimationEvent event = mEvents.get(j); 448 if (event.mEvent == AnimationEvent.ANIMATION_START) { 449 event.mNode.mAnimation.start(); 450 } else if (event.mEvent == AnimationEvent.ANIMATION_END) { 451 event.mNode.mAnimation.end(); 452 } 453 } 454 } 455 mPlayingSet.clear(); 456 } 457 endAnimation(); 458 } 459 460 /** 461 * Returns true if any of the child animations of this AnimatorSet have been started and have 462 * not yet ended. Child animations will not be started until the AnimatorSet has gone past 463 * its initial delay set through {@link #setStartDelay(long)}. 464 * 465 * @return Whether this AnimatorSet has gone past the initial delay, and at least one child 466 * animation has been started and not yet ended. 467 */ 468 @Override 469 public boolean isRunning() { 470 if (mStartDelay > 0) { 471 return mStarted && !mDelayAnim.isRunning(); 472 } else { 473 // No start delay, animation should start right away 474 return mStarted; 475 } 476 } 477 478 @Override 479 public boolean isStarted() { 480 return mStarted; 481 } 482 483 /** 484 * The amount of time, in milliseconds, to delay starting the animation after 485 * {@link #start()} is called. 486 * 487 * @return the number of milliseconds to delay running the animation 488 */ 489 @Override 490 public long getStartDelay() { 491 return mStartDelay; 492 } 493 494 /** 495 * The amount of time, in milliseconds, to delay starting the animation after 496 * {@link #start()} is called. Note that the start delay should always be non-negative. Any 497 * negative start delay will be clamped to 0 on N and above. 498 * 499 * @param startDelay The amount of the delay, in milliseconds 500 */ 501 @Override 502 public void setStartDelay(long startDelay) { 503 // Clamp start delay to non-negative range. 504 if (startDelay < 0) { 505 Log.w(TAG, "Start delay should always be non-negative"); 506 startDelay = 0; 507 } 508 long delta = startDelay - mStartDelay; 509 if (delta == 0) { 510 return; 511 } 512 mStartDelay = startDelay; 513 if (!mDependencyDirty) { 514 // Dependency graph already constructed, update all the nodes' start/end time 515 int size = mNodes.size(); 516 for (int i = 0; i < size; i++) { 517 Node node = mNodes.get(i); 518 if (node == mRootNode) { 519 node.mEndTime = mStartDelay; 520 } else { 521 node.mStartTime = node.mStartTime == DURATION_INFINITE ? 522 DURATION_INFINITE : node.mStartTime + delta; 523 node.mEndTime = node.mEndTime == DURATION_INFINITE ? 524 DURATION_INFINITE : node.mEndTime + delta; 525 } 526 } 527 // Update total duration, if necessary. 528 if (mTotalDuration != DURATION_INFINITE) { 529 mTotalDuration += delta; 530 } 531 } 532 } 533 534 /** 535 * Gets the length of each of the child animations of this AnimatorSet. This value may 536 * be less than 0, which indicates that no duration has been set on this AnimatorSet 537 * and each of the child animations will use their own duration. 538 * 539 * @return The length of the animation, in milliseconds, of each of the child 540 * animations of this AnimatorSet. 541 */ 542 @Override 543 public long getDuration() { 544 return mDuration; 545 } 546 547 /** 548 * Sets the length of each of the current child animations of this AnimatorSet. By default, 549 * each child animation will use its own duration. If the duration is set on the AnimatorSet, 550 * then each child animation inherits this duration. 551 * 552 * @param duration The length of the animation, in milliseconds, of each of the child 553 * animations of this AnimatorSet. 554 */ 555 @Override 556 public AnimatorSet setDuration(long duration) { 557 if (duration < 0) { 558 throw new IllegalArgumentException("duration must be a value of zero or greater"); 559 } 560 mDependencyDirty = true; 561 // Just record the value for now - it will be used later when the AnimatorSet starts 562 mDuration = duration; 563 return this; 564 } 565 566 @Override 567 public void setupStartValues() { 568 int size = mNodes.size(); 569 for (int i = 0; i < size; i++) { 570 Node node = mNodes.get(i); 571 if (node != mRootNode) { 572 node.mAnimation.setupStartValues(); 573 } 574 } 575 } 576 577 @Override 578 public void setupEndValues() { 579 int size = mNodes.size(); 580 for (int i = 0; i < size; i++) { 581 Node node = mNodes.get(i); 582 if (node != mRootNode) { 583 node.mAnimation.setupEndValues(); 584 } 585 } 586 } 587 588 @Override 589 public void pause() { 590 if (Looper.myLooper() == null) { 591 throw new AndroidRuntimeException("Animators may only be run on Looper threads"); 592 } 593 boolean previouslyPaused = mPaused; 594 super.pause(); 595 if (!previouslyPaused && mPaused) { 596 mPauseTime = -1; 597 } 598 } 599 600 @Override 601 public void resume() { 602 if (Looper.myLooper() == null) { 603 throw new AndroidRuntimeException("Animators may only be run on Looper threads"); 604 } 605 boolean previouslyPaused = mPaused; 606 super.resume(); 607 if (previouslyPaused && !mPaused) { 608 if (mPauseTime >= 0) { 609 addAnimationCallback(0); 610 } 611 } 612 } 613 614 /** 615 * {@inheritDoc} 616 * 617 * <p>Starting this <code>AnimatorSet</code> will, in turn, start the animations for which 618 * it is responsible. The details of when exactly those animations are started depends on 619 * the dependency relationships that have been set up between the animations. 620 */ 621 @SuppressWarnings("unchecked") 622 @Override 623 public void start() { 624 start(false, true); 625 } 626 627 @Override 628 void startWithoutPulsing(boolean inReverse) { 629 start(inReverse, false); 630 } 631 632 private void initAnimation() { 633 if (mInterpolator != null) { 634 for (int i = 0; i < mNodes.size(); i++) { 635 Node node = mNodes.get(i); 636 node.mAnimation.setInterpolator(mInterpolator); 637 } 638 } 639 updateAnimatorsDuration(); 640 createDependencyGraph(); 641 } 642 643 private void start(boolean inReverse, boolean selfPulse) { 644 if (Looper.myLooper() == null) { 645 throw new AndroidRuntimeException("Animators may only be run on Looper threads"); 646 } 647 mStarted = true; 648 mSelfPulse = selfPulse; 649 mPaused = false; 650 mPauseTime = -1; 651 652 int size = mNodes.size(); 653 for (int i = 0; i < size; i++) { 654 Node node = mNodes.get(i); 655 node.mEnded = false; 656 node.mAnimation.setAllowRunningAsynchronously(false); 657 } 658 659 initAnimation(); 660 if (inReverse && !canReverse()) { 661 throw new UnsupportedOperationException("Cannot reverse infinite AnimatorSet"); 662 } 663 664 mReversing = inReverse; 665 666 // Now that all dependencies are set up, start the animations that should be started. 667 boolean setIsEmpty = isEmptySet(this); 668 if (!setIsEmpty) { 669 startAnimation(); 670 } 671 672 if (mListeners != null) { 673 ArrayList<AnimatorListener> tmpListeners = 674 (ArrayList<AnimatorListener>) mListeners.clone(); 675 int numListeners = tmpListeners.size(); 676 for (int i = 0; i < numListeners; ++i) { 677 tmpListeners.get(i).onAnimationStart(this, inReverse); 678 } 679 } 680 if (setIsEmpty) { 681 // In the case of empty AnimatorSet, we will trigger the onAnimationEnd() right away. 682 end(); 683 } 684 } 685 686 // Returns true if set is empty or contains nothing but animator sets with no start delay. 687 private static boolean isEmptySet(AnimatorSet set) { 688 if (set.getStartDelay() > 0) { 689 return false; 690 } 691 for (int i = 0; i < set.getChildAnimations().size(); i++) { 692 Animator anim = set.getChildAnimations().get(i); 693 if (!(anim instanceof AnimatorSet)) { 694 // Contains non-AnimatorSet, not empty. 695 return false; 696 } else { 697 if (!isEmptySet((AnimatorSet) anim)) { 698 return false; 699 } 700 } 701 } 702 return true; 703 } 704 705 private void updateAnimatorsDuration() { 706 if (mDuration >= 0) { 707 // If the duration was set on this AnimatorSet, pass it along to all child animations 708 int size = mNodes.size(); 709 for (int i = 0; i < size; i++) { 710 Node node = mNodes.get(i); 711 // TODO: don't set the duration of the timing-only nodes created by AnimatorSet to 712 // insert "play-after" delays 713 node.mAnimation.setDuration(mDuration); 714 } 715 } 716 mDelayAnim.setDuration(mStartDelay); 717 } 718 719 @Override 720 void skipToEndValue(boolean inReverse) { 721 if (!isInitialized()) { 722 throw new UnsupportedOperationException("Children must be initialized."); 723 } 724 725 // This makes sure the animation events are sorted an up to date. 726 initAnimation(); 727 728 // Calling skip to the end in the sequence that they would be called in a forward/reverse 729 // run, such that the sequential animations modifying the same property would have 730 // the right value in the end. 731 if (inReverse) { 732 for (int i = mEvents.size() - 1; i >= 0; i--) { 733 if (mEvents.get(i).mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) { 734 mEvents.get(i).mNode.mAnimation.skipToEndValue(true); 735 } 736 } 737 } else { 738 for (int i = 0; i < mEvents.size(); i++) { 739 if (mEvents.get(i).mEvent == AnimationEvent.ANIMATION_END) { 740 mEvents.get(i).mNode.mAnimation.skipToEndValue(false); 741 } 742 } 743 } 744 } 745 746 /** 747 * Internal only. 748 * 749 * This method sets the animation values based on the play time. It also fast forward or 750 * backward all the child animations progress accordingly. 751 * 752 * This method is also responsible for calling 753 * {@link android.view.animation.Animation.AnimationListener#onAnimationRepeat(Animation)}, 754 * as needed, based on the last play time and current play time. 755 */ 756 @Override 757 void animateBasedOnPlayTime(long currentPlayTime, long lastPlayTime, boolean inReverse) { 758 if (currentPlayTime < 0 || lastPlayTime < 0) { 759 throw new UnsupportedOperationException("Error: Play time should never be negative."); 760 } 761 // TODO: take into account repeat counts and repeat callback when repeat is implemented. 762 // Clamp currentPlayTime and lastPlayTime 763 764 // TODO: Make this more efficient 765 766 // Convert the play times to the forward direction. 767 if (inReverse) { 768 if (getTotalDuration() == DURATION_INFINITE) { 769 throw new UnsupportedOperationException("Cannot reverse AnimatorSet with infinite" 770 + " duration"); 771 } 772 long duration = getTotalDuration() - mStartDelay; 773 currentPlayTime = Math.min(currentPlayTime, duration); 774 currentPlayTime = duration - currentPlayTime; 775 lastPlayTime = duration - lastPlayTime; 776 inReverse = false; 777 } 778 // Skip all values to start, and iterate mEvents to get animations to the right fraction. 779 skipToStartValue(false); 780 781 ArrayList<Node> unfinishedNodes = new ArrayList<>(); 782 // Assumes forward playing from here on. 783 for (int i = 0; i < mEvents.size(); i++) { 784 AnimationEvent event = mEvents.get(i); 785 if (event.getTime() > currentPlayTime) { 786 break; 787 } 788 789 // This animation started prior to the current play time, and won't finish before the 790 // play time, add to the unfinished list. 791 if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) { 792 if (event.mNode.mEndTime == DURATION_INFINITE 793 || event.mNode.mEndTime > currentPlayTime) { 794 unfinishedNodes.add(event.mNode); 795 } 796 } 797 // For animations that do finish before the play time, end them in the sequence that 798 // they would in a normal run. 799 if (event.mEvent == AnimationEvent.ANIMATION_END) { 800 // Skip to the end of the animation. 801 event.mNode.mAnimation.skipToEndValue(false); 802 } 803 } 804 805 // Seek unfinished animation to the right time. 806 for (int i = 0; i < unfinishedNodes.size(); i++) { 807 Node node = unfinishedNodes.get(i); 808 long playTime = getPlayTimeForNode(currentPlayTime, node, inReverse); 809 node.mAnimation.animateBasedOnPlayTime(playTime, lastPlayTime, inReverse); 810 } 811 } 812 813 @Override 814 boolean isInitialized() { 815 if (mChildrenInitialized) { 816 return true; 817 } 818 819 boolean allInitialized = true; 820 for (int i = 0; i < mNodes.size(); i++) { 821 if (!mNodes.get(i).mAnimation.isInitialized()) { 822 allInitialized = false; 823 break; 824 } 825 } 826 mChildrenInitialized = allInitialized; 827 return mChildrenInitialized; 828 } 829 830 private void skipToStartValue(boolean inReverse) { 831 skipToEndValue(!inReverse); 832 } 833 834 /** 835 * Sets the position of the animation to the specified point in time. This time should 836 * be between 0 and the total duration of the animation, including any repetition. If 837 * the animation has not yet been started, then it will not advance forward after it is 838 * set to this time; it will simply set the time to this value and perform any appropriate 839 * actions based on that time. If the animation is already running, then setCurrentPlayTime() 840 * will set the current playing time to this value and continue playing from that point. 841 * 842 * @param playTime The time, in milliseconds, to which the animation is advanced or rewound. 843 * Unless the animation is reversing, the playtime is considered the time since 844 * the end of the start delay of the AnimatorSet in a forward playing direction. 845 * 846 */ 847 public void setCurrentPlayTime(long playTime) { 848 if (mReversing && getTotalDuration() == DURATION_INFINITE) { 849 // Should never get here 850 throw new UnsupportedOperationException("Error: Cannot seek in reverse in an infinite" 851 + " AnimatorSet"); 852 } 853 854 if ((getTotalDuration() != DURATION_INFINITE && playTime > getTotalDuration() - mStartDelay) 855 || playTime < 0) { 856 throw new UnsupportedOperationException("Error: Play time should always be in between" 857 + "0 and duration."); 858 } 859 860 initAnimation(); 861 862 if (!isStarted()) { 863 if (mReversing) { 864 throw new UnsupportedOperationException("Error: Something went wrong. mReversing" 865 + " should not be set when AnimatorSet is not started."); 866 } 867 if (!mSeekState.isActive()) { 868 findLatestEventIdForTime(0); 869 // Set all the values to start values. 870 initChildren(); 871 skipToStartValue(mReversing); 872 mSeekState.setPlayTime(0, mReversing); 873 } 874 animateBasedOnPlayTime(playTime, 0, mReversing); 875 mSeekState.setPlayTime(playTime, mReversing); 876 } else { 877 // If the animation is running, just set the seek time and wait until the next frame 878 // (i.e. doAnimationFrame(...)) to advance the animation. 879 mSeekState.setPlayTime(playTime, mReversing); 880 } 881 } 882 883 private void initChildren() { 884 if (!isInitialized()) { 885 mChildrenInitialized = true; 886 // Forcefully initialize all children based on their end time, so that if the start 887 // value of a child is dependent on a previous animation, the animation will be 888 // initialized after the the previous animations have been advanced to the end. 889 skipToEndValue(false); 890 } 891 } 892 893 /** 894 * @param frameTime The frame start time, in the {@link SystemClock#uptimeMillis()} time 895 * base. 896 * @return 897 * @hide 898 */ 899 @Override 900 public boolean doAnimationFrame(long frameTime) { 901 if (mLastFrameTime < 0) { 902 mFirstFrame = mLastFrameTime = frameTime; 903 } 904 905 // Handle pause/resume 906 if (mPaused) { 907 // Note: Child animations don't receive pause events. Since it's never a contract that 908 // the child animators will be paused when set is paused, this is unlikely to be an 909 // issue. 910 mPauseTime = frameTime; 911 removeAnimationCallback(); 912 return false; 913 } else if (mPauseTime > 0) { 914 // Offset by the duration that the animation was paused 915 mFirstFrame += (frameTime - mPauseTime); 916 mPauseTime = -1; 917 } 918 919 // Continue at seeked position 920 if (mSeekState.isActive()) { 921 mSeekState.updateSeekDirection(mReversing); 922 mFirstFrame = frameTime - mSeekState.getPlayTime() - mStartDelay; 923 mSeekState.reset(); 924 } 925 926 // This playTime includes the start delay. 927 long playTime = frameTime - mFirstFrame; 928 929 // 1. Pulse the animators that will start or end in this frame 930 // 2. Pulse the animators that will finish in a later frame 931 int latestId = findLatestEventIdForTime(playTime); 932 int startId = mLastEventId; 933 934 handleAnimationEvents(startId, latestId, playTime); 935 936 mLastEventId = latestId; 937 938 // Pump a frame to the on-going animators 939 for (int i = 0; i < mPlayingSet.size(); i++) { 940 Node node = mPlayingSet.get(i); 941 if (!node.mEnded) { 942 node.mEnded = node.mAnimation.doAnimationFrame(getPlayTimeForNode(playTime, node)); 943 } 944 } 945 946 // Remove all the finished anims 947 for (int i = mPlayingSet.size() - 1; i >= 0; i--) { 948 if (mPlayingSet.get(i).mEnded) { 949 mPlayingSet.remove(i); 950 } 951 } 952 953 mLastFrameTime = frameTime; 954 if (mPlayingSet.isEmpty()) { 955 boolean finished; 956 if (mReversing) { 957 // Make sure there's no more END event before current event id and after start delay 958 finished = mLastEventId <= 3; 959 } else { 960 // Make sure there's no more START event before current event id: 961 finished = (mLastEventId == mEvents.size() - 1); 962 } 963 if (finished) { 964 endAnimation(); 965 return true; 966 } 967 } 968 return false; 969 } 970 971 /** 972 * When playing forward, we call start() at the animation's scheduled start time, and make sure 973 * to pump a frame at the animation's scheduled end time. 974 * 975 * When playing in reverse, we should reverse the animation when we hit animation's end event, 976 * and expect the animation to end at the its delay ended event, rather than start event. 977 */ 978 private void handleAnimationEvents(int startId, int latestId, long playTime) { 979 if (mReversing) { 980 startId = startId == -1 ? mEvents.size() : startId; 981 for (int i = startId - 1; i >= latestId; i--) { 982 AnimationEvent event = mEvents.get(i); 983 Node node = event.mNode; 984 if (event.mEvent == AnimationEvent.ANIMATION_END) { 985 mPlayingSet.add(event.mNode); 986 node.mAnimation.startWithoutPulsing(true); 987 node.mAnimation.doAnimationFrame(0); 988 } else if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED && !node.mEnded) { 989 // end event: 990 node.mEnded = 991 node.mAnimation.doAnimationFrame(getPlayTimeForNode(playTime, node)); 992 } 993 } 994 } else { 995 for (int i = startId + 1; i <= latestId; i++) { 996 AnimationEvent event = mEvents.get(i); 997 Node node = event.mNode; 998 if (event.mEvent == AnimationEvent.ANIMATION_START) { 999 mPlayingSet.add(event.mNode); 1000 node.mAnimation.startWithoutPulsing(false); 1001 node.mAnimation.doAnimationFrame(0); 1002 } else if (event.mEvent == AnimationEvent.ANIMATION_END && !node.mEnded) { 1003 // start event: 1004 node.mEnded = 1005 node.mAnimation.doAnimationFrame(getPlayTimeForNode(playTime, node)); 1006 } 1007 } 1008 } 1009 } 1010 1011 private long getPlayTimeForNode(long overallPlayTime, Node node) { 1012 return getPlayTimeForNode(overallPlayTime, node, mReversing); 1013 } 1014 1015 private long getPlayTimeForNode(long overallPlayTime, Node node, boolean inReverse) { 1016 if (inReverse) { 1017 overallPlayTime = getTotalDuration() - overallPlayTime; 1018 return node.mEndTime - overallPlayTime; 1019 } else { 1020 return overallPlayTime - node.mStartTime; 1021 } 1022 } 1023 1024 private void startAnimation() { 1025 addDummyListener(); 1026 1027 // Register animation callback 1028 addAnimationCallback(mStartDelay); 1029 1030 if (mSeekState.getPlayTimeNormalized() == 0 && mReversing) { 1031 // Maintain old behavior, if seeked to 0 then call reverse, we'll treat the case 1032 // the same as no seeking at all. 1033 mSeekState.reset(); 1034 } 1035 // Set the child animators to the right end: 1036 if (mShouldResetValuesAtStart) { 1037 if (mReversing || isInitialized()) { 1038 skipToEndValue(!mReversing); 1039 } else { 1040 // If not all children are initialized and play direction is forward 1041 for (int i = mEvents.size() - 1; i >= 0; i--) { 1042 if (mEvents.get(i).mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) { 1043 Animator anim = mEvents.get(i).mNode.mAnimation; 1044 // Only reset the animations that have been initialized to start value, 1045 // so that if they are defined without a start value, they will get the 1046 // values set at the right time (i.e. the next animation run) 1047 if (anim.isInitialized()) { 1048 anim.skipToEndValue(true); 1049 } 1050 } 1051 } 1052 } 1053 } 1054 1055 if (mReversing || mStartDelay == 0 || mSeekState.isActive()) { 1056 long playTime; 1057 // If no delay, we need to call start on the first animations to be consistent with old 1058 // behavior. 1059 if (mSeekState.isActive()) { 1060 mSeekState.updateSeekDirection(mReversing); 1061 playTime = mSeekState.getPlayTime(); 1062 } else { 1063 playTime = 0; 1064 } 1065 int toId = findLatestEventIdForTime(playTime); 1066 handleAnimationEvents(-1, toId, playTime); 1067 mLastEventId = toId; 1068 } 1069 } 1070 1071 // This is to work around the issue in b/34736819, as the old behavior in AnimatorSet had 1072 // masked a real bug in play movies. TODO: remove this and below once the root cause is fixed. 1073 private void addDummyListener() { 1074 for (int i = 1; i < mNodes.size(); i++) { 1075 mNodes.get(i).mAnimation.addListener(mDummyListener); 1076 } 1077 } 1078 1079 private void removeDummyListener() { 1080 for (int i = 1; i < mNodes.size(); i++) { 1081 mNodes.get(i).mAnimation.removeListener(mDummyListener); 1082 } 1083 } 1084 1085 private int findLatestEventIdForTime(long currentPlayTime) { 1086 int size = mEvents.size(); 1087 int latestId = mLastEventId; 1088 // Call start on the first animations now to be consistent with the old behavior 1089 if (mReversing) { 1090 currentPlayTime = getTotalDuration() - currentPlayTime; 1091 mLastEventId = mLastEventId == -1 ? size : mLastEventId; 1092 for (int j = mLastEventId - 1; j >= 0; j--) { 1093 AnimationEvent event = mEvents.get(j); 1094 if (event.getTime() >= currentPlayTime) { 1095 latestId = j; 1096 } 1097 } 1098 } else { 1099 for (int i = mLastEventId + 1; i < size; i++) { 1100 AnimationEvent event = mEvents.get(i); 1101 if (event.getTime() <= currentPlayTime) { 1102 latestId = i; 1103 } 1104 } 1105 } 1106 return latestId; 1107 } 1108 1109 private void endAnimation() { 1110 mStarted = false; 1111 mLastFrameTime = -1; 1112 mFirstFrame = -1; 1113 mLastEventId = -1; 1114 mPaused = false; 1115 mPauseTime = -1; 1116 mSeekState.reset(); 1117 mPlayingSet.clear(); 1118 1119 // No longer receive callbacks 1120 removeAnimationCallback(); 1121 // Call end listener 1122 if (mListeners != null) { 1123 ArrayList<AnimatorListener> tmpListeners = 1124 (ArrayList<AnimatorListener>) mListeners.clone(); 1125 int numListeners = tmpListeners.size(); 1126 for (int i = 0; i < numListeners; ++i) { 1127 tmpListeners.get(i).onAnimationEnd(this, mReversing); 1128 } 1129 } 1130 removeDummyListener(); 1131 mSelfPulse = true; 1132 mReversing = false; 1133 } 1134 1135 private void removeAnimationCallback() { 1136 if (!mSelfPulse) { 1137 return; 1138 } 1139 AnimationHandler handler = AnimationHandler.getInstance(); 1140 handler.removeCallback(this); 1141 } 1142 1143 private void addAnimationCallback(long delay) { 1144 if (!mSelfPulse) { 1145 return; 1146 } 1147 AnimationHandler handler = AnimationHandler.getInstance(); 1148 handler.addAnimationFrameCallback(this, delay); 1149 } 1150 1151 @Override 1152 public AnimatorSet clone() { 1153 final AnimatorSet anim = (AnimatorSet) super.clone(); 1154 /* 1155 * The basic clone() operation copies all items. This doesn't work very well for 1156 * AnimatorSet, because it will copy references that need to be recreated and state 1157 * that may not apply. What we need to do now is put the clone in an uninitialized 1158 * state, with fresh, empty data structures. Then we will build up the nodes list 1159 * manually, as we clone each Node (and its animation). The clone will then be sorted, 1160 * and will populate any appropriate lists, when it is started. 1161 */ 1162 final int nodeCount = mNodes.size(); 1163 anim.mStarted = false; 1164 anim.mLastFrameTime = -1; 1165 anim.mFirstFrame = -1; 1166 anim.mLastEventId = -1; 1167 anim.mPaused = false; 1168 anim.mPauseTime = -1; 1169 anim.mSeekState = new SeekState(); 1170 anim.mSelfPulse = true; 1171 anim.mPlayingSet = new ArrayList<Node>(); 1172 anim.mNodeMap = new ArrayMap<Animator, Node>(); 1173 anim.mNodes = new ArrayList<Node>(nodeCount); 1174 anim.mEvents = new ArrayList<AnimationEvent>(); 1175 anim.mReversing = false; 1176 anim.mDependencyDirty = true; 1177 1178 // Walk through the old nodes list, cloning each node and adding it to the new nodemap. 1179 // One problem is that the old node dependencies point to nodes in the old AnimatorSet. 1180 // We need to track the old/new nodes in order to reconstruct the dependencies in the clone. 1181 1182 for (int n = 0; n < nodeCount; n++) { 1183 final Node node = mNodes.get(n); 1184 Node nodeClone = node.clone(); 1185 node.mTmpClone = nodeClone; 1186 anim.mNodes.add(nodeClone); 1187 anim.mNodeMap.put(nodeClone.mAnimation, nodeClone); 1188 } 1189 1190 anim.mRootNode = mRootNode.mTmpClone; 1191 anim.mDelayAnim = (ValueAnimator) anim.mRootNode.mAnimation; 1192 1193 // Now that we've cloned all of the nodes, we're ready to walk through their 1194 // dependencies, mapping the old dependencies to the new nodes 1195 for (int i = 0; i < nodeCount; i++) { 1196 Node node = mNodes.get(i); 1197 // Update dependencies for node's clone 1198 node.mTmpClone.mLatestParent = node.mLatestParent == null ? 1199 null : node.mLatestParent.mTmpClone; 1200 int size = node.mChildNodes == null ? 0 : node.mChildNodes.size(); 1201 for (int j = 0; j < size; j++) { 1202 node.mTmpClone.mChildNodes.set(j, node.mChildNodes.get(j).mTmpClone); 1203 } 1204 size = node.mSiblings == null ? 0 : node.mSiblings.size(); 1205 for (int j = 0; j < size; j++) { 1206 node.mTmpClone.mSiblings.set(j, node.mSiblings.get(j).mTmpClone); 1207 } 1208 size = node.mParents == null ? 0 : node.mParents.size(); 1209 for (int j = 0; j < size; j++) { 1210 node.mTmpClone.mParents.set(j, node.mParents.get(j).mTmpClone); 1211 } 1212 } 1213 1214 for (int n = 0; n < nodeCount; n++) { 1215 mNodes.get(n).mTmpClone = null; 1216 } 1217 return anim; 1218 } 1219 1220 1221 /** 1222 * AnimatorSet is only reversible when the set contains no sequential animation, and no child 1223 * animators have a start delay. 1224 * @hide 1225 */ 1226 @Override 1227 public boolean canReverse() { 1228 return getTotalDuration() != DURATION_INFINITE; 1229 } 1230 1231 /** 1232 * Plays the AnimatorSet in reverse. If the animation has been seeked to a specific play time 1233 * using {@link #setCurrentPlayTime(long)}, it will play backwards from the point seeked when 1234 * reverse was called. Otherwise, then it will start from the end and play backwards. This 1235 * behavior is only set for the current animation; future playing of the animation will use the 1236 * default behavior of playing forward. 1237 * <p> 1238 * Note: reverse is not supported for infinite AnimatorSet. 1239 */ 1240 @Override 1241 public void reverse() { 1242 start(true, true); 1243 } 1244 1245 @Override 1246 public String toString() { 1247 String returnVal = "AnimatorSet@" + Integer.toHexString(hashCode()) + "{"; 1248 int size = mNodes.size(); 1249 for (int i = 0; i < size; i++) { 1250 Node node = mNodes.get(i); 1251 returnVal += "\n " + node.mAnimation.toString(); 1252 } 1253 return returnVal + "\n}"; 1254 } 1255 1256 private void printChildCount() { 1257 // Print out the child count through a level traverse. 1258 ArrayList<Node> list = new ArrayList<>(mNodes.size()); 1259 list.add(mRootNode); 1260 Log.d(TAG, "Current tree: "); 1261 int index = 0; 1262 while (index < list.size()) { 1263 int listSize = list.size(); 1264 StringBuilder builder = new StringBuilder(); 1265 for (; index < listSize; index++) { 1266 Node node = list.get(index); 1267 int num = 0; 1268 if (node.mChildNodes != null) { 1269 for (int i = 0; i < node.mChildNodes.size(); i++) { 1270 Node child = node.mChildNodes.get(i); 1271 if (child.mLatestParent == node) { 1272 num++; 1273 list.add(child); 1274 } 1275 } 1276 } 1277 builder.append(" "); 1278 builder.append(num); 1279 } 1280 Log.d(TAG, builder.toString()); 1281 } 1282 } 1283 1284 private void createDependencyGraph() { 1285 if (!mDependencyDirty) { 1286 // Check whether any duration of the child animations has changed 1287 boolean durationChanged = false; 1288 for (int i = 0; i < mNodes.size(); i++) { 1289 Animator anim = mNodes.get(i).mAnimation; 1290 if (mNodes.get(i).mTotalDuration != anim.getTotalDuration()) { 1291 durationChanged = true; 1292 break; 1293 } 1294 } 1295 if (!durationChanged) { 1296 return; 1297 } 1298 } 1299 1300 mDependencyDirty = false; 1301 // Traverse all the siblings and make sure they have all the parents 1302 int size = mNodes.size(); 1303 for (int i = 0; i < size; i++) { 1304 mNodes.get(i).mParentsAdded = false; 1305 } 1306 for (int i = 0; i < size; i++) { 1307 Node node = mNodes.get(i); 1308 if (node.mParentsAdded) { 1309 continue; 1310 } 1311 1312 node.mParentsAdded = true; 1313 if (node.mSiblings == null) { 1314 continue; 1315 } 1316 1317 // Find all the siblings 1318 findSiblings(node, node.mSiblings); 1319 node.mSiblings.remove(node); 1320 1321 // Get parents from all siblings 1322 int siblingSize = node.mSiblings.size(); 1323 for (int j = 0; j < siblingSize; j++) { 1324 node.addParents(node.mSiblings.get(j).mParents); 1325 } 1326 1327 // Now make sure all siblings share the same set of parents 1328 for (int j = 0; j < siblingSize; j++) { 1329 Node sibling = node.mSiblings.get(j); 1330 sibling.addParents(node.mParents); 1331 sibling.mParentsAdded = true; 1332 } 1333 } 1334 1335 for (int i = 0; i < size; i++) { 1336 Node node = mNodes.get(i); 1337 if (node != mRootNode && node.mParents == null) { 1338 node.addParent(mRootNode); 1339 } 1340 } 1341 1342 // Do a DFS on the tree 1343 ArrayList<Node> visited = new ArrayList<Node>(mNodes.size()); 1344 // Assign start/end time 1345 mRootNode.mStartTime = 0; 1346 mRootNode.mEndTime = mDelayAnim.getDuration(); 1347 updatePlayTime(mRootNode, visited); 1348 1349 sortAnimationEvents(); 1350 mTotalDuration = mEvents.get(mEvents.size() - 1).getTime(); 1351 } 1352 1353 private void sortAnimationEvents() { 1354 // Sort the list of events in ascending order of their time 1355 // Create the list including the delay animation. 1356 mEvents.clear(); 1357 for (int i = 1; i < mNodes.size(); i++) { 1358 Node node = mNodes.get(i); 1359 mEvents.add(new AnimationEvent(node, AnimationEvent.ANIMATION_START)); 1360 mEvents.add(new AnimationEvent(node, AnimationEvent.ANIMATION_DELAY_ENDED)); 1361 mEvents.add(new AnimationEvent(node, AnimationEvent.ANIMATION_END)); 1362 } 1363 mEvents.sort(new Comparator<AnimationEvent>() { 1364 @Override 1365 public int compare(AnimationEvent e1, AnimationEvent e2) { 1366 long t1 = e1.getTime(); 1367 long t2 = e2.getTime(); 1368 if (t1 == t2) { 1369 // For events that happen at the same time, we need them to be in the sequence 1370 // (end, start, start delay ended) 1371 if (e2.mEvent + e1.mEvent == AnimationEvent.ANIMATION_START 1372 + AnimationEvent.ANIMATION_DELAY_ENDED) { 1373 // Ensure start delay happens after start 1374 return e1.mEvent - e2.mEvent; 1375 } else { 1376 return e2.mEvent - e1.mEvent; 1377 } 1378 } 1379 if (t2 == DURATION_INFINITE) { 1380 return -1; 1381 } 1382 if (t1 == DURATION_INFINITE) { 1383 return 1; 1384 } 1385 // When neither event happens at INFINITE time: 1386 return (int) (t1 - t2); 1387 } 1388 }); 1389 1390 int eventSize = mEvents.size(); 1391 // For the same animation, start event has to happen before end. 1392 for (int i = 0; i < eventSize;) { 1393 AnimationEvent event = mEvents.get(i); 1394 if (event.mEvent == AnimationEvent.ANIMATION_END) { 1395 boolean needToSwapStart; 1396 if (event.mNode.mStartTime == event.mNode.mEndTime) { 1397 needToSwapStart = true; 1398 } else if (event.mNode.mEndTime == event.mNode.mStartTime 1399 + event.mNode.mAnimation.getStartDelay()) { 1400 // Swapping start delay 1401 needToSwapStart = false; 1402 } else { 1403 i++; 1404 continue; 1405 } 1406 1407 int startEventId = eventSize; 1408 int startDelayEndId = eventSize; 1409 for (int j = i + 1; j < eventSize; j++) { 1410 if (startEventId < eventSize && startDelayEndId < eventSize) { 1411 break; 1412 } 1413 if (mEvents.get(j).mNode == event.mNode) { 1414 if (mEvents.get(j).mEvent == AnimationEvent.ANIMATION_START) { 1415 // Found start event 1416 startEventId = j; 1417 } else if (mEvents.get(j).mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) { 1418 startDelayEndId = j; 1419 } 1420 } 1421 1422 } 1423 if (needToSwapStart && startEventId == mEvents.size()) { 1424 throw new UnsupportedOperationException("Something went wrong, no start is" 1425 + "found after stop for an animation that has the same start and end" 1426 + "time."); 1427 1428 } 1429 if (startDelayEndId == mEvents.size()) { 1430 throw new UnsupportedOperationException("Something went wrong, no start" 1431 + "delay end is found after stop for an animation"); 1432 1433 } 1434 1435 // We need to make sure start is inserted before start delay ended event, 1436 // because otherwise inserting start delay ended events first would change 1437 // the start event index. 1438 if (needToSwapStart) { 1439 AnimationEvent startEvent = mEvents.remove(startEventId); 1440 mEvents.add(i, startEvent); 1441 i++; 1442 } 1443 1444 AnimationEvent startDelayEndEvent = mEvents.remove(startDelayEndId); 1445 mEvents.add(i, startDelayEndEvent); 1446 i += 2; 1447 } else { 1448 i++; 1449 } 1450 } 1451 1452 if (!mEvents.isEmpty() && mEvents.get(0).mEvent != AnimationEvent.ANIMATION_START) { 1453 throw new UnsupportedOperationException( 1454 "Sorting went bad, the start event should always be at index 0"); 1455 } 1456 1457 // Add AnimatorSet's start delay node to the beginning 1458 mEvents.add(0, new AnimationEvent(mRootNode, AnimationEvent.ANIMATION_START)); 1459 mEvents.add(1, new AnimationEvent(mRootNode, AnimationEvent.ANIMATION_DELAY_ENDED)); 1460 mEvents.add(2, new AnimationEvent(mRootNode, AnimationEvent.ANIMATION_END)); 1461 1462 if (mEvents.get(mEvents.size() - 1).mEvent == AnimationEvent.ANIMATION_START 1463 || mEvents.get(mEvents.size() - 1).mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) { 1464 throw new UnsupportedOperationException( 1465 "Something went wrong, the last event is not an end event"); 1466 } 1467 } 1468 1469 /** 1470 * Based on parent's start/end time, calculate children's start/end time. If cycle exists in 1471 * the graph, all the nodes on the cycle will be marked to start at {@link #DURATION_INFINITE}, 1472 * meaning they will ever play. 1473 */ 1474 private void updatePlayTime(Node parent, ArrayList<Node> visited) { 1475 if (parent.mChildNodes == null) { 1476 if (parent == mRootNode) { 1477 // All the animators are in a cycle 1478 for (int i = 0; i < mNodes.size(); i++) { 1479 Node node = mNodes.get(i); 1480 if (node != mRootNode) { 1481 node.mStartTime = DURATION_INFINITE; 1482 node.mEndTime = DURATION_INFINITE; 1483 } 1484 } 1485 } 1486 return; 1487 } 1488 1489 visited.add(parent); 1490 int childrenSize = parent.mChildNodes.size(); 1491 for (int i = 0; i < childrenSize; i++) { 1492 Node child = parent.mChildNodes.get(i); 1493 int index = visited.indexOf(child); 1494 if (index >= 0) { 1495 // Child has been visited, cycle found. Mark all the nodes in the cycle. 1496 for (int j = index; j < visited.size(); j++) { 1497 visited.get(j).mLatestParent = null; 1498 visited.get(j).mStartTime = DURATION_INFINITE; 1499 visited.get(j).mEndTime = DURATION_INFINITE; 1500 } 1501 child.mStartTime = DURATION_INFINITE; 1502 child.mEndTime = DURATION_INFINITE; 1503 child.mLatestParent = null; 1504 Log.w(TAG, "Cycle found in AnimatorSet: " + this); 1505 continue; 1506 } 1507 1508 if (child.mStartTime != DURATION_INFINITE) { 1509 if (parent.mEndTime == DURATION_INFINITE) { 1510 child.mLatestParent = parent; 1511 child.mStartTime = DURATION_INFINITE; 1512 child.mEndTime = DURATION_INFINITE; 1513 } else { 1514 if (parent.mEndTime >= child.mStartTime) { 1515 child.mLatestParent = parent; 1516 child.mStartTime = parent.mEndTime; 1517 } 1518 1519 long duration = child.mAnimation.getTotalDuration(); 1520 child.mEndTime = duration == DURATION_INFINITE ? 1521 DURATION_INFINITE : child.mStartTime + duration; 1522 } 1523 } 1524 updatePlayTime(child, visited); 1525 } 1526 visited.remove(parent); 1527 } 1528 1529 // Recursively find all the siblings 1530 private void findSiblings(Node node, ArrayList<Node> siblings) { 1531 if (!siblings.contains(node)) { 1532 siblings.add(node); 1533 if (node.mSiblings == null) { 1534 return; 1535 } 1536 for (int i = 0; i < node.mSiblings.size(); i++) { 1537 findSiblings(node.mSiblings.get(i), siblings); 1538 } 1539 } 1540 } 1541 1542 /** 1543 * @hide 1544 * TODO: For animatorSet defined in XML, we can use a flag to indicate what the play order 1545 * if defined (i.e. sequential or together), then we can use the flag instead of calculating 1546 * dynamically. Note that when AnimatorSet is empty this method returns true. 1547 * @return whether all the animators in the set are supposed to play together 1548 */ 1549 public boolean shouldPlayTogether() { 1550 updateAnimatorsDuration(); 1551 createDependencyGraph(); 1552 // All the child nodes are set out to play right after the delay animation 1553 return mRootNode.mChildNodes == null || mRootNode.mChildNodes.size() == mNodes.size() - 1; 1554 } 1555 1556 @Override 1557 public long getTotalDuration() { 1558 updateAnimatorsDuration(); 1559 createDependencyGraph(); 1560 return mTotalDuration; 1561 } 1562 1563 private Node getNodeForAnimation(Animator anim) { 1564 Node node = mNodeMap.get(anim); 1565 if (node == null) { 1566 node = new Node(anim); 1567 mNodeMap.put(anim, node); 1568 mNodes.add(node); 1569 } 1570 return node; 1571 } 1572 1573 /** 1574 * A Node is an embodiment of both the Animator that it wraps as well as 1575 * any dependencies that are associated with that Animation. This includes 1576 * both dependencies upon other nodes (in the dependencies list) as 1577 * well as dependencies of other nodes upon this (in the nodeDependents list). 1578 */ 1579 private static class Node implements Cloneable { 1580 Animator mAnimation; 1581 1582 /** 1583 * Child nodes are the nodes associated with animations that will be played immediately 1584 * after current node. 1585 */ 1586 ArrayList<Node> mChildNodes = null; 1587 1588 /** 1589 * Temporary field to hold the clone in AnimatorSet#clone. Cleaned after clone is complete 1590 */ 1591 private Node mTmpClone = null; 1592 1593 /** 1594 * Flag indicating whether the animation in this node is finished. This flag 1595 * is used by AnimatorSet to check, as each animation ends, whether all child animations 1596 * are mEnded and it's time to send out an end event for the entire AnimatorSet. 1597 */ 1598 boolean mEnded = false; 1599 1600 /** 1601 * Nodes with animations that are defined to play simultaneously with the animation 1602 * associated with this current node. 1603 */ 1604 ArrayList<Node> mSiblings; 1605 1606 /** 1607 * Parent nodes are the nodes with animations preceding current node's animation. Parent 1608 * nodes here are derived from user defined animation sequence. 1609 */ 1610 ArrayList<Node> mParents; 1611 1612 /** 1613 * Latest parent is the parent node associated with a animation that finishes after all 1614 * the other parents' animations. 1615 */ 1616 Node mLatestParent = null; 1617 1618 boolean mParentsAdded = false; 1619 long mStartTime = 0; 1620 long mEndTime = 0; 1621 long mTotalDuration = 0; 1622 1623 /** 1624 * Constructs the Node with the animation that it encapsulates. A Node has no 1625 * dependencies by default; dependencies are added via the addDependency() 1626 * method. 1627 * 1628 * @param animation The animation that the Node encapsulates. 1629 */ 1630 public Node(Animator animation) { 1631 this.mAnimation = animation; 1632 } 1633 1634 @Override 1635 public Node clone() { 1636 try { 1637 Node node = (Node) super.clone(); 1638 node.mAnimation = mAnimation.clone(); 1639 if (mChildNodes != null) { 1640 node.mChildNodes = new ArrayList<>(mChildNodes); 1641 } 1642 if (mSiblings != null) { 1643 node.mSiblings = new ArrayList<>(mSiblings); 1644 } 1645 if (mParents != null) { 1646 node.mParents = new ArrayList<>(mParents); 1647 } 1648 node.mEnded = false; 1649 return node; 1650 } catch (CloneNotSupportedException e) { 1651 throw new AssertionError(); 1652 } 1653 } 1654 1655 void addChild(Node node) { 1656 if (mChildNodes == null) { 1657 mChildNodes = new ArrayList<>(); 1658 } 1659 if (!mChildNodes.contains(node)) { 1660 mChildNodes.add(node); 1661 node.addParent(this); 1662 } 1663 } 1664 1665 public void addSibling(Node node) { 1666 if (mSiblings == null) { 1667 mSiblings = new ArrayList<Node>(); 1668 } 1669 if (!mSiblings.contains(node)) { 1670 mSiblings.add(node); 1671 node.addSibling(this); 1672 } 1673 } 1674 1675 public void addParent(Node node) { 1676 if (mParents == null) { 1677 mParents = new ArrayList<Node>(); 1678 } 1679 if (!mParents.contains(node)) { 1680 mParents.add(node); 1681 node.addChild(this); 1682 } 1683 } 1684 1685 public void addParents(ArrayList<Node> parents) { 1686 if (parents == null) { 1687 return; 1688 } 1689 int size = parents.size(); 1690 for (int i = 0; i < size; i++) { 1691 addParent(parents.get(i)); 1692 } 1693 } 1694 } 1695 1696 /** 1697 * This class is a wrapper around a node and an event for the animation corresponding to the 1698 * node. The 3 types of events represent the start of an animation, the end of a start delay of 1699 * an animation, and the end of an animation. When playing forward (i.e. in the non-reverse 1700 * direction), start event marks when start() should be called, and end event corresponds to 1701 * when the animation should finish. When playing in reverse, start delay will not be a part 1702 * of the animation. Therefore, reverse() is called at the end event, and animation should end 1703 * at the delay ended event. 1704 */ 1705 private static class AnimationEvent { 1706 static final int ANIMATION_START = 0; 1707 static final int ANIMATION_DELAY_ENDED = 1; 1708 static final int ANIMATION_END = 2; 1709 final Node mNode; 1710 final int mEvent; 1711 1712 AnimationEvent(Node node, int event) { 1713 mNode = node; 1714 mEvent = event; 1715 } 1716 1717 long getTime() { 1718 if (mEvent == ANIMATION_START) { 1719 return mNode.mStartTime; 1720 } else if (mEvent == ANIMATION_DELAY_ENDED) { 1721 return mNode.mStartTime + mNode.mAnimation.getStartDelay(); 1722 } else { 1723 return mNode.mEndTime; 1724 } 1725 } 1726 1727 public String toString() { 1728 String eventStr = mEvent == ANIMATION_START ? "start" : ( 1729 mEvent == ANIMATION_DELAY_ENDED ? "delay ended" : "end"); 1730 return eventStr + " " + mNode.mAnimation.toString(); 1731 } 1732 } 1733 1734 private class SeekState { 1735 private long mPlayTime = -1; 1736 private boolean mSeekingInReverse = false; 1737 void reset() { 1738 mPlayTime = -1; 1739 mSeekingInReverse = false; 1740 } 1741 1742 void setPlayTime(long playTime, boolean inReverse) { 1743 // TODO: This can be simplified. 1744 1745 // Clamp the play time 1746 if (getTotalDuration() != DURATION_INFINITE) { 1747 mPlayTime = Math.min(playTime, getTotalDuration() - mStartDelay); 1748 } 1749 mPlayTime = Math.max(0, mPlayTime); 1750 mSeekingInReverse = inReverse; 1751 } 1752 1753 void updateSeekDirection(boolean inReverse) { 1754 // Change seek direction without changing the overall fraction 1755 if (inReverse && getTotalDuration() == DURATION_INFINITE) { 1756 throw new UnsupportedOperationException("Error: Cannot reverse infinite animator" 1757 + " set"); 1758 } 1759 if (mPlayTime >= 0) { 1760 if (inReverse != mSeekingInReverse) { 1761 mPlayTime = getTotalDuration() - mStartDelay - mPlayTime; 1762 } 1763 } 1764 } 1765 1766 long getPlayTime() { 1767 return mPlayTime; 1768 } 1769 1770 /** 1771 * Returns the playtime assuming the animation is forward playing 1772 */ 1773 long getPlayTimeNormalized() { 1774 if (mReversing) { 1775 return getTotalDuration() - mStartDelay - mPlayTime; 1776 } 1777 return mPlayTime; 1778 } 1779 1780 boolean isActive() { 1781 return mPlayTime != -1; 1782 } 1783 } 1784 1785 /** 1786 * The <code>Builder</code> object is a utility class to facilitate adding animations to a 1787 * <code>AnimatorSet</code> along with the relationships between the various animations. The 1788 * intention of the <code>Builder</code> methods, along with the {@link 1789 * AnimatorSet#play(Animator) play()} method of <code>AnimatorSet</code> is to make it possible 1790 * to express the dependency relationships of animations in a natural way. Developers can also 1791 * use the {@link AnimatorSet#playTogether(Animator[]) playTogether()} and {@link 1792 * AnimatorSet#playSequentially(Animator[]) playSequentially()} methods if these suit the need, 1793 * but it might be easier in some situations to express the AnimatorSet of animations in pairs. 1794 * <p/> 1795 * <p>The <code>Builder</code> object cannot be constructed directly, but is rather constructed 1796 * internally via a call to {@link AnimatorSet#play(Animator)}.</p> 1797 * <p/> 1798 * <p>For example, this sets up a AnimatorSet to play anim1 and anim2 at the same time, anim3 to 1799 * play when anim2 finishes, and anim4 to play when anim3 finishes:</p> 1800 * <pre> 1801 * AnimatorSet s = new AnimatorSet(); 1802 * s.play(anim1).with(anim2); 1803 * s.play(anim2).before(anim3); 1804 * s.play(anim4).after(anim3); 1805 * </pre> 1806 * <p/> 1807 * <p>Note in the example that both {@link Builder#before(Animator)} and {@link 1808 * Builder#after(Animator)} are used. These are just different ways of expressing the same 1809 * relationship and are provided to make it easier to say things in a way that is more natural, 1810 * depending on the situation.</p> 1811 * <p/> 1812 * <p>It is possible to make several calls into the same <code>Builder</code> object to express 1813 * multiple relationships. However, note that it is only the animation passed into the initial 1814 * {@link AnimatorSet#play(Animator)} method that is the dependency in any of the successive 1815 * calls to the <code>Builder</code> object. For example, the following code starts both anim2 1816 * and anim3 when anim1 ends; there is no direct dependency relationship between anim2 and 1817 * anim3: 1818 * <pre> 1819 * AnimatorSet s = new AnimatorSet(); 1820 * s.play(anim1).before(anim2).before(anim3); 1821 * </pre> 1822 * If the desired result is to play anim1 then anim2 then anim3, this code expresses the 1823 * relationship correctly:</p> 1824 * <pre> 1825 * AnimatorSet s = new AnimatorSet(); 1826 * s.play(anim1).before(anim2); 1827 * s.play(anim2).before(anim3); 1828 * </pre> 1829 * <p/> 1830 * <p>Note that it is possible to express relationships that cannot be resolved and will not 1831 * result in sensible results. For example, <code>play(anim1).after(anim1)</code> makes no 1832 * sense. In general, circular dependencies like this one (or more indirect ones where a depends 1833 * on b, which depends on c, which depends on a) should be avoided. Only create AnimatorSets 1834 * that can boil down to a simple, one-way relationship of animations starting with, before, and 1835 * after other, different, animations.</p> 1836 */ 1837 public class Builder { 1838 1839 /** 1840 * This tracks the current node being processed. It is supplied to the play() method 1841 * of AnimatorSet and passed into the constructor of Builder. 1842 */ 1843 private Node mCurrentNode; 1844 1845 /** 1846 * package-private constructor. Builders are only constructed by AnimatorSet, when the 1847 * play() method is called. 1848 * 1849 * @param anim The animation that is the dependency for the other animations passed into 1850 * the other methods of this Builder object. 1851 */ 1852 Builder(Animator anim) { 1853 mDependencyDirty = true; 1854 mCurrentNode = getNodeForAnimation(anim); 1855 } 1856 1857 /** 1858 * Sets up the given animation to play at the same time as the animation supplied in the 1859 * {@link AnimatorSet#play(Animator)} call that created this <code>Builder</code> object. 1860 * 1861 * @param anim The animation that will play when the animation supplied to the 1862 * {@link AnimatorSet#play(Animator)} method starts. 1863 */ 1864 public Builder with(Animator anim) { 1865 Node node = getNodeForAnimation(anim); 1866 mCurrentNode.addSibling(node); 1867 return this; 1868 } 1869 1870 /** 1871 * Sets up the given animation to play when the animation supplied in the 1872 * {@link AnimatorSet#play(Animator)} call that created this <code>Builder</code> object 1873 * ends. 1874 * 1875 * @param anim The animation that will play when the animation supplied to the 1876 * {@link AnimatorSet#play(Animator)} method ends. 1877 */ 1878 public Builder before(Animator anim) { 1879 Node node = getNodeForAnimation(anim); 1880 mCurrentNode.addChild(node); 1881 return this; 1882 } 1883 1884 /** 1885 * Sets up the given animation to play when the animation supplied in the 1886 * {@link AnimatorSet#play(Animator)} call that created this <code>Builder</code> object 1887 * to start when the animation supplied in this method call ends. 1888 * 1889 * @param anim The animation whose end will cause the animation supplied to the 1890 * {@link AnimatorSet#play(Animator)} method to play. 1891 */ 1892 public Builder after(Animator anim) { 1893 Node node = getNodeForAnimation(anim); 1894 mCurrentNode.addParent(node); 1895 return this; 1896 } 1897 1898 /** 1899 * Sets up the animation supplied in the 1900 * {@link AnimatorSet#play(Animator)} call that created this <code>Builder</code> object 1901 * to play when the given amount of time elapses. 1902 * 1903 * @param delay The number of milliseconds that should elapse before the 1904 * animation starts. 1905 */ 1906 public Builder after(long delay) { 1907 // setup dummy ValueAnimator just to run the clock 1908 ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f); 1909 anim.setDuration(delay); 1910 after(anim); 1911 return this; 1912 } 1913 1914 } 1915 1916} 1917