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