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