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