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