blob: 35cf39f1b056dea99d79728797101393228a55c0 [file] [log] [blame]
Chet Haase17fb4b02010-06-28 17:55:07 -07001/*
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
Doris Liu58606db2016-04-13 13:28:38 -070019import android.app.ActivityThread;
20import android.app.Application;
21import android.os.Build;
Doris Liu13351992017-01-17 17:10:42 -080022import android.os.Looper;
23import android.util.AndroidRuntimeException;
Doris Liud7444422015-05-11 13:23:31 -070024import android.util.ArrayMap;
Doris Liu13099142015-07-10 17:32:41 -070025import android.util.Log;
Doris Liu13351992017-01-17 17:10:42 -080026import android.view.animation.Animation;
Doris Liud7444422015-05-11 13:23:31 -070027
Chet Haase17fb4b02010-06-28 17:55:07 -070028import java.util.ArrayList;
Chet Haase37a7bec2010-11-30 15:55:39 -080029import java.util.Collection;
Doris Liu13351992017-01-17 17:10:42 -080030import java.util.Comparator;
Doris Liu56b2df02017-05-11 16:50:53 -070031import java.util.HashMap;
Chet Haase37a7bec2010-11-30 15:55:39 -080032import java.util.List;
Chet Haase17fb4b02010-06-28 17:55:07 -070033
34/**
Chet Haasea18a86b2010-09-07 13:20:00 -070035 * This class plays a set of {@link Animator} objects in the specified order. Animations
Chet Haase17fb4b02010-06-28 17:55:07 -070036 * can be set up to play together, in sequence, or after a specified delay.
37 *
Chet Haasea18a86b2010-09-07 13:20:00 -070038 * <p>There are two different approaches to adding animations to a <code>AnimatorSet</code>:
39 * either the {@link AnimatorSet#playTogether(Animator[]) playTogether()} or
40 * {@link AnimatorSet#playSequentially(Animator[]) playSequentially()} methods can be called to add
41 * a set of animations all at once, or the {@link AnimatorSet#play(Animator)} can be
42 * used in conjunction with methods in the {@link AnimatorSet.Builder Builder}
Chet Haase17fb4b02010-06-28 17:55:07 -070043 * class to add animations
44 * one by one.</p>
45 *
Chet Haasea18a86b2010-09-07 13:20:00 -070046 * <p>It is possible to set up a <code>AnimatorSet</code> with circular dependencies between
Chet Haase17fb4b02010-06-28 17:55:07 -070047 * its animations. For example, an animation a1 could be set up to start before animation a2, a2
48 * before a3, and a3 before a1. The results of this configuration are undefined, but will typically
49 * result in none of the affected animations being played. Because of this (and because
50 * circular dependencies do not make logical sense anyway), circular dependencies
51 * should be avoided, and the dependency flow of animations should only be in one direction.
Joe Fernandez3aef8e1d2011-12-20 10:38:34 -080052 *
53 * <div class="special reference">
54 * <h3>Developer Guides</h3>
55 * <p>For more information about animating with {@code AnimatorSet}, read the
56 * <a href="{@docRoot}guide/topics/graphics/prop-animation.html#choreography">Property
57 * Animation</a> developer guide.</p>
58 * </div>
Chet Haase17fb4b02010-06-28 17:55:07 -070059 */
Doris Liu13351992017-01-17 17:10:42 -080060public final class AnimatorSet extends Animator implements AnimationHandler.AnimationFrameCallback {
Chet Haase17fb4b02010-06-28 17:55:07 -070061
Doris Liu13099142015-07-10 17:32:41 -070062 private static final String TAG = "AnimatorSet";
Chet Haase17fb4b02010-06-28 17:55:07 -070063 /**
Chet Haase49afa5b2010-08-23 11:39:53 -070064 * Internal variables
65 * NOTE: This object implements the clone() method, making a deep copy of any referenced
66 * objects. As other non-trivial fields are added to this class, make sure to add logic
67 * to clone() to make deep copies of them.
68 */
69
70 /**
Chet Haase3b69b6f2010-07-29 09:09:05 -070071 * Tracks animations currently being played, so that we know what to
Chet Haasea18a86b2010-09-07 13:20:00 -070072 * cancel or end when cancel() or end() is called on this AnimatorSet
Chet Haase17fb4b02010-06-28 17:55:07 -070073 */
Doris Liu13351992017-01-17 17:10:42 -080074 private ArrayList<Node> mPlayingSet = new ArrayList<Node>();
Chet Haase17fb4b02010-06-28 17:55:07 -070075
76 /**
Chet Haasea18a86b2010-09-07 13:20:00 -070077 * Contains all nodes, mapped to their respective Animators. When new
78 * dependency information is added for an Animator, we want to add it
79 * to a single node representing that Animator, not create a new Node
Chet Haase17fb4b02010-06-28 17:55:07 -070080 * if one already exists.
81 */
Doris Liud7444422015-05-11 13:23:31 -070082 private ArrayMap<Animator, Node> mNodeMap = new ArrayMap<Animator, Node>();
Chet Haase17fb4b02010-06-28 17:55:07 -070083
84 /**
Doris Liu13351992017-01-17 17:10:42 -080085 * Contains the start and end events of all the nodes. All these events are sorted in this list.
86 */
87 private ArrayList<AnimationEvent> mEvents = new ArrayList<>();
88
89 /**
Chet Haasea18a86b2010-09-07 13:20:00 -070090 * Set of all nodes created for this AnimatorSet. This list is used upon
91 * starting the set, and the nodes are placed in sorted order into the
Chet Haase17fb4b02010-06-28 17:55:07 -070092 * sortedNodes collection.
93 */
Chet Haase49afa5b2010-08-23 11:39:53 -070094 private ArrayList<Node> mNodes = new ArrayList<Node>();
Chet Haase17fb4b02010-06-28 17:55:07 -070095
96 /**
Doris Liu13099142015-07-10 17:32:41 -070097 * Tracks whether any change has been made to the AnimatorSet, which is then used to
98 * determine whether the dependency graph should be re-constructed.
99 */
100 private boolean mDependencyDirty = false;
Chet Haase010dbaa2010-07-19 17:29:49 -0700101
Chet Haase8b699792011-08-05 15:20:19 -0700102 /**
103 * Indicates whether an AnimatorSet has been start()'d, whether or
104 * not there is a nonzero startDelay.
105 */
106 private boolean mStarted = false;
107
Chet Haase21cd1382010-09-01 17:42:29 -0700108 // The amount of time in ms to delay starting the animation after start() is called
109 private long mStartDelay = 0;
110
Chet Haasee2ab7cc2010-12-06 16:10:07 -0800111 // Animator used for a nonzero startDelay
Doris Liu13099142015-07-10 17:32:41 -0700112 private ValueAnimator mDelayAnim = ValueAnimator.ofFloat(0f, 1f).setDuration(0);
Chet Haasee2ab7cc2010-12-06 16:10:07 -0800113
Doris Liu13099142015-07-10 17:32:41 -0700114 // Root of the dependency tree of all the animators in the set. In this tree, parent-child
115 // relationship captures the order of animation (i.e. parent and child will play sequentially),
116 // and sibling relationship indicates "with" relationship, as sibling animators start at the
117 // same time.
118 private Node mRootNode = new Node(mDelayAnim);
Chet Haase21cd1382010-09-01 17:42:29 -0700119
120 // How long the child animations should last in ms. The default value is negative, which
Chet Haasea18a86b2010-09-07 13:20:00 -0700121 // simply means that there is no duration set on the AnimatorSet. When a real duration is
Chet Haase21cd1382010-09-01 17:42:29 -0700122 // set, it is passed along to the child animations.
123 private long mDuration = -1;
124
Chet Haase430742f2013-04-12 11:18:36 -0700125 // Records the interpolator for the set. Null value indicates that no interpolator
126 // was set on this AnimatorSet, so it should not be passed down to the children.
127 private TimeInterpolator mInterpolator = null;
128
Doris Liu13099142015-07-10 17:32:41 -0700129 // The total duration of finishing all the Animators in the set.
130 private long mTotalDuration = 0;
131
Doris Liu58606db2016-04-13 13:28:38 -0700132 // In pre-N releases, calling end() before start() on an animator set is no-op. But that is not
133 // consistent with the behavior for other animator types. In order to keep the behavior
134 // consistent within Animation framework, when end() is called without start(), we will start
135 // the animator set and immediately end it for N and forward.
136 private final boolean mShouldIgnoreEndWithoutStart;
137
Doris Liu13351992017-01-17 17:10:42 -0800138 // In pre-O releases, calling start() doesn't reset all the animators values to start values.
139 // As a result, the start of the animation is inconsistent with what setCurrentPlayTime(0) would
140 // look like on O. Also it is inconsistent with what reverse() does on O, as reverse would
141 // advance all the animations to the right beginning values for before starting to reverse.
142 // From O and forward, we will add an additional step of resetting the animation values (unless
143 // the animation was previously seeked and therefore doesn't start from the beginning).
144 private final boolean mShouldResetValuesAtStart;
145
Doris Liu6d452092017-02-08 14:47:08 -0800146 // In pre-O releases, end() may never explicitly called on a child animator. As a result, end()
147 // may not even be properly implemented in a lot of cases. After a few apps crashing on this,
148 // it became necessary to use an sdk target guard for calling end().
149 private final boolean mEndCanBeCalled;
150
Doris Liu13351992017-01-17 17:10:42 -0800151 // The time, in milliseconds, when last frame of the animation came in. -1 when the animation is
152 // not running.
153 private long mLastFrameTime = -1;
154
Doris Liu6d452092017-02-08 14:47:08 -0800155 // The time, in milliseconds, when the first frame of the animation came in. This is the
156 // frame before we start counting down the start delay, if any.
Doris Liu13351992017-01-17 17:10:42 -0800157 // -1 when the animation is not running.
158 private long mFirstFrame = -1;
159
160 // The time, in milliseconds, when the first frame of the animation came in.
161 // -1 when the animation is not running.
162 private int mLastEventId = -1;
163
164 // Indicates whether the animation is reversing.
165 private boolean mReversing = false;
166
167 // Indicates whether the animation should register frame callbacks. If false, the animation will
168 // passively wait for an AnimatorSet to pulse it.
169 private boolean mSelfPulse = true;
170
171 // SeekState stores the last seeked play time as well as seek direction.
172 private SeekState mSeekState = new SeekState();
173
174 // Indicates where children animators are all initialized with their start values captured.
175 private boolean mChildrenInitialized = false;
176
177 /**
178 * Set on the next frame after pause() is called, used to calculate a new startTime
179 * or delayStartTime which allows the animator set to continue from the point at which
180 * it was paused. If negative, has not yet been set.
181 */
182 private long mPauseTime = -1;
183
Doris Liu6ba5ed32017-01-26 18:50:04 -0800184 // This is to work around a bug in b/34736819. This needs to be removed once app team
Doris Liu66c564e2017-01-26 11:53:46 -0800185 // fixes their side.
Doris Liu6ba5ed32017-01-26 18:50:04 -0800186 private AnimatorListenerAdapter mDummyListener = new AnimatorListenerAdapter() {
187 @Override
188 public void onAnimationEnd(Animator animation) {
189 if (mNodeMap.get(animation) == null) {
190 throw new AndroidRuntimeException("Error: animation ended is not in the node map");
191 }
192 mNodeMap.get(animation).mEnded = true;
193
194 }
195 };
Doris Liu66c564e2017-01-26 11:53:46 -0800196
Doris Liu13099142015-07-10 17:32:41 -0700197 public AnimatorSet() {
198 super();
199 mNodeMap.put(mDelayAnim, mRootNode);
200 mNodes.add(mRootNode);
Doris Liu6d452092017-02-08 14:47:08 -0800201 boolean isPreO;
Doris Liu58606db2016-04-13 13:28:38 -0700202 // Set the flag to ignore calling end() without start() for pre-N releases
203 Application app = ActivityThread.currentApplication();
204 if (app == null || app.getApplicationInfo() == null) {
205 mShouldIgnoreEndWithoutStart = true;
Doris Liu6d452092017-02-08 14:47:08 -0800206 isPreO = true;
Doris Liu58606db2016-04-13 13:28:38 -0700207 } else {
Doris Liu13351992017-01-17 17:10:42 -0800208 if (app.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) {
209 mShouldIgnoreEndWithoutStart = true;
210 } else {
211 mShouldIgnoreEndWithoutStart = false;
212 }
213
Doris Liu6d452092017-02-08 14:47:08 -0800214 isPreO = app.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.O;
Doris Liu58606db2016-04-13 13:28:38 -0700215 }
Doris Liu6d452092017-02-08 14:47:08 -0800216 mShouldResetValuesAtStart = !isPreO;
217 mEndCanBeCalled = !isPreO;
Doris Liu13099142015-07-10 17:32:41 -0700218 }
219
Chet Haase010dbaa2010-07-19 17:29:49 -0700220 /**
Chet Haasea18a86b2010-09-07 13:20:00 -0700221 * Sets up this AnimatorSet to play all of the supplied animations at the same time.
Chet Haase430742f2013-04-12 11:18:36 -0700222 * This is equivalent to calling {@link #play(Animator)} with the first animator in the
223 * set and then {@link Builder#with(Animator)} with each of the other animators. Note that
224 * an Animator with a {@link Animator#setStartDelay(long) startDelay} will not actually
225 * start until that delay elapses, which means that if the first animator in the list
226 * supplied to this constructor has a startDelay, none of the other animators will start
227 * until that first animator's startDelay has elapsed.
Chet Haase17fb4b02010-06-28 17:55:07 -0700228 *
Chet Haasea18a86b2010-09-07 13:20:00 -0700229 * @param items The animations that will be started simultaneously.
Chet Haase17fb4b02010-06-28 17:55:07 -0700230 */
Chet Haasea18a86b2010-09-07 13:20:00 -0700231 public void playTogether(Animator... items) {
232 if (items != null) {
Chet Haasea18a86b2010-09-07 13:20:00 -0700233 Builder builder = play(items[0]);
234 for (int i = 1; i < items.length; ++i) {
235 builder.with(items[i]);
Chet Haase17fb4b02010-06-28 17:55:07 -0700236 }
237 }
238 }
239
240 /**
Chet Haase37a7bec2010-11-30 15:55:39 -0800241 * Sets up this AnimatorSet to play all of the supplied animations at the same time.
242 *
243 * @param items The animations that will be started simultaneously.
244 */
245 public void playTogether(Collection<Animator> items) {
246 if (items != null && items.size() > 0) {
Chet Haase37a7bec2010-11-30 15:55:39 -0800247 Builder builder = null;
248 for (Animator anim : items) {
249 if (builder == null) {
250 builder = play(anim);
251 } else {
252 builder.with(anim);
253 }
254 }
255 }
256 }
257
258 /**
Chet Haasea18a86b2010-09-07 13:20:00 -0700259 * Sets up this AnimatorSet to play each of the supplied animations when the
Chet Haase17fb4b02010-06-28 17:55:07 -0700260 * previous animation ends.
261 *
Chet Haase37a7bec2010-11-30 15:55:39 -0800262 * @param items The animations that will be started one after another.
Chet Haase17fb4b02010-06-28 17:55:07 -0700263 */
Chet Haasea18a86b2010-09-07 13:20:00 -0700264 public void playSequentially(Animator... items) {
265 if (items != null) {
Chet Haasea18a86b2010-09-07 13:20:00 -0700266 if (items.length == 1) {
267 play(items[0]);
Chet Haase17fb4b02010-06-28 17:55:07 -0700268 } else {
Chet Haasea18a86b2010-09-07 13:20:00 -0700269 for (int i = 0; i < items.length - 1; ++i) {
Doris Liu13099142015-07-10 17:32:41 -0700270 play(items[i]).before(items[i + 1]);
Chet Haase17fb4b02010-06-28 17:55:07 -0700271 }
272 }
273 }
274 }
275
276 /**
Chet Haase37a7bec2010-11-30 15:55:39 -0800277 * Sets up this AnimatorSet to play each of the supplied animations when the
278 * previous animation ends.
279 *
280 * @param items The animations that will be started one after another.
281 */
282 public void playSequentially(List<Animator> items) {
283 if (items != null && items.size() > 0) {
Chet Haase37a7bec2010-11-30 15:55:39 -0800284 if (items.size() == 1) {
285 play(items.get(0));
286 } else {
287 for (int i = 0; i < items.size() - 1; ++i) {
Doris Liu13099142015-07-10 17:32:41 -0700288 play(items.get(i)).before(items.get(i + 1));
Chet Haase37a7bec2010-11-30 15:55:39 -0800289 }
290 }
291 }
292 }
293
294 /**
Chet Haasea18a86b2010-09-07 13:20:00 -0700295 * Returns the current list of child Animator objects controlled by this
296 * AnimatorSet. This is a copy of the internal list; modifications to the returned list
297 * will not affect the AnimatorSet, although changes to the underlying Animator objects
298 * will affect those objects being managed by the AnimatorSet.
Chet Haasef54a8d72010-07-22 14:44:59 -0700299 *
Chet Haasea18a86b2010-09-07 13:20:00 -0700300 * @return ArrayList<Animator> The list of child animations of this AnimatorSet.
Chet Haasef54a8d72010-07-22 14:44:59 -0700301 */
Chet Haasea18a86b2010-09-07 13:20:00 -0700302 public ArrayList<Animator> getChildAnimations() {
303 ArrayList<Animator> childList = new ArrayList<Animator>();
Doris Liu13099142015-07-10 17:32:41 -0700304 int size = mNodes.size();
305 for (int i = 0; i < size; i++) {
306 Node node = mNodes.get(i);
Doris Liudbf69e42015-08-05 20:00:11 -0700307 if (node != mRootNode) {
308 childList.add(node.mAnimation);
309 }
Chet Haasef54a8d72010-07-22 14:44:59 -0700310 }
311 return childList;
312 }
313
314 /**
Chet Haase811ed1062010-08-06 10:38:15 -0700315 * Sets the target object for all current {@link #getChildAnimations() child animations}
Chet Haasea18a86b2010-09-07 13:20:00 -0700316 * of this AnimatorSet that take targets ({@link ObjectAnimator} and
317 * AnimatorSet).
Chet Haase811ed1062010-08-06 10:38:15 -0700318 *
319 * @param target The object being animated
320 */
Chet Haase21cd1382010-09-01 17:42:29 -0700321 @Override
Chet Haase811ed1062010-08-06 10:38:15 -0700322 public void setTarget(Object target) {
Doris Liu13099142015-07-10 17:32:41 -0700323 int size = mNodes.size();
324 for (int i = 0; i < size; i++) {
325 Node node = mNodes.get(i);
326 Animator animation = node.mAnimation;
Chet Haasea18a86b2010-09-07 13:20:00 -0700327 if (animation instanceof AnimatorSet) {
328 ((AnimatorSet)animation).setTarget(target);
329 } else if (animation instanceof ObjectAnimator) {
330 ((ObjectAnimator)animation).setTarget(target);
Chet Haase811ed1062010-08-06 10:38:15 -0700331 }
332 }
333 }
334
335 /**
Yigit Boyard422dc32014-09-25 12:23:35 -0700336 * @hide
337 */
338 @Override
339 public int getChangingConfigurations() {
340 int conf = super.getChangingConfigurations();
341 final int nodeCount = mNodes.size();
342 for (int i = 0; i < nodeCount; i ++) {
Doris Liu13099142015-07-10 17:32:41 -0700343 conf |= mNodes.get(i).mAnimation.getChangingConfigurations();
Yigit Boyard422dc32014-09-25 12:23:35 -0700344 }
345 return conf;
346 }
347
348 /**
Chet Haasee0ee2e92010-10-07 09:06:18 -0700349 * Sets the TimeInterpolator for all current {@link #getChildAnimations() child animations}
Chet Haase430742f2013-04-12 11:18:36 -0700350 * of this AnimatorSet. The default value is null, which means that no interpolator
351 * is set on this AnimatorSet. Setting the interpolator to any non-null value
352 * will cause that interpolator to be set on the child animations
353 * when the set is started.
Chet Haase21cd1382010-09-01 17:42:29 -0700354 *
Chet Haasea18a86b2010-09-07 13:20:00 -0700355 * @param interpolator the interpolator to be used by each child animation of this AnimatorSet
Chet Haase21cd1382010-09-01 17:42:29 -0700356 */
357 @Override
Chet Haasee0ee2e92010-10-07 09:06:18 -0700358 public void setInterpolator(TimeInterpolator interpolator) {
Chet Haase430742f2013-04-12 11:18:36 -0700359 mInterpolator = interpolator;
360 }
361
362 @Override
363 public TimeInterpolator getInterpolator() {
364 return mInterpolator;
Chet Haase21cd1382010-09-01 17:42:29 -0700365 }
366
367 /**
Chet Haase17fb4b02010-06-28 17:55:07 -0700368 * This method creates a <code>Builder</code> object, which is used to
369 * set up playing constraints. This initial <code>play()</code> method
370 * tells the <code>Builder</code> the animation that is the dependency for
371 * the succeeding commands to the <code>Builder</code>. For example,
Chet Haasea18a86b2010-09-07 13:20:00 -0700372 * calling <code>play(a1).with(a2)</code> sets up the AnimatorSet to play
Chet Haase17fb4b02010-06-28 17:55:07 -0700373 * <code>a1</code> and <code>a2</code> at the same time,
Chet Haasea18a86b2010-09-07 13:20:00 -0700374 * <code>play(a1).before(a2)</code> sets up the AnimatorSet to play
Chet Haase17fb4b02010-06-28 17:55:07 -0700375 * <code>a1</code> first, followed by <code>a2</code>, and
Chet Haasea18a86b2010-09-07 13:20:00 -0700376 * <code>play(a1).after(a2)</code> sets up the AnimatorSet to play
Chet Haase17fb4b02010-06-28 17:55:07 -0700377 * <code>a2</code> first, followed by <code>a1</code>.
378 *
379 * <p>Note that <code>play()</code> is the only way to tell the
380 * <code>Builder</code> the animation upon which the dependency is created,
381 * so successive calls to the various functions in <code>Builder</code>
382 * will all refer to the initial parameter supplied in <code>play()</code>
383 * as the dependency of the other animations. For example, calling
384 * <code>play(a1).before(a2).before(a3)</code> will play both <code>a2</code>
385 * and <code>a3</code> when a1 ends; it does not set up a dependency between
386 * <code>a2</code> and <code>a3</code>.</p>
387 *
388 * @param anim The animation that is the dependency used in later calls to the
389 * methods in the returned <code>Builder</code> object. A null parameter will result
390 * in a null <code>Builder</code> return value.
Chet Haasea18a86b2010-09-07 13:20:00 -0700391 * @return Builder The object that constructs the AnimatorSet based on the dependencies
Chet Haase17fb4b02010-06-28 17:55:07 -0700392 * outlined in the calls to <code>play</code> and the other methods in the
393 * <code>Builder</code object.
394 */
Chet Haasea18a86b2010-09-07 13:20:00 -0700395 public Builder play(Animator anim) {
Chet Haase17fb4b02010-06-28 17:55:07 -0700396 if (anim != null) {
Chet Haase17fb4b02010-06-28 17:55:07 -0700397 return new Builder(anim);
398 }
399 return null;
400 }
401
402 /**
403 * {@inheritDoc}
404 *
Chet Haase8b699792011-08-05 15:20:19 -0700405 * <p>Note that canceling a <code>AnimatorSet</code> also cancels all of the animations that it
406 * is responsible for.</p>
Chet Haase17fb4b02010-06-28 17:55:07 -0700407 */
408 @SuppressWarnings("unchecked")
409 @Override
410 public void cancel() {
Doris Liu13351992017-01-17 17:10:42 -0800411 if (Looper.myLooper() == null) {
412 throw new AndroidRuntimeException("Animators may only be run on Looper threads");
413 }
Chet Haase8b699792011-08-05 15:20:19 -0700414 if (isStarted()) {
Chet Haase7dfacdb2011-07-11 17:01:56 -0700415 ArrayList<AnimatorListener> tmpListeners = null;
Chet Haasee2ab7cc2010-12-06 16:10:07 -0800416 if (mListeners != null) {
Chet Haase7dfacdb2011-07-11 17:01:56 -0700417 tmpListeners = (ArrayList<AnimatorListener>) mListeners.clone();
Doris Liu13099142015-07-10 17:32:41 -0700418 int size = tmpListeners.size();
419 for (int i = 0; i < size; i++) {
420 tmpListeners.get(i).onAnimationCancel(this);
Chet Haase7dfacdb2011-07-11 17:01:56 -0700421 }
422 }
Doris Liu13351992017-01-17 17:10:42 -0800423 ArrayList<Node> playingSet = new ArrayList<>(mPlayingSet);
Doris Liu13099142015-07-10 17:32:41 -0700424 int setSize = playingSet.size();
425 for (int i = 0; i < setSize; i++) {
Doris Liu13351992017-01-17 17:10:42 -0800426 playingSet.get(i).mAnimation.cancel();
Chet Haase7dfacdb2011-07-11 17:01:56 -0700427 }
Doris Liu13351992017-01-17 17:10:42 -0800428 mPlayingSet.clear();
429 endAnimation();
Chet Haase17fb4b02010-06-28 17:55:07 -0700430 }
431 }
432
Doris Liu6d452092017-02-08 14:47:08 -0800433 // Force all the animations to end when the duration scale is 0.
434 private void forceToEnd() {
Doris Liuae570c02017-04-03 11:08:11 -0700435 if (mEndCanBeCalled) {
436 end();
437 return;
438 }
Doris Liud7968dc2017-03-16 10:58:47 -0700439
440 // Note: we don't want to combine this case with the end() method below because in
441 // the case of developer calling end(), we still need to make sure end() is explicitly
442 // called on the child animators to maintain the old behavior.
443 if (mReversing) {
444 handleAnimationEvents(mLastEventId, 0, getTotalDuration());
Doris Liu6d452092017-02-08 14:47:08 -0800445 } else {
Doris Liud7968dc2017-03-16 10:58:47 -0700446 long zeroScalePlayTime = getTotalDuration();
447 if (zeroScalePlayTime == DURATION_INFINITE) {
448 // Use a large number for the play time.
449 zeroScalePlayTime = Integer.MAX_VALUE;
Doris Liu6d452092017-02-08 14:47:08 -0800450 }
Doris Liud7968dc2017-03-16 10:58:47 -0700451 handleAnimationEvents(mLastEventId, mEvents.size() - 1, zeroScalePlayTime);
Doris Liu6d452092017-02-08 14:47:08 -0800452 }
Doris Liud7968dc2017-03-16 10:58:47 -0700453 mPlayingSet.clear();
454 endAnimation();
Doris Liu6d452092017-02-08 14:47:08 -0800455 }
456
Chet Haase17fb4b02010-06-28 17:55:07 -0700457 /**
458 * {@inheritDoc}
459 *
Chet Haasea18a86b2010-09-07 13:20:00 -0700460 * <p>Note that ending a <code>AnimatorSet</code> also ends all of the animations that it is
Chet Haase17fb4b02010-06-28 17:55:07 -0700461 * responsible for.</p>
462 */
463 @Override
464 public void end() {
Doris Liu13351992017-01-17 17:10:42 -0800465 if (Looper.myLooper() == null) {
466 throw new AndroidRuntimeException("Animators may only be run on Looper threads");
467 }
Doris Liu58606db2016-04-13 13:28:38 -0700468 if (mShouldIgnoreEndWithoutStart && !isStarted()) {
469 return;
470 }
Chet Haase8b699792011-08-05 15:20:19 -0700471 if (isStarted()) {
Doris Liu13351992017-01-17 17:10:42 -0800472 // Iterate the animations that haven't finished or haven't started, and end them.
473 if (mReversing) {
474 // Between start() and first frame, mLastEventId would be unset (i.e. -1)
475 mLastEventId = mLastEventId == -1 ? mEvents.size() : mLastEventId;
Doris Liu88bb31b2017-06-06 14:56:02 -0700476 while (mLastEventId > 0) {
477 mLastEventId = mLastEventId - 1;
478 AnimationEvent event = mEvents.get(mLastEventId);
Doris Liu6d452092017-02-08 14:47:08 -0800479 Animator anim = event.mNode.mAnimation;
Doris Liu88bb31b2017-06-06 14:56:02 -0700480 if (mNodeMap.get(anim).mEnded) {
481 continue;
482 }
Doris Liu13351992017-01-17 17:10:42 -0800483 if (event.mEvent == AnimationEvent.ANIMATION_END) {
Doris Liu6d452092017-02-08 14:47:08 -0800484 anim.reverse();
485 } else if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED
486 && anim.isStarted()) {
487 // Make sure anim hasn't finished before calling end() so that we don't end
488 // already ended animations, which will cause start and end callbacks to be
489 // triggered again.
490 anim.end();
Chet Haase7dfacdb2011-07-11 17:01:56 -0700491 }
Doris Liu13351992017-01-17 17:10:42 -0800492 }
493 } else {
Doris Liu88bb31b2017-06-06 14:56:02 -0700494 while (mLastEventId < mEvents.size() - 1) {
495 // Avoid potential reentrant loop caused by child animators manipulating
496 // AnimatorSet's lifecycle (i.e. not a recommended approach).
497 mLastEventId = mLastEventId + 1;
498 AnimationEvent event = mEvents.get(mLastEventId);
Doris Liu6d452092017-02-08 14:47:08 -0800499 Animator anim = event.mNode.mAnimation;
Doris Liu88bb31b2017-06-06 14:56:02 -0700500 if (mNodeMap.get(anim).mEnded) {
501 continue;
502 }
Doris Liu13351992017-01-17 17:10:42 -0800503 if (event.mEvent == AnimationEvent.ANIMATION_START) {
Doris Liu6d452092017-02-08 14:47:08 -0800504 anim.start();
505 } else if (event.mEvent == AnimationEvent.ANIMATION_END && anim.isStarted()) {
506 // Make sure anim hasn't finished before calling end() so that we don't end
507 // already ended animations, which will cause start and end callbacks to be
508 // triggered again.
509 anim.end();
Doris Liu13351992017-01-17 17:10:42 -0800510 }
Chet Haase1e0ac5a2010-08-27 08:32:11 -0700511 }
Chet Haase1e0ac5a2010-08-27 08:32:11 -0700512 }
Doris Liu13351992017-01-17 17:10:42 -0800513 mPlayingSet.clear();
Chet Haase17fb4b02010-06-28 17:55:07 -0700514 }
Doris Liu13351992017-01-17 17:10:42 -0800515 endAnimation();
Chet Haase17fb4b02010-06-28 17:55:07 -0700516 }
517
518 /**
Chet Haase8b699792011-08-05 15:20:19 -0700519 * Returns true if any of the child animations of this AnimatorSet have been started and have
Doris Liuee684552015-08-28 13:21:03 -0700520 * not yet ended. Child animations will not be started until the AnimatorSet has gone past
521 * its initial delay set through {@link #setStartDelay(long)}.
522 *
523 * @return Whether this AnimatorSet has gone past the initial delay, and at least one child
524 * animation has been started and not yet ended.
Chet Haase673e42f2010-08-25 16:32:37 -0700525 */
526 @Override
527 public boolean isRunning() {
Doris Liu6d452092017-02-08 14:47:08 -0800528 if (mStartDelay == 0) {
Doris Liu13351992017-01-17 17:10:42 -0800529 return mStarted;
Chet Haase673e42f2010-08-25 16:32:37 -0700530 }
Doris Liu6d452092017-02-08 14:47:08 -0800531 return mLastFrameTime > 0;
Chet Haase8b699792011-08-05 15:20:19 -0700532 }
533
534 @Override
535 public boolean isStarted() {
536 return mStarted;
Chet Haase673e42f2010-08-25 16:32:37 -0700537 }
538
539 /**
Chet Haase21cd1382010-09-01 17:42:29 -0700540 * The amount of time, in milliseconds, to delay starting the animation after
541 * {@link #start()} is called.
542 *
543 * @return the number of milliseconds to delay running the animation
544 */
545 @Override
546 public long getStartDelay() {
547 return mStartDelay;
548 }
549
550 /**
551 * The amount of time, in milliseconds, to delay starting the animation after
Doris Liu61045c52016-05-24 16:38:19 -0700552 * {@link #start()} is called. Note that the start delay should always be non-negative. Any
553 * negative start delay will be clamped to 0 on N and above.
554 *
Chet Haase21cd1382010-09-01 17:42:29 -0700555 * @param startDelay The amount of the delay, in milliseconds
556 */
557 @Override
558 public void setStartDelay(long startDelay) {
Doris Liu61045c52016-05-24 16:38:19 -0700559 // Clamp start delay to non-negative range.
560 if (startDelay < 0) {
561 Log.w(TAG, "Start delay should always be non-negative");
562 startDelay = 0;
ztenghui7bc6a3f2014-07-15 15:12:12 -0700563 }
Doris Liu13099142015-07-10 17:32:41 -0700564 long delta = startDelay - mStartDelay;
Doris Liu49db4242015-08-10 13:33:06 -0700565 if (delta == 0) {
566 return;
567 }
Chet Haase21cd1382010-09-01 17:42:29 -0700568 mStartDelay = startDelay;
Doris Liu13099142015-07-10 17:32:41 -0700569 if (!mDependencyDirty) {
570 // Dependency graph already constructed, update all the nodes' start/end time
571 int size = mNodes.size();
572 for (int i = 0; i < size; i++) {
573 Node node = mNodes.get(i);
574 if (node == mRootNode) {
575 node.mEndTime = mStartDelay;
576 } else {
577 node.mStartTime = node.mStartTime == DURATION_INFINITE ?
578 DURATION_INFINITE : node.mStartTime + delta;
579 node.mEndTime = node.mEndTime == DURATION_INFINITE ?
580 DURATION_INFINITE : node.mEndTime + delta;
Doris Liu13099142015-07-10 17:32:41 -0700581 }
582 }
Doris Liu49db4242015-08-10 13:33:06 -0700583 // Update total duration, if necessary.
584 if (mTotalDuration != DURATION_INFINITE) {
585 mTotalDuration += delta;
586 }
Doris Liu13099142015-07-10 17:32:41 -0700587 }
Chet Haase21cd1382010-09-01 17:42:29 -0700588 }
589
590 /**
Chet Haasea18a86b2010-09-07 13:20:00 -0700591 * Gets the length of each of the child animations of this AnimatorSet. This value may
592 * be less than 0, which indicates that no duration has been set on this AnimatorSet
Chet Haase21cd1382010-09-01 17:42:29 -0700593 * and each of the child animations will use their own duration.
594 *
595 * @return The length of the animation, in milliseconds, of each of the child
Chet Haasea18a86b2010-09-07 13:20:00 -0700596 * animations of this AnimatorSet.
Chet Haase21cd1382010-09-01 17:42:29 -0700597 */
598 @Override
599 public long getDuration() {
600 return mDuration;
601 }
602
603 /**
Chet Haasea18a86b2010-09-07 13:20:00 -0700604 * Sets the length of each of the current child animations of this AnimatorSet. By default,
605 * each child animation will use its own duration. If the duration is set on the AnimatorSet,
Chet Haase21cd1382010-09-01 17:42:29 -0700606 * then each child animation inherits this duration.
607 *
608 * @param duration The length of the animation, in milliseconds, of each of the child
Chet Haasea18a86b2010-09-07 13:20:00 -0700609 * animations of this AnimatorSet.
Chet Haase21cd1382010-09-01 17:42:29 -0700610 */
611 @Override
Chet Haase2794eb32010-10-12 16:29:28 -0700612 public AnimatorSet setDuration(long duration) {
Chet Haase21cd1382010-09-01 17:42:29 -0700613 if (duration < 0) {
614 throw new IllegalArgumentException("duration must be a value of zero or greater");
615 }
Doris Liu13099142015-07-10 17:32:41 -0700616 mDependencyDirty = true;
Chet Haasec299a332012-04-12 07:51:50 -0700617 // Just record the value for now - it will be used later when the AnimatorSet starts
Chet Haase21cd1382010-09-01 17:42:29 -0700618 mDuration = duration;
Chet Haase2794eb32010-10-12 16:29:28 -0700619 return this;
Chet Haase21cd1382010-09-01 17:42:29 -0700620 }
621
Chet Haase2970c492010-11-09 13:58:04 -0800622 @Override
623 public void setupStartValues() {
Doris Liu13099142015-07-10 17:32:41 -0700624 int size = mNodes.size();
625 for (int i = 0; i < size; i++) {
626 Node node = mNodes.get(i);
Doris Liu458f20e2015-08-10 17:32:54 -0700627 if (node != mRootNode) {
628 node.mAnimation.setupStartValues();
629 }
Chet Haase2970c492010-11-09 13:58:04 -0800630 }
631 }
632
633 @Override
634 public void setupEndValues() {
Doris Liu13099142015-07-10 17:32:41 -0700635 int size = mNodes.size();
636 for (int i = 0; i < size; i++) {
637 Node node = mNodes.get(i);
Doris Liu458f20e2015-08-10 17:32:54 -0700638 if (node != mRootNode) {
639 node.mAnimation.setupEndValues();
640 }
Chet Haase2970c492010-11-09 13:58:04 -0800641 }
642 }
643
Chet Haase8aa1ffb2013-08-08 14:00:00 -0700644 @Override
645 public void pause() {
Doris Liu13351992017-01-17 17:10:42 -0800646 if (Looper.myLooper() == null) {
647 throw new AndroidRuntimeException("Animators may only be run on Looper threads");
648 }
Chet Haase8aa1ffb2013-08-08 14:00:00 -0700649 boolean previouslyPaused = mPaused;
650 super.pause();
651 if (!previouslyPaused && mPaused) {
Doris Liu13351992017-01-17 17:10:42 -0800652 mPauseTime = -1;
Chet Haase8aa1ffb2013-08-08 14:00:00 -0700653 }
654 }
655
656 @Override
657 public void resume() {
Doris Liu13351992017-01-17 17:10:42 -0800658 if (Looper.myLooper() == null) {
659 throw new AndroidRuntimeException("Animators may only be run on Looper threads");
660 }
Chet Haase8aa1ffb2013-08-08 14:00:00 -0700661 boolean previouslyPaused = mPaused;
662 super.resume();
663 if (previouslyPaused && !mPaused) {
Doris Liu13351992017-01-17 17:10:42 -0800664 if (mPauseTime >= 0) {
665 addAnimationCallback(0);
Chet Haase8aa1ffb2013-08-08 14:00:00 -0700666 }
667 }
668 }
669
Chet Haase21cd1382010-09-01 17:42:29 -0700670 /**
Chet Haase17fb4b02010-06-28 17:55:07 -0700671 * {@inheritDoc}
672 *
Chet Haasea18a86b2010-09-07 13:20:00 -0700673 * <p>Starting this <code>AnimatorSet</code> will, in turn, start the animations for which
Chet Haase17fb4b02010-06-28 17:55:07 -0700674 * it is responsible. The details of when exactly those animations are started depends on
675 * the dependency relationships that have been set up between the animations.
Doris Liu88bb31b2017-06-06 14:56:02 -0700676 *
677 * <b>Note:</b> Manipulating AnimatorSet's lifecycle in the child animators' listener callbacks
678 * will lead to undefined behaviors. Also, AnimatorSet will ignore any seeking in the child
679 * animators once {@link #start()} is called.
Chet Haase17fb4b02010-06-28 17:55:07 -0700680 */
681 @SuppressWarnings("unchecked")
682 @Override
683 public void start() {
Doris Liu13351992017-01-17 17:10:42 -0800684 start(false, true);
685 }
686
687 @Override
688 void startWithoutPulsing(boolean inReverse) {
689 start(inReverse, false);
690 }
691
692 private void initAnimation() {
693 if (mInterpolator != null) {
694 for (int i = 0; i < mNodes.size(); i++) {
695 Node node = mNodes.get(i);
696 node.mAnimation.setInterpolator(mInterpolator);
697 }
698 }
699 updateAnimatorsDuration();
700 createDependencyGraph();
701 }
702
703 private void start(boolean inReverse, boolean selfPulse) {
704 if (Looper.myLooper() == null) {
705 throw new AndroidRuntimeException("Animators may only be run on Looper threads");
706 }
Chet Haase8b699792011-08-05 15:20:19 -0700707 mStarted = true;
Doris Liu13351992017-01-17 17:10:42 -0800708 mSelfPulse = selfPulse;
Chet Haase8aa1ffb2013-08-08 14:00:00 -0700709 mPaused = false;
Doris Liu13351992017-01-17 17:10:42 -0800710 mPauseTime = -1;
Chet Haase010dbaa2010-07-19 17:29:49 -0700711
Doris Liu13099142015-07-10 17:32:41 -0700712 int size = mNodes.size();
713 for (int i = 0; i < size; i++) {
714 Node node = mNodes.get(i);
715 node.mEnded = false;
716 node.mAnimation.setAllowRunningAsynchronously(false);
John Reckf5945a02014-09-05 15:57:47 -0700717 }
718
Doris Liu13351992017-01-17 17:10:42 -0800719 initAnimation();
720 if (inReverse && !canReverse()) {
721 throw new UnsupportedOperationException("Cannot reverse infinite AnimatorSet");
Chet Haasee2ab7cc2010-12-06 16:10:07 -0800722 }
723
Doris Liu13351992017-01-17 17:10:42 -0800724 mReversing = inReverse;
Doris Liu13099142015-07-10 17:32:41 -0700725
Chet Haase17fb4b02010-06-28 17:55:07 -0700726 // Now that all dependencies are set up, start the animations that should be started.
Doris Liu21ba77f2017-03-06 16:44:38 -0800727 boolean isEmptySet = isEmptySet(this);
728 if (!isEmptySet) {
Doris Liu13351992017-01-17 17:10:42 -0800729 startAnimation();
Doris Liuf57bfe22015-10-01 13:26:01 -0700730 }
731
Chet Haase17fb4b02010-06-28 17:55:07 -0700732 if (mListeners != null) {
Chet Haasea18a86b2010-09-07 13:20:00 -0700733 ArrayList<AnimatorListener> tmpListeners =
734 (ArrayList<AnimatorListener>) mListeners.clone();
Chet Haase7c608f22010-10-22 17:54:04 -0700735 int numListeners = tmpListeners.size();
736 for (int i = 0; i < numListeners; ++i) {
Doris Liu13351992017-01-17 17:10:42 -0800737 tmpListeners.get(i).onAnimationStart(this, inReverse);
Chet Haase8b699792011-08-05 15:20:19 -0700738 }
739 }
Doris Liu21ba77f2017-03-06 16:44:38 -0800740 if (isEmptySet) {
Doris Liu6d452092017-02-08 14:47:08 -0800741 // In the case of empty AnimatorSet, or 0 duration scale, we will trigger the
742 // onAnimationEnd() right away.
Doris Liud7968dc2017-03-16 10:58:47 -0700743 end();
Chet Haase17fb4b02010-06-28 17:55:07 -0700744 }
745 }
746
Doris Liuf66d2f62016-08-22 14:30:48 -0700747 // Returns true if set is empty or contains nothing but animator sets with no start delay.
748 private static boolean isEmptySet(AnimatorSet set) {
749 if (set.getStartDelay() > 0) {
750 return false;
751 }
752 for (int i = 0; i < set.getChildAnimations().size(); i++) {
753 Animator anim = set.getChildAnimations().get(i);
754 if (!(anim instanceof AnimatorSet)) {
755 // Contains non-AnimatorSet, not empty.
756 return false;
757 } else {
758 if (!isEmptySet((AnimatorSet) anim)) {
759 return false;
760 }
761 }
762 }
763 return true;
764 }
765
Doris Liu13099142015-07-10 17:32:41 -0700766 private void updateAnimatorsDuration() {
767 if (mDuration >= 0) {
768 // If the duration was set on this AnimatorSet, pass it along to all child animations
769 int size = mNodes.size();
770 for (int i = 0; i < size; i++) {
771 Node node = mNodes.get(i);
772 // TODO: don't set the duration of the timing-only nodes created by AnimatorSet to
773 // insert "play-after" delays
774 node.mAnimation.setDuration(mDuration);
775 }
776 }
777 mDelayAnim.setDuration(mStartDelay);
778 }
779
Doris Liu13351992017-01-17 17:10:42 -0800780 @Override
781 void skipToEndValue(boolean inReverse) {
782 if (!isInitialized()) {
783 throw new UnsupportedOperationException("Children must be initialized.");
784 }
785
786 // This makes sure the animation events are sorted an up to date.
787 initAnimation();
788
789 // Calling skip to the end in the sequence that they would be called in a forward/reverse
790 // run, such that the sequential animations modifying the same property would have
791 // the right value in the end.
792 if (inReverse) {
793 for (int i = mEvents.size() - 1; i >= 0; i--) {
794 if (mEvents.get(i).mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
795 mEvents.get(i).mNode.mAnimation.skipToEndValue(true);
796 }
797 }
798 } else {
799 for (int i = 0; i < mEvents.size(); i++) {
800 if (mEvents.get(i).mEvent == AnimationEvent.ANIMATION_END) {
801 mEvents.get(i).mNode.mAnimation.skipToEndValue(false);
802 }
803 }
804 }
805 }
806
807 /**
808 * Internal only.
809 *
810 * This method sets the animation values based on the play time. It also fast forward or
811 * backward all the child animations progress accordingly.
812 *
813 * This method is also responsible for calling
814 * {@link android.view.animation.Animation.AnimationListener#onAnimationRepeat(Animation)},
815 * as needed, based on the last play time and current play time.
816 */
817 @Override
818 void animateBasedOnPlayTime(long currentPlayTime, long lastPlayTime, boolean inReverse) {
819 if (currentPlayTime < 0 || lastPlayTime < 0) {
820 throw new UnsupportedOperationException("Error: Play time should never be negative.");
821 }
822 // TODO: take into account repeat counts and repeat callback when repeat is implemented.
823 // Clamp currentPlayTime and lastPlayTime
824
825 // TODO: Make this more efficient
826
827 // Convert the play times to the forward direction.
828 if (inReverse) {
829 if (getTotalDuration() == DURATION_INFINITE) {
830 throw new UnsupportedOperationException("Cannot reverse AnimatorSet with infinite"
831 + " duration");
832 }
833 long duration = getTotalDuration() - mStartDelay;
834 currentPlayTime = Math.min(currentPlayTime, duration);
835 currentPlayTime = duration - currentPlayTime;
836 lastPlayTime = duration - lastPlayTime;
837 inReverse = false;
838 }
Doris Liu13351992017-01-17 17:10:42 -0800839
840 ArrayList<Node> unfinishedNodes = new ArrayList<>();
841 // Assumes forward playing from here on.
842 for (int i = 0; i < mEvents.size(); i++) {
843 AnimationEvent event = mEvents.get(i);
Doris Liu29027162017-09-26 17:00:54 -0700844 if (event.getTime() > currentPlayTime || event.getTime() == DURATION_INFINITE) {
Doris Liu13351992017-01-17 17:10:42 -0800845 break;
846 }
847
848 // This animation started prior to the current play time, and won't finish before the
849 // play time, add to the unfinished list.
850 if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
851 if (event.mNode.mEndTime == DURATION_INFINITE
852 || event.mNode.mEndTime > currentPlayTime) {
853 unfinishedNodes.add(event.mNode);
854 }
855 }
856 // For animations that do finish before the play time, end them in the sequence that
857 // they would in a normal run.
858 if (event.mEvent == AnimationEvent.ANIMATION_END) {
859 // Skip to the end of the animation.
860 event.mNode.mAnimation.skipToEndValue(false);
861 }
862 }
863
864 // Seek unfinished animation to the right time.
865 for (int i = 0; i < unfinishedNodes.size(); i++) {
866 Node node = unfinishedNodes.get(i);
867 long playTime = getPlayTimeForNode(currentPlayTime, node, inReverse);
Doris Liu6d452092017-02-08 14:47:08 -0800868 if (!inReverse) {
869 playTime -= node.mAnimation.getStartDelay();
870 }
Doris Liu13351992017-01-17 17:10:42 -0800871 node.mAnimation.animateBasedOnPlayTime(playTime, lastPlayTime, inReverse);
872 }
Doris Liu0b2c8362018-04-02 16:37:17 -0700873
874 // Seek not yet started animations.
875 for (int i = 0; i < mEvents.size(); i++) {
876 AnimationEvent event = mEvents.get(i);
877 if (event.getTime() > currentPlayTime
878 && event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
879 event.mNode.mAnimation.skipToEndValue(true);
880 }
881 }
882
Doris Liu13351992017-01-17 17:10:42 -0800883 }
884
885 @Override
886 boolean isInitialized() {
887 if (mChildrenInitialized) {
888 return true;
889 }
890
891 boolean allInitialized = true;
892 for (int i = 0; i < mNodes.size(); i++) {
893 if (!mNodes.get(i).mAnimation.isInitialized()) {
894 allInitialized = false;
895 break;
896 }
897 }
898 mChildrenInitialized = allInitialized;
899 return mChildrenInitialized;
900 }
901
902 private void skipToStartValue(boolean inReverse) {
903 skipToEndValue(!inReverse);
904 }
905
906 /**
907 * Sets the position of the animation to the specified point in time. This time should
908 * be between 0 and the total duration of the animation, including any repetition. If
909 * the animation has not yet been started, then it will not advance forward after it is
910 * set to this time; it will simply set the time to this value and perform any appropriate
911 * actions based on that time. If the animation is already running, then setCurrentPlayTime()
912 * will set the current playing time to this value and continue playing from that point.
913 *
914 * @param playTime The time, in milliseconds, to which the animation is advanced or rewound.
915 * Unless the animation is reversing, the playtime is considered the time since
916 * the end of the start delay of the AnimatorSet in a forward playing direction.
917 *
918 */
919 public void setCurrentPlayTime(long playTime) {
920 if (mReversing && getTotalDuration() == DURATION_INFINITE) {
921 // Should never get here
922 throw new UnsupportedOperationException("Error: Cannot seek in reverse in an infinite"
923 + " AnimatorSet");
924 }
925
926 if ((getTotalDuration() != DURATION_INFINITE && playTime > getTotalDuration() - mStartDelay)
927 || playTime < 0) {
928 throw new UnsupportedOperationException("Error: Play time should always be in between"
929 + "0 and duration.");
930 }
931
932 initAnimation();
933
Doris Liu0b2c8362018-04-02 16:37:17 -0700934 if (!isStarted() || isPaused()) {
Doris Liu13351992017-01-17 17:10:42 -0800935 if (mReversing) {
936 throw new UnsupportedOperationException("Error: Something went wrong. mReversing"
937 + " should not be set when AnimatorSet is not started.");
938 }
939 if (!mSeekState.isActive()) {
940 findLatestEventIdForTime(0);
941 // Set all the values to start values.
942 initChildren();
Doris Liu13351992017-01-17 17:10:42 -0800943 mSeekState.setPlayTime(0, mReversing);
944 }
945 animateBasedOnPlayTime(playTime, 0, mReversing);
946 mSeekState.setPlayTime(playTime, mReversing);
947 } else {
948 // If the animation is running, just set the seek time and wait until the next frame
949 // (i.e. doAnimationFrame(...)) to advance the animation.
950 mSeekState.setPlayTime(playTime, mReversing);
951 }
952 }
953
Doris Liudd65ab02017-02-08 14:51:54 -0800954 /**
Doris Liu02eabdf2017-04-26 11:09:43 -0700955 * Returns the milliseconds elapsed since the start of the animation.
Doris Liudd65ab02017-02-08 14:51:54 -0800956 *
Doris Liu02eabdf2017-04-26 11:09:43 -0700957 * <p>For ongoing animations, this method returns the current progress of the animation in
958 * terms of play time. For an animation that has not yet been started: if the animation has been
959 * seeked to a certain time via {@link #setCurrentPlayTime(long)}, the seeked play time will
960 * be returned; otherwise, this method will return 0.
961 *
962 * @return the current position in time of the animation in milliseconds
Doris Liudd65ab02017-02-08 14:51:54 -0800963 */
964 public long getCurrentPlayTime() {
965 if (mSeekState.isActive()) {
966 return mSeekState.getPlayTime();
967 }
968 if (mLastFrameTime == -1) {
969 // Not yet started or during start delay
970 return 0;
971 }
972 float durationScale = ValueAnimator.getDurationScale();
973 durationScale = durationScale == 0 ? 1 : durationScale;
974 if (mReversing) {
975 return (long) ((mLastFrameTime - mFirstFrame) / durationScale);
976 } else {
977 return (long) ((mLastFrameTime - mFirstFrame - mStartDelay) / durationScale);
978 }
979 }
980
Doris Liu13351992017-01-17 17:10:42 -0800981 private void initChildren() {
982 if (!isInitialized()) {
983 mChildrenInitialized = true;
984 // Forcefully initialize all children based on their end time, so that if the start
985 // value of a child is dependent on a previous animation, the animation will be
986 // initialized after the the previous animations have been advanced to the end.
987 skipToEndValue(false);
988 }
989 }
990
991 /**
992 * @param frameTime The frame start time, in the {@link SystemClock#uptimeMillis()} time
993 * base.
994 * @return
995 * @hide
996 */
997 @Override
998 public boolean doAnimationFrame(long frameTime) {
Doris Liu6d452092017-02-08 14:47:08 -0800999 float durationScale = ValueAnimator.getDurationScale();
1000 if (durationScale == 0f) {
Doris Liu21ba77f2017-03-06 16:44:38 -08001001 // Duration scale is 0, end the animation right away.
Doris Liu6d452092017-02-08 14:47:08 -08001002 forceToEnd();
1003 return true;
1004 }
1005
1006 // After the first frame comes in, we need to wait for start delay to pass before updating
1007 // any animation values.
1008 if (mFirstFrame < 0) {
1009 mFirstFrame = frameTime;
Doris Liu13351992017-01-17 17:10:42 -08001010 }
1011
1012 // Handle pause/resume
1013 if (mPaused) {
1014 // Note: Child animations don't receive pause events. Since it's never a contract that
1015 // the child animators will be paused when set is paused, this is unlikely to be an
1016 // issue.
1017 mPauseTime = frameTime;
1018 removeAnimationCallback();
1019 return false;
1020 } else if (mPauseTime > 0) {
1021 // Offset by the duration that the animation was paused
1022 mFirstFrame += (frameTime - mPauseTime);
1023 mPauseTime = -1;
1024 }
1025
1026 // Continue at seeked position
1027 if (mSeekState.isActive()) {
1028 mSeekState.updateSeekDirection(mReversing);
Doris Liu6d452092017-02-08 14:47:08 -08001029 if (mReversing) {
1030 mFirstFrame = (long) (frameTime - mSeekState.getPlayTime() * durationScale);
1031 } else {
1032 mFirstFrame = (long) (frameTime - (mSeekState.getPlayTime() + mStartDelay)
1033 * durationScale);
1034 }
Doris Liu13351992017-01-17 17:10:42 -08001035 mSeekState.reset();
1036 }
1037
Doris Liu6d452092017-02-08 14:47:08 -08001038 if (!mReversing && frameTime < mFirstFrame + mStartDelay * durationScale) {
1039 // Still during start delay in a forward playing case.
1040 return false;
1041 }
1042
1043 // From here on, we always use unscaled play time. Note this unscaled playtime includes
1044 // the start delay.
1045 long unscaledPlayTime = (long) ((frameTime - mFirstFrame) / durationScale);
1046 mLastFrameTime = frameTime;
Doris Liu13351992017-01-17 17:10:42 -08001047
1048 // 1. Pulse the animators that will start or end in this frame
1049 // 2. Pulse the animators that will finish in a later frame
Doris Liu6d452092017-02-08 14:47:08 -08001050 int latestId = findLatestEventIdForTime(unscaledPlayTime);
Doris Liu13351992017-01-17 17:10:42 -08001051 int startId = mLastEventId;
1052
Doris Liu6d452092017-02-08 14:47:08 -08001053 handleAnimationEvents(startId, latestId, unscaledPlayTime);
Doris Liu13351992017-01-17 17:10:42 -08001054
1055 mLastEventId = latestId;
1056
1057 // Pump a frame to the on-going animators
1058 for (int i = 0; i < mPlayingSet.size(); i++) {
1059 Node node = mPlayingSet.get(i);
1060 if (!node.mEnded) {
Doris Liu6d452092017-02-08 14:47:08 -08001061 pulseFrame(node, getPlayTimeForNode(unscaledPlayTime, node));
Doris Liu13351992017-01-17 17:10:42 -08001062 }
1063 }
1064
1065 // Remove all the finished anims
1066 for (int i = mPlayingSet.size() - 1; i >= 0; i--) {
1067 if (mPlayingSet.get(i).mEnded) {
1068 mPlayingSet.remove(i);
1069 }
1070 }
1071
Doris Liu6d452092017-02-08 14:47:08 -08001072 boolean finished = false;
1073 if (mReversing) {
1074 if (mPlayingSet.size() == 1 && mPlayingSet.get(0) == mRootNode) {
1075 // The only animation that is running is the delay animation.
1076 finished = true;
1077 } else if (mPlayingSet.isEmpty() && mLastEventId < 3) {
1078 // The only remaining animation is the delay animation
1079 finished = true;
Doris Liu13351992017-01-17 17:10:42 -08001080 }
Doris Liu6d452092017-02-08 14:47:08 -08001081 } else {
1082 finished = mPlayingSet.isEmpty() && mLastEventId == mEvents.size() - 1;
1083 }
1084
1085 if (finished) {
1086 endAnimation();
1087 return true;
Doris Liu13351992017-01-17 17:10:42 -08001088 }
1089 return false;
1090 }
1091
1092 /**
Doris Liu5c71b8c2017-01-31 14:38:21 -08001093 * @hide
1094 */
1095 @Override
1096 public void commitAnimationFrame(long frameTime) {
1097 // No op.
1098 }
1099
1100 @Override
1101 boolean pulseAnimationFrame(long frameTime) {
1102 return doAnimationFrame(frameTime);
1103 }
1104
1105 /**
Doris Liu13351992017-01-17 17:10:42 -08001106 * When playing forward, we call start() at the animation's scheduled start time, and make sure
1107 * to pump a frame at the animation's scheduled end time.
1108 *
1109 * When playing in reverse, we should reverse the animation when we hit animation's end event,
1110 * and expect the animation to end at the its delay ended event, rather than start event.
1111 */
1112 private void handleAnimationEvents(int startId, int latestId, long playTime) {
1113 if (mReversing) {
1114 startId = startId == -1 ? mEvents.size() : startId;
1115 for (int i = startId - 1; i >= latestId; i--) {
1116 AnimationEvent event = mEvents.get(i);
1117 Node node = event.mNode;
1118 if (event.mEvent == AnimationEvent.ANIMATION_END) {
Doris Liu5fb129c2017-04-21 12:24:54 -07001119 if (node.mAnimation.isStarted()) {
1120 // If the animation has already been started before its due time (i.e.
1121 // the child animator is being manipulated outside of the AnimatorSet), we
1122 // need to cancel the animation to reset the internal state (e.g. frame
1123 // time tracking) and remove the self pulsing callbacks
1124 node.mAnimation.cancel();
1125 }
1126 node.mEnded = false;
Doris Liu13351992017-01-17 17:10:42 -08001127 mPlayingSet.add(event.mNode);
1128 node.mAnimation.startWithoutPulsing(true);
Doris Liu5c71b8c2017-01-31 14:38:21 -08001129 pulseFrame(node, 0);
Doris Liu13351992017-01-17 17:10:42 -08001130 } else if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED && !node.mEnded) {
1131 // end event:
Doris Liu5c71b8c2017-01-31 14:38:21 -08001132 pulseFrame(node, getPlayTimeForNode(playTime, node));
Doris Liu13351992017-01-17 17:10:42 -08001133 }
1134 }
1135 } else {
1136 for (int i = startId + 1; i <= latestId; i++) {
1137 AnimationEvent event = mEvents.get(i);
1138 Node node = event.mNode;
1139 if (event.mEvent == AnimationEvent.ANIMATION_START) {
1140 mPlayingSet.add(event.mNode);
Doris Liu5fb129c2017-04-21 12:24:54 -07001141 if (node.mAnimation.isStarted()) {
1142 // If the animation has already been started before its due time (i.e.
1143 // the child animator is being manipulated outside of the AnimatorSet), we
1144 // need to cancel the animation to reset the internal state (e.g. frame
1145 // time tracking) and remove the self pulsing callbacks
1146 node.mAnimation.cancel();
1147 }
1148 node.mEnded = false;
Doris Liu13351992017-01-17 17:10:42 -08001149 node.mAnimation.startWithoutPulsing(false);
Doris Liu5c71b8c2017-01-31 14:38:21 -08001150 pulseFrame(node, 0);
Doris Liu13351992017-01-17 17:10:42 -08001151 } else if (event.mEvent == AnimationEvent.ANIMATION_END && !node.mEnded) {
1152 // start event:
Doris Liu5c71b8c2017-01-31 14:38:21 -08001153 pulseFrame(node, getPlayTimeForNode(playTime, node));
Doris Liu13351992017-01-17 17:10:42 -08001154 }
1155 }
1156 }
1157 }
1158
Doris Liu6d452092017-02-08 14:47:08 -08001159 /**
1160 * This method pulses frames into child animations. It scales the input animation play time
1161 * with the duration scale and pass that to the child animation via pulseAnimationFrame(long).
1162 *
1163 * @param node child animator node
1164 * @param animPlayTime unscaled play time (including start delay) for the child animator
1165 */
1166 private void pulseFrame(Node node, long animPlayTime) {
Doris Liu5c71b8c2017-01-31 14:38:21 -08001167 if (!node.mEnded) {
Doris Liud7968dc2017-03-16 10:58:47 -07001168 float durationScale = ValueAnimator.getDurationScale();
1169 durationScale = durationScale == 0 ? 1 : durationScale;
Doris Liu6d452092017-02-08 14:47:08 -08001170 node.mEnded = node.mAnimation.pulseAnimationFrame(
Doris Liud7968dc2017-03-16 10:58:47 -07001171 (long) (animPlayTime * durationScale));
Doris Liu5c71b8c2017-01-31 14:38:21 -08001172 }
1173 }
1174
Doris Liu13351992017-01-17 17:10:42 -08001175 private long getPlayTimeForNode(long overallPlayTime, Node node) {
1176 return getPlayTimeForNode(overallPlayTime, node, mReversing);
1177 }
1178
1179 private long getPlayTimeForNode(long overallPlayTime, Node node, boolean inReverse) {
1180 if (inReverse) {
1181 overallPlayTime = getTotalDuration() - overallPlayTime;
1182 return node.mEndTime - overallPlayTime;
1183 } else {
1184 return overallPlayTime - node.mStartTime;
1185 }
1186 }
1187
1188 private void startAnimation() {
Doris Liu66c564e2017-01-26 11:53:46 -08001189 addDummyListener();
1190
Doris Liu13351992017-01-17 17:10:42 -08001191 // Register animation callback
Doris Liu6d452092017-02-08 14:47:08 -08001192 addAnimationCallback(0);
Doris Liu13351992017-01-17 17:10:42 -08001193
1194 if (mSeekState.getPlayTimeNormalized() == 0 && mReversing) {
1195 // Maintain old behavior, if seeked to 0 then call reverse, we'll treat the case
1196 // the same as no seeking at all.
1197 mSeekState.reset();
1198 }
1199 // Set the child animators to the right end:
1200 if (mShouldResetValuesAtStart) {
Doris Liu6d452092017-02-08 14:47:08 -08001201 if (isInitialized()) {
1202 skipToEndValue(!mReversing);
1203 } else if (mReversing) {
1204 // Reversing but haven't initialized all the children yet.
1205 initChildren();
Doris Liu13351992017-01-17 17:10:42 -08001206 skipToEndValue(!mReversing);
1207 } else {
1208 // If not all children are initialized and play direction is forward
1209 for (int i = mEvents.size() - 1; i >= 0; i--) {
1210 if (mEvents.get(i).mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
1211 Animator anim = mEvents.get(i).mNode.mAnimation;
1212 // Only reset the animations that have been initialized to start value,
1213 // so that if they are defined without a start value, they will get the
1214 // values set at the right time (i.e. the next animation run)
1215 if (anim.isInitialized()) {
1216 anim.skipToEndValue(true);
1217 }
1218 }
1219 }
1220 }
1221 }
1222
1223 if (mReversing || mStartDelay == 0 || mSeekState.isActive()) {
1224 long playTime;
1225 // If no delay, we need to call start on the first animations to be consistent with old
1226 // behavior.
1227 if (mSeekState.isActive()) {
1228 mSeekState.updateSeekDirection(mReversing);
1229 playTime = mSeekState.getPlayTime();
1230 } else {
1231 playTime = 0;
1232 }
1233 int toId = findLatestEventIdForTime(playTime);
1234 handleAnimationEvents(-1, toId, playTime);
Doris Liu6d452092017-02-08 14:47:08 -08001235 for (int i = mPlayingSet.size() - 1; i >= 0; i--) {
1236 if (mPlayingSet.get(i).mEnded) {
1237 mPlayingSet.remove(i);
1238 }
1239 }
Doris Liu13351992017-01-17 17:10:42 -08001240 mLastEventId = toId;
1241 }
1242 }
1243
Doris Liu66c564e2017-01-26 11:53:46 -08001244 // This is to work around the issue in b/34736819, as the old behavior in AnimatorSet had
1245 // masked a real bug in play movies. TODO: remove this and below once the root cause is fixed.
1246 private void addDummyListener() {
1247 for (int i = 1; i < mNodes.size(); i++) {
1248 mNodes.get(i).mAnimation.addListener(mDummyListener);
1249 }
1250 }
1251
1252 private void removeDummyListener() {
1253 for (int i = 1; i < mNodes.size(); i++) {
1254 mNodes.get(i).mAnimation.removeListener(mDummyListener);
1255 }
1256 }
1257
Doris Liu13351992017-01-17 17:10:42 -08001258 private int findLatestEventIdForTime(long currentPlayTime) {
1259 int size = mEvents.size();
1260 int latestId = mLastEventId;
1261 // Call start on the first animations now to be consistent with the old behavior
1262 if (mReversing) {
1263 currentPlayTime = getTotalDuration() - currentPlayTime;
1264 mLastEventId = mLastEventId == -1 ? size : mLastEventId;
1265 for (int j = mLastEventId - 1; j >= 0; j--) {
1266 AnimationEvent event = mEvents.get(j);
1267 if (event.getTime() >= currentPlayTime) {
1268 latestId = j;
1269 }
1270 }
1271 } else {
1272 for (int i = mLastEventId + 1; i < size; i++) {
1273 AnimationEvent event = mEvents.get(i);
Doris Liu29027162017-09-26 17:00:54 -07001274 // TODO: need a function that accounts for infinite duration to compare time
1275 if (event.getTime() != DURATION_INFINITE && event.getTime() <= currentPlayTime) {
Doris Liu13351992017-01-17 17:10:42 -08001276 latestId = i;
1277 }
1278 }
1279 }
1280 return latestId;
1281 }
1282
1283 private void endAnimation() {
1284 mStarted = false;
1285 mLastFrameTime = -1;
1286 mFirstFrame = -1;
1287 mLastEventId = -1;
1288 mPaused = false;
1289 mPauseTime = -1;
1290 mSeekState.reset();
1291 mPlayingSet.clear();
1292
1293 // No longer receive callbacks
1294 removeAnimationCallback();
1295 // Call end listener
1296 if (mListeners != null) {
1297 ArrayList<AnimatorListener> tmpListeners =
1298 (ArrayList<AnimatorListener>) mListeners.clone();
1299 int numListeners = tmpListeners.size();
1300 for (int i = 0; i < numListeners; ++i) {
1301 tmpListeners.get(i).onAnimationEnd(this, mReversing);
1302 }
1303 }
Doris Liu66c564e2017-01-26 11:53:46 -08001304 removeDummyListener();
Doris Liu13351992017-01-17 17:10:42 -08001305 mSelfPulse = true;
1306 mReversing = false;
1307 }
1308
1309 private void removeAnimationCallback() {
1310 if (!mSelfPulse) {
1311 return;
1312 }
1313 AnimationHandler handler = AnimationHandler.getInstance();
1314 handler.removeCallback(this);
1315 }
1316
1317 private void addAnimationCallback(long delay) {
1318 if (!mSelfPulse) {
1319 return;
1320 }
1321 AnimationHandler handler = AnimationHandler.getInstance();
1322 handler.addAnimationFrameCallback(this, delay);
Doris Liu13099142015-07-10 17:32:41 -07001323 }
1324
Chet Haase49afa5b2010-08-23 11:39:53 -07001325 @Override
Chet Haasea18a86b2010-09-07 13:20:00 -07001326 public AnimatorSet clone() {
1327 final AnimatorSet anim = (AnimatorSet) super.clone();
Chet Haase49afa5b2010-08-23 11:39:53 -07001328 /*
1329 * The basic clone() operation copies all items. This doesn't work very well for
Chet Haasea18a86b2010-09-07 13:20:00 -07001330 * AnimatorSet, because it will copy references that need to be recreated and state
Chet Haase49afa5b2010-08-23 11:39:53 -07001331 * that may not apply. What we need to do now is put the clone in an uninitialized
1332 * state, with fresh, empty data structures. Then we will build up the nodes list
1333 * manually, as we clone each Node (and its animation). The clone will then be sorted,
1334 * and will populate any appropriate lists, when it is started.
1335 */
Yigit Boyard422dc32014-09-25 12:23:35 -07001336 final int nodeCount = mNodes.size();
Chet Haase8b699792011-08-05 15:20:19 -07001337 anim.mStarted = false;
Doris Liu13351992017-01-17 17:10:42 -08001338 anim.mLastFrameTime = -1;
1339 anim.mFirstFrame = -1;
1340 anim.mLastEventId = -1;
1341 anim.mPaused = false;
1342 anim.mPauseTime = -1;
1343 anim.mSeekState = new SeekState();
1344 anim.mSelfPulse = true;
1345 anim.mPlayingSet = new ArrayList<Node>();
Doris Liud7444422015-05-11 13:23:31 -07001346 anim.mNodeMap = new ArrayMap<Animator, Node>();
Yigit Boyard422dc32014-09-25 12:23:35 -07001347 anim.mNodes = new ArrayList<Node>(nodeCount);
Doris Liu13351992017-01-17 17:10:42 -08001348 anim.mEvents = new ArrayList<AnimationEvent>();
Doris Liu6ba5ed32017-01-26 18:50:04 -08001349 anim.mDummyListener = new AnimatorListenerAdapter() {
1350 @Override
1351 public void onAnimationEnd(Animator animation) {
1352 if (anim.mNodeMap.get(animation) == null) {
1353 throw new AndroidRuntimeException("Error: animation ended is not in the node"
1354 + " map");
1355 }
1356 anim.mNodeMap.get(animation).mEnded = true;
1357
1358 }
1359 };
Doris Liu13351992017-01-17 17:10:42 -08001360 anim.mReversing = false;
1361 anim.mDependencyDirty = true;
Chet Haase49afa5b2010-08-23 11:39:53 -07001362
1363 // Walk through the old nodes list, cloning each node and adding it to the new nodemap.
Chet Haasea18a86b2010-09-07 13:20:00 -07001364 // One problem is that the old node dependencies point to nodes in the old AnimatorSet.
Chet Haase49afa5b2010-08-23 11:39:53 -07001365 // We need to track the old/new nodes in order to reconstruct the dependencies in the clone.
Yigit Boyard422dc32014-09-25 12:23:35 -07001366
Doris Liu56b2df02017-05-11 16:50:53 -07001367 HashMap<Node, Node> clonesMap = new HashMap<>(nodeCount);
Yigit Boyard422dc32014-09-25 12:23:35 -07001368 for (int n = 0; n < nodeCount; n++) {
1369 final Node node = mNodes.get(n);
Chet Haase49afa5b2010-08-23 11:39:53 -07001370 Node nodeClone = node.clone();
Doris Liuc4575472017-05-12 18:32:54 -07001371 // Remove the old internal listener from the cloned child
1372 nodeClone.mAnimation.removeListener(mDummyListener);
Doris Liu56b2df02017-05-11 16:50:53 -07001373 clonesMap.put(node, nodeClone);
Chet Haase49afa5b2010-08-23 11:39:53 -07001374 anim.mNodes.add(nodeClone);
Doris Liu13099142015-07-10 17:32:41 -07001375 anim.mNodeMap.put(nodeClone.mAnimation, nodeClone);
Chet Haase49afa5b2010-08-23 11:39:53 -07001376 }
Doris Liu13099142015-07-10 17:32:41 -07001377
Doris Liu56b2df02017-05-11 16:50:53 -07001378 anim.mRootNode = clonesMap.get(mRootNode);
Doris Liu13099142015-07-10 17:32:41 -07001379 anim.mDelayAnim = (ValueAnimator) anim.mRootNode.mAnimation;
1380
Chet Haase49afa5b2010-08-23 11:39:53 -07001381 // Now that we've cloned all of the nodes, we're ready to walk through their
1382 // dependencies, mapping the old dependencies to the new nodes
Doris Liu13099142015-07-10 17:32:41 -07001383 for (int i = 0; i < nodeCount; i++) {
1384 Node node = mNodes.get(i);
1385 // Update dependencies for node's clone
Doris Liu56b2df02017-05-11 16:50:53 -07001386 Node nodeClone = clonesMap.get(node);
1387 nodeClone.mLatestParent = node.mLatestParent == null
1388 ? null : clonesMap.get(node.mLatestParent);
Doris Liu13099142015-07-10 17:32:41 -07001389 int size = node.mChildNodes == null ? 0 : node.mChildNodes.size();
1390 for (int j = 0; j < size; j++) {
Doris Liu56b2df02017-05-11 16:50:53 -07001391 nodeClone.mChildNodes.set(j, clonesMap.get(node.mChildNodes.get(j)));
Yigit Boyard422dc32014-09-25 12:23:35 -07001392 }
Doris Liu13099142015-07-10 17:32:41 -07001393 size = node.mSiblings == null ? 0 : node.mSiblings.size();
1394 for (int j = 0; j < size; j++) {
Doris Liu56b2df02017-05-11 16:50:53 -07001395 nodeClone.mSiblings.set(j, clonesMap.get(node.mSiblings.get(j)));
Yigit Boyard422dc32014-09-25 12:23:35 -07001396 }
Doris Liu13099142015-07-10 17:32:41 -07001397 size = node.mParents == null ? 0 : node.mParents.size();
1398 for (int j = 0; j < size; j++) {
Doris Liu56b2df02017-05-11 16:50:53 -07001399 nodeClone.mParents.set(j, clonesMap.get(node.mParents.get(j)));
Chet Haase49afa5b2010-08-23 11:39:53 -07001400 }
1401 }
Chet Haase49afa5b2010-08-23 11:39:53 -07001402 return anim;
1403 }
1404
Chet Haase17fb4b02010-06-28 17:55:07 -07001405
Chet Haase17fb4b02010-06-28 17:55:07 -07001406 /**
Doris Liuc4bb1852016-02-19 21:39:21 +00001407 * AnimatorSet is only reversible when the set contains no sequential animation, and no child
1408 * animators have a start delay.
ztenghui7bc6a3f2014-07-15 15:12:12 -07001409 * @hide
1410 */
1411 @Override
1412 public boolean canReverse() {
Doris Liu13351992017-01-17 17:10:42 -08001413 return getTotalDuration() != DURATION_INFINITE;
ztenghui7bc6a3f2014-07-15 15:12:12 -07001414 }
1415
1416 /**
Doris Liu13351992017-01-17 17:10:42 -08001417 * Plays the AnimatorSet in reverse. If the animation has been seeked to a specific play time
1418 * using {@link #setCurrentPlayTime(long)}, it will play backwards from the point seeked when
1419 * reverse was called. Otherwise, then it will start from the end and play backwards. This
1420 * behavior is only set for the current animation; future playing of the animation will use the
1421 * default behavior of playing forward.
1422 * <p>
1423 * Note: reverse is not supported for infinite AnimatorSet.
ztenghui7bc6a3f2014-07-15 15:12:12 -07001424 */
1425 @Override
1426 public void reverse() {
Doris Liu13351992017-01-17 17:10:42 -08001427 start(true, true);
ztenghui7bc6a3f2014-07-15 15:12:12 -07001428 }
1429
Chet Haased4307532014-12-01 06:32:38 -08001430 @Override
1431 public String toString() {
1432 String returnVal = "AnimatorSet@" + Integer.toHexString(hashCode()) + "{";
Doris Liu13099142015-07-10 17:32:41 -07001433 int size = mNodes.size();
1434 for (int i = 0; i < size; i++) {
1435 Node node = mNodes.get(i);
1436 returnVal += "\n " + node.mAnimation.toString();
Chet Haased4307532014-12-01 06:32:38 -08001437 }
1438 return returnVal + "\n}";
1439 }
1440
Doris Liu13099142015-07-10 17:32:41 -07001441 private void printChildCount() {
1442 // Print out the child count through a level traverse.
1443 ArrayList<Node> list = new ArrayList<>(mNodes.size());
1444 list.add(mRootNode);
1445 Log.d(TAG, "Current tree: ");
1446 int index = 0;
1447 while (index < list.size()) {
1448 int listSize = list.size();
1449 StringBuilder builder = new StringBuilder();
1450 for (; index < listSize; index++) {
1451 Node node = list.get(index);
1452 int num = 0;
1453 if (node.mChildNodes != null) {
1454 for (int i = 0; i < node.mChildNodes.size(); i++) {
1455 Node child = node.mChildNodes.get(i);
1456 if (child.mLatestParent == node) {
1457 num++;
1458 list.add(child);
1459 }
1460 }
1461 }
1462 builder.append(" ");
1463 builder.append(num);
1464 }
1465 Log.d(TAG, builder.toString());
Chet Haase17fb4b02010-06-28 17:55:07 -07001466 }
1467 }
1468
Doris Liu13099142015-07-10 17:32:41 -07001469 private void createDependencyGraph() {
1470 if (!mDependencyDirty) {
Doris Liu49db4242015-08-10 13:33:06 -07001471 // Check whether any duration of the child animations has changed
1472 boolean durationChanged = false;
1473 for (int i = 0; i < mNodes.size(); i++) {
1474 Animator anim = mNodes.get(i).mAnimation;
1475 if (mNodes.get(i).mTotalDuration != anim.getTotalDuration()) {
1476 durationChanged = true;
1477 break;
1478 }
1479 }
1480 if (!durationChanged) {
1481 return;
1482 }
Doris Liu13099142015-07-10 17:32:41 -07001483 }
1484
Doris Liu13099142015-07-10 17:32:41 -07001485 mDependencyDirty = false;
1486 // Traverse all the siblings and make sure they have all the parents
1487 int size = mNodes.size();
1488 for (int i = 0; i < size; i++) {
1489 mNodes.get(i).mParentsAdded = false;
1490 }
1491 for (int i = 0; i < size; i++) {
1492 Node node = mNodes.get(i);
1493 if (node.mParentsAdded) {
1494 continue;
1495 }
1496
1497 node.mParentsAdded = true;
1498 if (node.mSiblings == null) {
1499 continue;
1500 }
1501
1502 // Find all the siblings
1503 findSiblings(node, node.mSiblings);
1504 node.mSiblings.remove(node);
1505
1506 // Get parents from all siblings
1507 int siblingSize = node.mSiblings.size();
1508 for (int j = 0; j < siblingSize; j++) {
1509 node.addParents(node.mSiblings.get(j).mParents);
1510 }
1511
1512 // Now make sure all siblings share the same set of parents
1513 for (int j = 0; j < siblingSize; j++) {
1514 Node sibling = node.mSiblings.get(j);
1515 sibling.addParents(node.mParents);
1516 sibling.mParentsAdded = true;
1517 }
1518 }
1519
1520 for (int i = 0; i < size; i++) {
1521 Node node = mNodes.get(i);
1522 if (node != mRootNode && node.mParents == null) {
1523 node.addParent(mRootNode);
1524 }
1525 }
1526
1527 // Do a DFS on the tree
1528 ArrayList<Node> visited = new ArrayList<Node>(mNodes.size());
1529 // Assign start/end time
1530 mRootNode.mStartTime = 0;
1531 mRootNode.mEndTime = mDelayAnim.getDuration();
1532 updatePlayTime(mRootNode, visited);
1533
Doris Liu13351992017-01-17 17:10:42 -08001534 sortAnimationEvents();
1535 mTotalDuration = mEvents.get(mEvents.size() - 1).getTime();
1536 }
1537
1538 private void sortAnimationEvents() {
1539 // Sort the list of events in ascending order of their time
1540 // Create the list including the delay animation.
1541 mEvents.clear();
Doris Liu40e84692017-01-25 17:10:38 -08001542 for (int i = 1; i < mNodes.size(); i++) {
Doris Liu13099142015-07-10 17:32:41 -07001543 Node node = mNodes.get(i);
Doris Liu13351992017-01-17 17:10:42 -08001544 mEvents.add(new AnimationEvent(node, AnimationEvent.ANIMATION_START));
1545 mEvents.add(new AnimationEvent(node, AnimationEvent.ANIMATION_DELAY_ENDED));
1546 mEvents.add(new AnimationEvent(node, AnimationEvent.ANIMATION_END));
Doris Liu13099142015-07-10 17:32:41 -07001547 }
Doris Liu13351992017-01-17 17:10:42 -08001548 mEvents.sort(new Comparator<AnimationEvent>() {
1549 @Override
1550 public int compare(AnimationEvent e1, AnimationEvent e2) {
1551 long t1 = e1.getTime();
1552 long t2 = e2.getTime();
1553 if (t1 == t2) {
Doris Liu40e84692017-01-25 17:10:38 -08001554 // For events that happen at the same time, we need them to be in the sequence
1555 // (end, start, start delay ended)
1556 if (e2.mEvent + e1.mEvent == AnimationEvent.ANIMATION_START
1557 + AnimationEvent.ANIMATION_DELAY_ENDED) {
1558 // Ensure start delay happens after start
Doris Liu13351992017-01-17 17:10:42 -08001559 return e1.mEvent - e2.mEvent;
Doris Liu40e84692017-01-25 17:10:38 -08001560 } else {
1561 return e2.mEvent - e1.mEvent;
Doris Liu13351992017-01-17 17:10:42 -08001562 }
Doris Liu13351992017-01-17 17:10:42 -08001563 }
1564 if (t2 == DURATION_INFINITE) {
1565 return -1;
1566 }
1567 if (t1 == DURATION_INFINITE) {
1568 return 1;
1569 }
1570 // When neither event happens at INFINITE time:
1571 return (int) (t1 - t2);
1572 }
1573 });
1574
Doris Liu40e84692017-01-25 17:10:38 -08001575 int eventSize = mEvents.size();
1576 // For the same animation, start event has to happen before end.
1577 for (int i = 0; i < eventSize;) {
1578 AnimationEvent event = mEvents.get(i);
1579 if (event.mEvent == AnimationEvent.ANIMATION_END) {
1580 boolean needToSwapStart;
1581 if (event.mNode.mStartTime == event.mNode.mEndTime) {
1582 needToSwapStart = true;
1583 } else if (event.mNode.mEndTime == event.mNode.mStartTime
1584 + event.mNode.mAnimation.getStartDelay()) {
1585 // Swapping start delay
1586 needToSwapStart = false;
1587 } else {
1588 i++;
1589 continue;
1590 }
1591
1592 int startEventId = eventSize;
1593 int startDelayEndId = eventSize;
1594 for (int j = i + 1; j < eventSize; j++) {
1595 if (startEventId < eventSize && startDelayEndId < eventSize) {
1596 break;
1597 }
1598 if (mEvents.get(j).mNode == event.mNode) {
1599 if (mEvents.get(j).mEvent == AnimationEvent.ANIMATION_START) {
1600 // Found start event
1601 startEventId = j;
1602 } else if (mEvents.get(j).mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
1603 startDelayEndId = j;
1604 }
1605 }
1606
1607 }
1608 if (needToSwapStart && startEventId == mEvents.size()) {
1609 throw new UnsupportedOperationException("Something went wrong, no start is"
1610 + "found after stop for an animation that has the same start and end"
1611 + "time.");
1612
1613 }
1614 if (startDelayEndId == mEvents.size()) {
1615 throw new UnsupportedOperationException("Something went wrong, no start"
1616 + "delay end is found after stop for an animation");
1617
1618 }
1619
1620 // We need to make sure start is inserted before start delay ended event,
1621 // because otherwise inserting start delay ended events first would change
1622 // the start event index.
1623 if (needToSwapStart) {
1624 AnimationEvent startEvent = mEvents.remove(startEventId);
1625 mEvents.add(i, startEvent);
1626 i++;
1627 }
1628
1629 AnimationEvent startDelayEndEvent = mEvents.remove(startDelayEndId);
1630 mEvents.add(i, startDelayEndEvent);
1631 i += 2;
1632 } else {
1633 i++;
1634 }
1635 }
1636
1637 if (!mEvents.isEmpty() && mEvents.get(0).mEvent != AnimationEvent.ANIMATION_START) {
1638 throw new UnsupportedOperationException(
1639 "Sorting went bad, the start event should always be at index 0");
1640 }
1641
1642 // Add AnimatorSet's start delay node to the beginning
1643 mEvents.add(0, new AnimationEvent(mRootNode, AnimationEvent.ANIMATION_START));
1644 mEvents.add(1, new AnimationEvent(mRootNode, AnimationEvent.ANIMATION_DELAY_ENDED));
1645 mEvents.add(2, new AnimationEvent(mRootNode, AnimationEvent.ANIMATION_END));
1646
Doris Liu13351992017-01-17 17:10:42 -08001647 if (mEvents.get(mEvents.size() - 1).mEvent == AnimationEvent.ANIMATION_START
1648 || mEvents.get(mEvents.size() - 1).mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
1649 throw new UnsupportedOperationException(
1650 "Something went wrong, the last event is not an end event");
1651 }
Doris Liu13099142015-07-10 17:32:41 -07001652 }
1653
1654 /**
1655 * Based on parent's start/end time, calculate children's start/end time. If cycle exists in
1656 * the graph, all the nodes on the cycle will be marked to start at {@link #DURATION_INFINITE},
1657 * meaning they will ever play.
1658 */
1659 private void updatePlayTime(Node parent, ArrayList<Node> visited) {
1660 if (parent.mChildNodes == null) {
Doris Liu49db4242015-08-10 13:33:06 -07001661 if (parent == mRootNode) {
1662 // All the animators are in a cycle
1663 for (int i = 0; i < mNodes.size(); i++) {
1664 Node node = mNodes.get(i);
1665 if (node != mRootNode) {
1666 node.mStartTime = DURATION_INFINITE;
1667 node.mEndTime = DURATION_INFINITE;
1668 }
1669 }
1670 }
Doris Liu13099142015-07-10 17:32:41 -07001671 return;
1672 }
1673
1674 visited.add(parent);
1675 int childrenSize = parent.mChildNodes.size();
1676 for (int i = 0; i < childrenSize; i++) {
1677 Node child = parent.mChildNodes.get(i);
Philip Quinn7fb80f72017-05-15 09:44:30 -07001678 child.mTotalDuration = child.mAnimation.getTotalDuration(); // Update cached duration.
1679
Doris Liu13099142015-07-10 17:32:41 -07001680 int index = visited.indexOf(child);
1681 if (index >= 0) {
1682 // Child has been visited, cycle found. Mark all the nodes in the cycle.
Doris Liu49db4242015-08-10 13:33:06 -07001683 for (int j = index; j < visited.size(); j++) {
Doris Liu13099142015-07-10 17:32:41 -07001684 visited.get(j).mLatestParent = null;
1685 visited.get(j).mStartTime = DURATION_INFINITE;
1686 visited.get(j).mEndTime = DURATION_INFINITE;
1687 }
1688 child.mStartTime = DURATION_INFINITE;
1689 child.mEndTime = DURATION_INFINITE;
1690 child.mLatestParent = null;
1691 Log.w(TAG, "Cycle found in AnimatorSet: " + this);
1692 continue;
1693 }
1694
1695 if (child.mStartTime != DURATION_INFINITE) {
1696 if (parent.mEndTime == DURATION_INFINITE) {
1697 child.mLatestParent = parent;
1698 child.mStartTime = DURATION_INFINITE;
1699 child.mEndTime = DURATION_INFINITE;
1700 } else {
1701 if (parent.mEndTime >= child.mStartTime) {
1702 child.mLatestParent = parent;
1703 child.mStartTime = parent.mEndTime;
1704 }
1705
Philip Quinn7fb80f72017-05-15 09:44:30 -07001706 child.mEndTime = child.mTotalDuration == DURATION_INFINITE
1707 ? DURATION_INFINITE : child.mStartTime + child.mTotalDuration;
Doris Liu13099142015-07-10 17:32:41 -07001708 }
1709 }
1710 updatePlayTime(child, visited);
1711 }
1712 visited.remove(parent);
1713 }
1714
1715 // Recursively find all the siblings
1716 private void findSiblings(Node node, ArrayList<Node> siblings) {
1717 if (!siblings.contains(node)) {
1718 siblings.add(node);
1719 if (node.mSiblings == null) {
1720 return;
1721 }
1722 for (int i = 0; i < node.mSiblings.size(); i++) {
1723 findSiblings(node.mSiblings.get(i), siblings);
1724 }
1725 }
1726 }
1727
Doris Liu766431a2016-02-04 22:17:11 +00001728 /**
1729 * @hide
1730 * TODO: For animatorSet defined in XML, we can use a flag to indicate what the play order
Doris Liuc4704662016-06-23 10:34:17 -07001731 * if defined (i.e. sequential or together), then we can use the flag instead of calculating
1732 * dynamically. Note that when AnimatorSet is empty this method returns true.
Doris Liu766431a2016-02-04 22:17:11 +00001733 * @return whether all the animators in the set are supposed to play together
1734 */
1735 public boolean shouldPlayTogether() {
1736 updateAnimatorsDuration();
1737 createDependencyGraph();
1738 // All the child nodes are set out to play right after the delay animation
Doris Liuc4704662016-06-23 10:34:17 -07001739 return mRootNode.mChildNodes == null || mRootNode.mChildNodes.size() == mNodes.size() - 1;
Doris Liu766431a2016-02-04 22:17:11 +00001740 }
1741
Doris Liu13099142015-07-10 17:32:41 -07001742 @Override
1743 public long getTotalDuration() {
1744 updateAnimatorsDuration();
1745 createDependencyGraph();
1746 return mTotalDuration;
1747 }
1748
1749 private Node getNodeForAnimation(Animator anim) {
1750 Node node = mNodeMap.get(anim);
1751 if (node == null) {
1752 node = new Node(anim);
1753 mNodeMap.put(anim, node);
1754 mNodes.add(node);
1755 }
1756 return node;
1757 }
1758
Chet Haase17fb4b02010-06-28 17:55:07 -07001759 /**
Chet Haasea18a86b2010-09-07 13:20:00 -07001760 * A Node is an embodiment of both the Animator that it wraps as well as
Chet Haase17fb4b02010-06-28 17:55:07 -07001761 * any dependencies that are associated with that Animation. This includes
1762 * both dependencies upon other nodes (in the dependencies list) as
1763 * well as dependencies of other nodes upon this (in the nodeDependents list).
1764 */
Chet Haase49afa5b2010-08-23 11:39:53 -07001765 private static class Node implements Cloneable {
Doris Liu13099142015-07-10 17:32:41 -07001766 Animator mAnimation;
Chet Haase17fb4b02010-06-28 17:55:07 -07001767
1768 /**
Doris Liu13099142015-07-10 17:32:41 -07001769 * Child nodes are the nodes associated with animations that will be played immediately
1770 * after current node.
Chet Haase17fb4b02010-06-28 17:55:07 -07001771 */
Doris Liu13099142015-07-10 17:32:41 -07001772 ArrayList<Node> mChildNodes = null;
Chet Haase1e0ac5a2010-08-27 08:32:11 -07001773
1774 /**
Doris Liu13099142015-07-10 17:32:41 -07001775 * Flag indicating whether the animation in this node is finished. This flag
1776 * is used by AnimatorSet to check, as each animation ends, whether all child animations
1777 * are mEnded and it's time to send out an end event for the entire AnimatorSet.
1778 */
1779 boolean mEnded = false;
1780
1781 /**
1782 * Nodes with animations that are defined to play simultaneously with the animation
1783 * associated with this current node.
1784 */
1785 ArrayList<Node> mSiblings;
1786
1787 /**
1788 * Parent nodes are the nodes with animations preceding current node's animation. Parent
1789 * nodes here are derived from user defined animation sequence.
1790 */
1791 ArrayList<Node> mParents;
1792
1793 /**
1794 * Latest parent is the parent node associated with a animation that finishes after all
1795 * the other parents' animations.
1796 */
1797 Node mLatestParent = null;
1798
1799 boolean mParentsAdded = false;
1800 long mStartTime = 0;
1801 long mEndTime = 0;
Doris Liu49db4242015-08-10 13:33:06 -07001802 long mTotalDuration = 0;
Doris Liu13099142015-07-10 17:32:41 -07001803
1804 /**
Chet Haase17fb4b02010-06-28 17:55:07 -07001805 * Constructs the Node with the animation that it encapsulates. A Node has no
1806 * dependencies by default; dependencies are added via the addDependency()
1807 * method.
1808 *
1809 * @param animation The animation that the Node encapsulates.
1810 */
Chet Haasea18a86b2010-09-07 13:20:00 -07001811 public Node(Animator animation) {
Doris Liu13099142015-07-10 17:32:41 -07001812 this.mAnimation = animation;
Chet Haase17fb4b02010-06-28 17:55:07 -07001813 }
Chet Haase49afa5b2010-08-23 11:39:53 -07001814
1815 @Override
Chet Haase21cd1382010-09-01 17:42:29 -07001816 public Node clone() {
1817 try {
1818 Node node = (Node) super.clone();
Doris Liu13099142015-07-10 17:32:41 -07001819 node.mAnimation = mAnimation.clone();
1820 if (mChildNodes != null) {
1821 node.mChildNodes = new ArrayList<>(mChildNodes);
1822 }
Doris Liu5e666162015-08-03 16:06:56 -07001823 if (mSiblings != null) {
1824 node.mSiblings = new ArrayList<>(mSiblings);
1825 }
1826 if (mParents != null) {
1827 node.mParents = new ArrayList<>(mParents);
1828 }
Doris Liu13099142015-07-10 17:32:41 -07001829 node.mEnded = false;
Chet Haase21cd1382010-09-01 17:42:29 -07001830 return node;
1831 } catch (CloneNotSupportedException e) {
1832 throw new AssertionError();
1833 }
Chet Haase49afa5b2010-08-23 11:39:53 -07001834 }
Doris Liu13099142015-07-10 17:32:41 -07001835
1836 void addChild(Node node) {
1837 if (mChildNodes == null) {
1838 mChildNodes = new ArrayList<>();
1839 }
1840 if (!mChildNodes.contains(node)) {
1841 mChildNodes.add(node);
1842 node.addParent(this);
1843 }
1844 }
1845
1846 public void addSibling(Node node) {
1847 if (mSiblings == null) {
1848 mSiblings = new ArrayList<Node>();
1849 }
1850 if (!mSiblings.contains(node)) {
1851 mSiblings.add(node);
1852 node.addSibling(this);
1853 }
1854 }
1855
1856 public void addParent(Node node) {
1857 if (mParents == null) {
1858 mParents = new ArrayList<Node>();
1859 }
1860 if (!mParents.contains(node)) {
1861 mParents.add(node);
1862 node.addChild(this);
1863 }
1864 }
1865
1866 public void addParents(ArrayList<Node> parents) {
1867 if (parents == null) {
1868 return;
1869 }
1870 int size = parents.size();
1871 for (int i = 0; i < size; i++) {
1872 addParent(parents.get(i));
1873 }
1874 }
Chet Haase17fb4b02010-06-28 17:55:07 -07001875 }
1876
1877 /**
Doris Liu13351992017-01-17 17:10:42 -08001878 * This class is a wrapper around a node and an event for the animation corresponding to the
1879 * node. The 3 types of events represent the start of an animation, the end of a start delay of
1880 * an animation, and the end of an animation. When playing forward (i.e. in the non-reverse
1881 * direction), start event marks when start() should be called, and end event corresponds to
1882 * when the animation should finish. When playing in reverse, start delay will not be a part
1883 * of the animation. Therefore, reverse() is called at the end event, and animation should end
1884 * at the delay ended event.
1885 */
1886 private static class AnimationEvent {
1887 static final int ANIMATION_START = 0;
1888 static final int ANIMATION_DELAY_ENDED = 1;
1889 static final int ANIMATION_END = 2;
1890 final Node mNode;
1891 final int mEvent;
1892
1893 AnimationEvent(Node node, int event) {
1894 mNode = node;
1895 mEvent = event;
1896 }
1897
1898 long getTime() {
1899 if (mEvent == ANIMATION_START) {
1900 return mNode.mStartTime;
1901 } else if (mEvent == ANIMATION_DELAY_ENDED) {
Doris Liue8a5d8d2017-02-14 11:01:45 -08001902 return mNode.mStartTime == DURATION_INFINITE
Doris Liuc9b50342017-02-10 17:48:17 -08001903 ? DURATION_INFINITE : mNode.mStartTime + mNode.mAnimation.getStartDelay();
Doris Liu13351992017-01-17 17:10:42 -08001904 } else {
1905 return mNode.mEndTime;
1906 }
1907 }
1908
1909 public String toString() {
1910 String eventStr = mEvent == ANIMATION_START ? "start" : (
1911 mEvent == ANIMATION_DELAY_ENDED ? "delay ended" : "end");
1912 return eventStr + " " + mNode.mAnimation.toString();
1913 }
1914 }
1915
1916 private class SeekState {
1917 private long mPlayTime = -1;
1918 private boolean mSeekingInReverse = false;
1919 void reset() {
1920 mPlayTime = -1;
1921 mSeekingInReverse = false;
1922 }
1923
1924 void setPlayTime(long playTime, boolean inReverse) {
1925 // TODO: This can be simplified.
1926
1927 // Clamp the play time
1928 if (getTotalDuration() != DURATION_INFINITE) {
1929 mPlayTime = Math.min(playTime, getTotalDuration() - mStartDelay);
1930 }
1931 mPlayTime = Math.max(0, mPlayTime);
1932 mSeekingInReverse = inReverse;
1933 }
1934
1935 void updateSeekDirection(boolean inReverse) {
1936 // Change seek direction without changing the overall fraction
1937 if (inReverse && getTotalDuration() == DURATION_INFINITE) {
1938 throw new UnsupportedOperationException("Error: Cannot reverse infinite animator"
1939 + " set");
1940 }
1941 if (mPlayTime >= 0) {
1942 if (inReverse != mSeekingInReverse) {
1943 mPlayTime = getTotalDuration() - mStartDelay - mPlayTime;
Doris Liu6d452092017-02-08 14:47:08 -08001944 mSeekingInReverse = inReverse;
Doris Liu13351992017-01-17 17:10:42 -08001945 }
1946 }
1947 }
1948
1949 long getPlayTime() {
1950 return mPlayTime;
1951 }
1952
1953 /**
1954 * Returns the playtime assuming the animation is forward playing
1955 */
1956 long getPlayTimeNormalized() {
1957 if (mReversing) {
1958 return getTotalDuration() - mStartDelay - mPlayTime;
1959 }
1960 return mPlayTime;
1961 }
1962
1963 boolean isActive() {
1964 return mPlayTime != -1;
1965 }
1966 }
1967
1968 /**
Chet Haase17fb4b02010-06-28 17:55:07 -07001969 * The <code>Builder</code> object is a utility class to facilitate adding animations to a
Chet Haasea18a86b2010-09-07 13:20:00 -07001970 * <code>AnimatorSet</code> along with the relationships between the various animations. The
Chet Haase17fb4b02010-06-28 17:55:07 -07001971 * intention of the <code>Builder</code> methods, along with the {@link
Chet Haase8b699792011-08-05 15:20:19 -07001972 * AnimatorSet#play(Animator) play()} method of <code>AnimatorSet</code> is to make it possible
1973 * to express the dependency relationships of animations in a natural way. Developers can also
1974 * use the {@link AnimatorSet#playTogether(Animator[]) playTogether()} and {@link
Chet Haasea18a86b2010-09-07 13:20:00 -07001975 * AnimatorSet#playSequentially(Animator[]) playSequentially()} methods if these suit the need,
1976 * but it might be easier in some situations to express the AnimatorSet of animations in pairs.
Chet Haase17fb4b02010-06-28 17:55:07 -07001977 * <p/>
1978 * <p>The <code>Builder</code> object cannot be constructed directly, but is rather constructed
Chet Haasea18a86b2010-09-07 13:20:00 -07001979 * internally via a call to {@link AnimatorSet#play(Animator)}.</p>
Chet Haase17fb4b02010-06-28 17:55:07 -07001980 * <p/>
Chet Haasea18a86b2010-09-07 13:20:00 -07001981 * <p>For example, this sets up a AnimatorSet to play anim1 and anim2 at the same time, anim3 to
Chet Haase17fb4b02010-06-28 17:55:07 -07001982 * play when anim2 finishes, and anim4 to play when anim3 finishes:</p>
1983 * <pre>
Chet Haasea18a86b2010-09-07 13:20:00 -07001984 * AnimatorSet s = new AnimatorSet();
Chet Haase17fb4b02010-06-28 17:55:07 -07001985 * s.play(anim1).with(anim2);
1986 * s.play(anim2).before(anim3);
1987 * s.play(anim4).after(anim3);
1988 * </pre>
1989 * <p/>
Chet Haasea18a86b2010-09-07 13:20:00 -07001990 * <p>Note in the example that both {@link Builder#before(Animator)} and {@link
1991 * Builder#after(Animator)} are used. These are just different ways of expressing the same
Chet Haase17fb4b02010-06-28 17:55:07 -07001992 * relationship and are provided to make it easier to say things in a way that is more natural,
1993 * depending on the situation.</p>
1994 * <p/>
1995 * <p>It is possible to make several calls into the same <code>Builder</code> object to express
1996 * multiple relationships. However, note that it is only the animation passed into the initial
Chet Haasea18a86b2010-09-07 13:20:00 -07001997 * {@link AnimatorSet#play(Animator)} method that is the dependency in any of the successive
Chet Haase17fb4b02010-06-28 17:55:07 -07001998 * calls to the <code>Builder</code> object. For example, the following code starts both anim2
1999 * and anim3 when anim1 ends; there is no direct dependency relationship between anim2 and
2000 * anim3:
2001 * <pre>
Chet Haasea18a86b2010-09-07 13:20:00 -07002002 * AnimatorSet s = new AnimatorSet();
Chet Haase17fb4b02010-06-28 17:55:07 -07002003 * s.play(anim1).before(anim2).before(anim3);
2004 * </pre>
2005 * If the desired result is to play anim1 then anim2 then anim3, this code expresses the
2006 * relationship correctly:</p>
2007 * <pre>
Chet Haasea18a86b2010-09-07 13:20:00 -07002008 * AnimatorSet s = new AnimatorSet();
Chet Haase17fb4b02010-06-28 17:55:07 -07002009 * s.play(anim1).before(anim2);
2010 * s.play(anim2).before(anim3);
2011 * </pre>
2012 * <p/>
2013 * <p>Note that it is possible to express relationships that cannot be resolved and will not
2014 * result in sensible results. For example, <code>play(anim1).after(anim1)</code> makes no
2015 * sense. In general, circular dependencies like this one (or more indirect ones where a depends
Chet Haasea18a86b2010-09-07 13:20:00 -07002016 * on b, which depends on c, which depends on a) should be avoided. Only create AnimatorSets
2017 * that can boil down to a simple, one-way relationship of animations starting with, before, and
Chet Haase17fb4b02010-06-28 17:55:07 -07002018 * after other, different, animations.</p>
2019 */
2020 public class Builder {
2021
2022 /**
2023 * This tracks the current node being processed. It is supplied to the play() method
Chet Haasea18a86b2010-09-07 13:20:00 -07002024 * of AnimatorSet and passed into the constructor of Builder.
Chet Haase17fb4b02010-06-28 17:55:07 -07002025 */
2026 private Node mCurrentNode;
2027
2028 /**
Chet Haasea18a86b2010-09-07 13:20:00 -07002029 * package-private constructor. Builders are only constructed by AnimatorSet, when the
Chet Haase17fb4b02010-06-28 17:55:07 -07002030 * play() method is called.
2031 *
2032 * @param anim The animation that is the dependency for the other animations passed into
2033 * the other methods of this Builder object.
2034 */
Chet Haasea18a86b2010-09-07 13:20:00 -07002035 Builder(Animator anim) {
Doris Liu13099142015-07-10 17:32:41 -07002036 mDependencyDirty = true;
2037 mCurrentNode = getNodeForAnimation(anim);
Chet Haase17fb4b02010-06-28 17:55:07 -07002038 }
2039
2040 /**
2041 * Sets up the given animation to play at the same time as the animation supplied in the
Chet Haasea18a86b2010-09-07 13:20:00 -07002042 * {@link AnimatorSet#play(Animator)} call that created this <code>Builder</code> object.
Chet Haase17fb4b02010-06-28 17:55:07 -07002043 *
2044 * @param anim The animation that will play when the animation supplied to the
Chet Haasea18a86b2010-09-07 13:20:00 -07002045 * {@link AnimatorSet#play(Animator)} method starts.
Chet Haase17fb4b02010-06-28 17:55:07 -07002046 */
Chet Haase2970c492010-11-09 13:58:04 -08002047 public Builder with(Animator anim) {
Doris Liu13099142015-07-10 17:32:41 -07002048 Node node = getNodeForAnimation(anim);
2049 mCurrentNode.addSibling(node);
Chet Haase2970c492010-11-09 13:58:04 -08002050 return this;
Chet Haase17fb4b02010-06-28 17:55:07 -07002051 }
2052
2053 /**
2054 * Sets up the given animation to play when the animation supplied in the
Chet Haasea18a86b2010-09-07 13:20:00 -07002055 * {@link AnimatorSet#play(Animator)} call that created this <code>Builder</code> object
Chet Haase17fb4b02010-06-28 17:55:07 -07002056 * ends.
2057 *
2058 * @param anim The animation that will play when the animation supplied to the
Chet Haasea18a86b2010-09-07 13:20:00 -07002059 * {@link AnimatorSet#play(Animator)} method ends.
Chet Haase17fb4b02010-06-28 17:55:07 -07002060 */
Chet Haase2970c492010-11-09 13:58:04 -08002061 public Builder before(Animator anim) {
Doris Liu13099142015-07-10 17:32:41 -07002062 Node node = getNodeForAnimation(anim);
2063 mCurrentNode.addChild(node);
Chet Haase2970c492010-11-09 13:58:04 -08002064 return this;
Chet Haase17fb4b02010-06-28 17:55:07 -07002065 }
2066
2067 /**
2068 * Sets up the given animation to play when the animation supplied in the
Chet Haasea18a86b2010-09-07 13:20:00 -07002069 * {@link AnimatorSet#play(Animator)} call that created this <code>Builder</code> object
Chet Haase17fb4b02010-06-28 17:55:07 -07002070 * to start when the animation supplied in this method call ends.
2071 *
2072 * @param anim The animation whose end will cause the animation supplied to the
Chet Haasea18a86b2010-09-07 13:20:00 -07002073 * {@link AnimatorSet#play(Animator)} method to play.
Chet Haase17fb4b02010-06-28 17:55:07 -07002074 */
Chet Haase2970c492010-11-09 13:58:04 -08002075 public Builder after(Animator anim) {
Doris Liu13099142015-07-10 17:32:41 -07002076 Node node = getNodeForAnimation(anim);
2077 mCurrentNode.addParent(node);
Chet Haase2970c492010-11-09 13:58:04 -08002078 return this;
Chet Haase17fb4b02010-06-28 17:55:07 -07002079 }
2080
2081 /**
2082 * Sets up the animation supplied in the
Chet Haasea18a86b2010-09-07 13:20:00 -07002083 * {@link AnimatorSet#play(Animator)} call that created this <code>Builder</code> object
Chet Haase17fb4b02010-06-28 17:55:07 -07002084 * to play when the given amount of time elapses.
2085 *
2086 * @param delay The number of milliseconds that should elapse before the
2087 * animation starts.
2088 */
Chet Haase2970c492010-11-09 13:58:04 -08002089 public Builder after(long delay) {
Chet Haasea18a86b2010-09-07 13:20:00 -07002090 // setup dummy ValueAnimator just to run the clock
Chet Haase2794eb32010-10-12 16:29:28 -07002091 ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
2092 anim.setDuration(delay);
2093 after(anim);
Chet Haase2970c492010-11-09 13:58:04 -08002094 return this;
Chet Haase17fb4b02010-06-28 17:55:07 -07002095 }
2096
2097 }
2098
2099}