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