blob: 0feab4d027a991b34939fdfbe918956ca0d4bb1a [file] [log] [blame]
Rahul Ravikumar05336002019-10-14 15:04:32 -07001/*
2 * Copyright (C) 2013 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.transition;
18
19import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.TimeInterpolator;
22import android.annotation.Nullable;
23import android.annotation.UnsupportedAppUsage;
24import android.content.Context;
25import android.content.res.TypedArray;
26import android.graphics.Path;
27import android.graphics.Rect;
28import android.util.ArrayMap;
29import android.util.AttributeSet;
30import android.util.Log;
31import android.util.LongSparseArray;
32import android.util.SparseArray;
33import android.util.SparseLongArray;
34import android.view.InflateException;
35import android.view.SurfaceView;
36import android.view.TextureView;
37import android.view.View;
38import android.view.ViewGroup;
39import android.view.ViewOverlay;
40import android.view.WindowId;
41import android.view.animation.AnimationUtils;
42import android.widget.ListView;
43import android.widget.Spinner;
44
45import com.android.internal.R;
46
47import java.util.ArrayList;
48import java.util.List;
49import java.util.StringTokenizer;
50
51/**
52 * A Transition holds information about animations that will be run on its
53 * targets during a scene change. Subclasses of this abstract class may
54 * choreograph several child transitions ({@link TransitionSet} or they may
55 * perform custom animations themselves. Any Transition has two main jobs:
56 * (1) capture property values, and (2) play animations based on changes to
57 * captured property values. A custom transition knows what property values
58 * on View objects are of interest to it, and also knows how to animate
59 * changes to those values. For example, the {@link Fade} transition tracks
60 * changes to visibility-related properties and is able to construct and run
61 * animations that fade items in or out based on changes to those properties.
62 *
63 * <p>Note: Transitions may not work correctly with either {@link SurfaceView}
64 * or {@link TextureView}, due to the way that these views are displayed
65 * on the screen. For SurfaceView, the problem is that the view is updated from
66 * a non-UI thread, so changes to the view due to transitions (such as moving
67 * and resizing the view) may be out of sync with the display inside those bounds.
68 * TextureView is more compatible with transitions in general, but some
69 * specific transitions (such as {@link Fade}) may not be compatible
70 * with TextureView because they rely on {@link ViewOverlay} functionality,
71 * which does not currently work with TextureView.</p>
72 *
73 * <p>Transitions can be declared in XML resource files inside the <code>res/transition</code>
74 * directory. Transition resources consist of a tag name for one of the Transition
75 * subclasses along with attributes to define some of the attributes of that transition.
76 * For example, here is a minimal resource file that declares a {@link ChangeBounds} transition:
77 *
78 * {@sample development/samples/ApiDemos/res/transition/changebounds.xml ChangeBounds}
79 *
80 * <p>This TransitionSet contains {@link android.transition.Explode} for visibility,
81 * {@link android.transition.ChangeBounds}, {@link android.transition.ChangeTransform},
82 * and {@link android.transition.ChangeClipBounds} and
83 * {@link android.transition.ChangeImageTransform}:</p>
84 *
85 * {@sample development/samples/ApiDemos/res/transition/explode_move_together.xml MultipleTransform}
86 *
87 * <p>Custom transition classes may be instantiated with a <code>transition</code> tag:</p>
88 * <pre>&lt;transition class="my.app.transition.CustomTransition"/></pre>
89 * <p>Custom transition classes loaded from XML should have a public constructor taking
90 * a {@link android.content.Context} and {@link android.util.AttributeSet}.</p>
91 *
92 * <p>Note that attributes for the transition are not required, just as they are
93 * optional when declared in code; Transitions created from XML resources will use
94 * the same defaults as their code-created equivalents. Here is a slightly more
95 * elaborate example which declares a {@link TransitionSet} transition with
96 * {@link ChangeBounds} and {@link Fade} child transitions:</p>
97 *
98 * {@sample
99 * development/samples/ApiDemos/res/transition/changebounds_fadeout_sequential.xml TransitionSet}
100 *
101 * <p>In this example, the transitionOrdering attribute is used on the TransitionSet
102 * object to change from the default {@link TransitionSet#ORDERING_TOGETHER} behavior
103 * to be {@link TransitionSet#ORDERING_SEQUENTIAL} instead. Also, the {@link Fade}
104 * transition uses a fadingMode of {@link Fade#OUT} instead of the default
105 * out-in behavior. Finally, note the use of the <code>targets</code> sub-tag, which
106 * takes a set of {@link android.R.styleable#TransitionTarget target} tags, each
107 * of which lists a specific <code>targetId</code>, <code>targetClass</code>,
108 * <code>targetName</code>, <code>excludeId</code>, <code>excludeClass</code>, or
109 * <code>excludeName</code>, which this transition acts upon.
110 * Use of targets is optional, but can be used to either limit the time spent checking
111 * attributes on unchanging views, or limiting the types of animations run on specific views.
112 * In this case, we know that only the <code>grayscaleContainer</code> will be
113 * disappearing, so we choose to limit the {@link Fade} transition to only that view.</p>
114 *
115 * Further information on XML resource descriptions for transitions can be found for
116 * {@link android.R.styleable#Transition}, {@link android.R.styleable#TransitionSet},
117 * {@link android.R.styleable#TransitionTarget}, {@link android.R.styleable#Fade},
118 * {@link android.R.styleable#Slide}, and {@link android.R.styleable#ChangeTransform}.
119 *
120 */
121public abstract class Transition implements Cloneable {
122
123 private static final String LOG_TAG = "Transition";
124 static final boolean DBG = false;
125
126 /**
127 * With {@link #setMatchOrder(int...)}, chooses to match by View instance.
128 */
129 public static final int MATCH_INSTANCE = 0x1;
130 private static final int MATCH_FIRST = MATCH_INSTANCE;
131
132 /**
133 * With {@link #setMatchOrder(int...)}, chooses to match by
134 * {@link android.view.View#getTransitionName()}. Null names will not be matched.
135 */
136 public static final int MATCH_NAME = 0x2;
137
138 /**
139 * With {@link #setMatchOrder(int...)}, chooses to match by
140 * {@link android.view.View#getId()}. Negative IDs will not be matched.
141 */
142 public static final int MATCH_ID = 0x3;
143
144 /**
145 * With {@link #setMatchOrder(int...)}, chooses to match by the {@link android.widget.Adapter}
146 * item id. When {@link android.widget.Adapter#hasStableIds()} returns false, no match
147 * will be made for items.
148 */
149 public static final int MATCH_ITEM_ID = 0x4;
150
151 private static final int MATCH_LAST = MATCH_ITEM_ID;
152
153 private static final String MATCH_INSTANCE_STR = "instance";
154 private static final String MATCH_NAME_STR = "name";
155 /** To be removed before L release */
156 private static final String MATCH_VIEW_NAME_STR = "viewName";
157 private static final String MATCH_ID_STR = "id";
158 private static final String MATCH_ITEM_ID_STR = "itemId";
159
160 private static final int[] DEFAULT_MATCH_ORDER = {
161 MATCH_NAME,
162 MATCH_INSTANCE,
163 MATCH_ID,
164 MATCH_ITEM_ID,
165 };
166
167 private static final PathMotion STRAIGHT_PATH_MOTION = new PathMotion() {
168 @Override
169 public Path getPath(float startX, float startY, float endX, float endY) {
170 Path path = new Path();
171 path.moveTo(startX, startY);
172 path.lineTo(endX, endY);
173 return path;
174 }
175 };
176
177 private String mName = getClass().getName();
178
179 long mStartDelay = -1;
180 long mDuration = -1;
181 TimeInterpolator mInterpolator = null;
182 ArrayList<Integer> mTargetIds = new ArrayList<Integer>();
183 ArrayList<View> mTargets = new ArrayList<View>();
184 ArrayList<String> mTargetNames = null;
185 ArrayList<Class> mTargetTypes = null;
186 ArrayList<Integer> mTargetIdExcludes = null;
187 ArrayList<View> mTargetExcludes = null;
188 ArrayList<Class> mTargetTypeExcludes = null;
189 ArrayList<String> mTargetNameExcludes = null;
190 ArrayList<Integer> mTargetIdChildExcludes = null;
191 ArrayList<View> mTargetChildExcludes = null;
192 ArrayList<Class> mTargetTypeChildExcludes = null;
193 private TransitionValuesMaps mStartValues = new TransitionValuesMaps();
194 private TransitionValuesMaps mEndValues = new TransitionValuesMaps();
195 TransitionSet mParent = null;
196 int[] mMatchOrder = DEFAULT_MATCH_ORDER;
197 ArrayList<TransitionValues> mStartValuesList; // only valid after playTransition starts
198 ArrayList<TransitionValues> mEndValuesList; // only valid after playTransitions starts
199
200 // Per-animator information used for later canceling when future transitions overlap
201 private static ThreadLocal<ArrayMap<Animator, AnimationInfo>> sRunningAnimators =
202 new ThreadLocal<ArrayMap<Animator, AnimationInfo>>();
203
204 // Scene Root is set at createAnimator() time in the cloned Transition
205 ViewGroup mSceneRoot = null;
206
207 // Whether removing views from their parent is possible. This is only for views
208 // in the start scene, which are no longer in the view hierarchy. This property
209 // is determined by whether the previous Scene was created from a layout
210 // resource, and thus the views from the exited scene are going away anyway
211 // and can be removed as necessary to achieve a particular effect, such as
212 // removing them from parents to add them to overlays.
213 boolean mCanRemoveViews = false;
214
215 // Track all animators in use in case the transition gets canceled and needs to
216 // cancel running animators
217 private ArrayList<Animator> mCurrentAnimators = new ArrayList<Animator>();
218
219 // Number of per-target instances of this Transition currently running. This count is
220 // determined by calls to start() and end()
221 int mNumInstances = 0;
222
223 // Whether this transition is currently paused, due to a call to pause()
224 boolean mPaused = false;
225
226 // Whether this transition has ended. Used to avoid pause/resume on transitions
227 // that have completed
228 private boolean mEnded = false;
229
230 // The set of listeners to be sent transition lifecycle events.
231 ArrayList<TransitionListener> mListeners = null;
232
233 // The set of animators collected from calls to createAnimator(),
234 // to be run in runAnimators()
235 ArrayList<Animator> mAnimators = new ArrayList<Animator>();
236
237 // The function for calculating the Animation start delay.
238 TransitionPropagation mPropagation;
239
240 // The rectangular region for Transitions like Explode and TransitionPropagations
241 // like CircularPropagation
242 EpicenterCallback mEpicenterCallback;
243
244 // For Fragment shared element transitions, linking views explicitly by mismatching
245 // transitionNames.
246 ArrayMap<String, String> mNameOverrides;
247
248 // The function used to interpolate along two-dimensional points. Typically used
249 // for adding curves to x/y View motion.
250 PathMotion mPathMotion = STRAIGHT_PATH_MOTION;
251
252 /**
253 * Constructs a Transition object with no target objects. A transition with
254 * no targets defaults to running on all target objects in the scene hierarchy
255 * (if the transition is not contained in a TransitionSet), or all target
256 * objects passed down from its parent (if it is in a TransitionSet).
257 */
258 public Transition() {}
259
260 /**
261 * Perform inflation from XML and apply a class-specific base style from a
262 * theme attribute or style resource. This constructor of Transition allows
263 * subclasses to use their own base style when they are inflating.
264 *
265 * @param context The Context the transition is running in, through which it can
266 * access the current theme, resources, etc.
267 * @param attrs The attributes of the XML tag that is inflating the transition.
268 */
269 public Transition(Context context, AttributeSet attrs) {
270
271 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Transition);
272 long duration = a.getInt(R.styleable.Transition_duration, -1);
273 if (duration >= 0) {
274 setDuration(duration);
275 }
276 long startDelay = a.getInt(R.styleable.Transition_startDelay, -1);
277 if (startDelay > 0) {
278 setStartDelay(startDelay);
279 }
280 final int resID = a.getResourceId(com.android.internal.R.styleable.Animator_interpolator, 0);
281 if (resID > 0) {
282 setInterpolator(AnimationUtils.loadInterpolator(context, resID));
283 }
284 String matchOrder = a.getString(R.styleable.Transition_matchOrder);
285 if (matchOrder != null) {
286 setMatchOrder(parseMatchOrder(matchOrder));
287 }
288 a.recycle();
289 }
290
291 private static int[] parseMatchOrder(String matchOrderString) {
292 StringTokenizer st = new StringTokenizer(matchOrderString, ",");
293 int matches[] = new int[st.countTokens()];
294 int index = 0;
295 while (st.hasMoreTokens()) {
296 String token = st.nextToken().trim();
297 if (MATCH_ID_STR.equalsIgnoreCase(token)) {
298 matches[index] = Transition.MATCH_ID;
299 } else if (MATCH_INSTANCE_STR.equalsIgnoreCase(token)) {
300 matches[index] = Transition.MATCH_INSTANCE;
301 } else if (MATCH_NAME_STR.equalsIgnoreCase(token)) {
302 matches[index] = Transition.MATCH_NAME;
303 } else if (MATCH_VIEW_NAME_STR.equalsIgnoreCase(token)) {
304 matches[index] = Transition.MATCH_NAME;
305 } else if (MATCH_ITEM_ID_STR.equalsIgnoreCase(token)) {
306 matches[index] = Transition.MATCH_ITEM_ID;
307 } else if (token.isEmpty()) {
308 int[] smallerMatches = new int[matches.length - 1];
309 System.arraycopy(matches, 0, smallerMatches, 0, index);
310 matches = smallerMatches;
311 index--;
312 } else {
313 throw new InflateException("Unknown match type in matchOrder: '" + token + "'");
314 }
315 index++;
316 }
317 return matches;
318 }
319
320 /**
321 * Sets the duration of this transition. By default, there is no duration
322 * (indicated by a negative number), which means that the Animator created by
323 * the transition will have its own specified duration. If the duration of a
324 * Transition is set, that duration will override the Animator duration.
325 *
326 * @param duration The length of the animation, in milliseconds.
327 * @return This transition object.
328 * @attr ref android.R.styleable#Transition_duration
329 */
330 public Transition setDuration(long duration) {
331 mDuration = duration;
332 return this;
333 }
334
335 /**
336 * Returns the duration set on this transition. If no duration has been set,
337 * the returned value will be negative, indicating that resulting animators will
338 * retain their own durations.
339 *
340 * @return The duration set on this transition, in milliseconds, if one has been
341 * set, otherwise returns a negative number.
342 */
343 public long getDuration() {
344 return mDuration;
345 }
346
347 /**
348 * Sets the startDelay of this transition. By default, there is no delay
349 * (indicated by a negative number), which means that the Animator created by
350 * the transition will have its own specified startDelay. If the delay of a
351 * Transition is set, that delay will override the Animator delay.
352 *
353 * @param startDelay The length of the delay, in milliseconds.
354 * @return This transition object.
355 * @attr ref android.R.styleable#Transition_startDelay
356 */
357 public Transition setStartDelay(long startDelay) {
358 mStartDelay = startDelay;
359 return this;
360 }
361
362 /**
363 * Returns the startDelay set on this transition. If no startDelay has been set,
364 * the returned value will be negative, indicating that resulting animators will
365 * retain their own startDelays.
366 *
367 * @return The startDelay set on this transition, in milliseconds, if one has
368 * been set, otherwise returns a negative number.
369 */
370 public long getStartDelay() {
371 return mStartDelay;
372 }
373
374 /**
375 * Sets the interpolator of this transition. By default, the interpolator
376 * is null, which means that the Animator created by the transition
377 * will have its own specified interpolator. If the interpolator of a
378 * Transition is set, that interpolator will override the Animator interpolator.
379 *
380 * @param interpolator The time interpolator used by the transition
381 * @return This transition object.
382 * @attr ref android.R.styleable#Transition_interpolator
383 */
384 public Transition setInterpolator(TimeInterpolator interpolator) {
385 mInterpolator = interpolator;
386 return this;
387 }
388
389 /**
390 * Returns the interpolator set on this transition. If no interpolator has been set,
391 * the returned value will be null, indicating that resulting animators will
392 * retain their own interpolators.
393 *
394 * @return The interpolator set on this transition, if one has been set, otherwise
395 * returns null.
396 */
397 public TimeInterpolator getInterpolator() {
398 return mInterpolator;
399 }
400
401 /**
402 * Returns the set of property names used stored in the {@link TransitionValues}
403 * object passed into {@link #captureStartValues(TransitionValues)} that
404 * this transition cares about for the purposes of canceling overlapping animations.
405 * When any transition is started on a given scene root, all transitions
406 * currently running on that same scene root are checked to see whether the
407 * properties on which they based their animations agree with the end values of
408 * the same properties in the new transition. If the end values are not equal,
409 * then the old animation is canceled since the new transition will start a new
410 * animation to these new values. If the values are equal, the old animation is
411 * allowed to continue and no new animation is started for that transition.
412 *
413 * <p>A transition does not need to override this method. However, not doing so
414 * will mean that the cancellation logic outlined in the previous paragraph
415 * will be skipped for that transition, possibly leading to artifacts as
416 * old transitions and new transitions on the same targets run in parallel,
417 * animating views toward potentially different end values.</p>
418 *
419 * @return An array of property names as described in the class documentation for
420 * {@link TransitionValues}. The default implementation returns <code>null</code>.
421 */
422 public String[] getTransitionProperties() {
423 return null;
424 }
425
426 /**
427 * This method creates an animation that will be run for this transition
428 * given the information in the startValues and endValues structures captured
429 * earlier for the start and end scenes. Subclasses of Transition should override
430 * this method. The method should only be called by the transition system; it is
431 * not intended to be called from external classes.
432 *
433 * <p>This method is called by the transition's parent (all the way up to the
434 * topmost Transition in the hierarchy) with the sceneRoot and start/end
435 * values that the transition may need to set up initial target values
436 * and construct an appropriate animation. For example, if an overall
437 * Transition is a {@link TransitionSet} consisting of several
438 * child transitions in sequence, then some of the child transitions may
439 * want to set initial values on target views prior to the overall
440 * Transition commencing, to put them in an appropriate state for the
441 * delay between that start and the child Transition start time. For
442 * example, a transition that fades an item in may wish to set the starting
443 * alpha value to 0, to avoid it blinking in prior to the transition
444 * actually starting the animation. This is necessary because the scene
445 * change that triggers the Transition will automatically set the end-scene
446 * on all target views, so a Transition that wants to animate from a
447 * different value should set that value prior to returning from this method.</p>
448 *
449 * <p>Additionally, a Transition can perform logic to determine whether
450 * the transition needs to run on the given target and start/end values.
451 * For example, a transition that resizes objects on the screen may wish
452 * to avoid running for views which are not present in either the start
453 * or end scenes.</p>
454 *
455 * <p>If there is an animator created and returned from this method, the
456 * transition mechanism will apply any applicable duration, startDelay,
457 * and interpolator to that animation and start it. A return value of
458 * <code>null</code> indicates that no animation should run. The default
459 * implementation returns null.</p>
460 *
461 * <p>The method is called for every applicable target object, which is
462 * stored in the {@link TransitionValues#view} field.</p>
463 *
464 *
465 * @param sceneRoot The root of the transition hierarchy.
466 * @param startValues The values for a specific target in the start scene.
467 * @param endValues The values for the target in the end scene.
468 * @return A Animator to be started at the appropriate time in the
469 * overall transition for this scene change. A null value means no animation
470 * should be run.
471 */
472 public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
473 TransitionValues endValues) {
474 return null;
475 }
476
477 /**
478 * Sets the order in which Transition matches View start and end values.
479 * <p>
480 * The default behavior is to match first by {@link android.view.View#getTransitionName()},
481 * then by View instance, then by {@link android.view.View#getId()} and finally
482 * by its item ID if it is in a direct child of ListView. The caller can
483 * choose to have only some or all of the values of {@link #MATCH_INSTANCE},
484 * {@link #MATCH_NAME}, {@link #MATCH_ITEM_ID}, and {@link #MATCH_ID}. Only
485 * the match algorithms supplied will be used to determine whether Views are the
486 * the same in both the start and end Scene. Views that do not match will be considered
487 * as entering or leaving the Scene.
488 * </p>
489 * @param matches A list of zero or more of {@link #MATCH_INSTANCE},
490 * {@link #MATCH_NAME}, {@link #MATCH_ITEM_ID}, and {@link #MATCH_ID}.
491 * If none are provided, then the default match order will be set.
492 */
493 public void setMatchOrder(int... matches) {
494 if (matches == null || matches.length == 0) {
495 mMatchOrder = DEFAULT_MATCH_ORDER;
496 } else {
497 for (int i = 0; i < matches.length; i++) {
498 int match = matches[i];
499 if (!isValidMatch(match)) {
500 throw new IllegalArgumentException("matches contains invalid value");
501 }
502 if (alreadyContains(matches, i)) {
503 throw new IllegalArgumentException("matches contains a duplicate value");
504 }
505 }
506 mMatchOrder = matches.clone();
507 }
508 }
509
510 private static boolean isValidMatch(int match) {
511 return (match >= MATCH_FIRST && match <= MATCH_LAST);
512 }
513
514 private static boolean alreadyContains(int[] array, int searchIndex) {
515 int value = array[searchIndex];
516 for (int i = 0; i < searchIndex; i++) {
517 if (array[i] == value) {
518 return true;
519 }
520 }
521 return false;
522 }
523
524 /**
525 * Match start/end values by View instance. Adds matched values to mStartValuesList
526 * and mEndValuesList and removes them from unmatchedStart and unmatchedEnd.
527 */
528 private void matchInstances(ArrayMap<View, TransitionValues> unmatchedStart,
529 ArrayMap<View, TransitionValues> unmatchedEnd) {
530 for (int i = unmatchedStart.size() - 1; i >= 0; i--) {
531 View view = unmatchedStart.keyAt(i);
532 if (view != null && isValidTarget(view)) {
533 TransitionValues end = unmatchedEnd.remove(view);
534 if (end != null && isValidTarget(end.view)) {
535 TransitionValues start = unmatchedStart.removeAt(i);
536 mStartValuesList.add(start);
537 mEndValuesList.add(end);
538 }
539 }
540 }
541 }
542
543 /**
544 * Match start/end values by Adapter item ID. Adds matched values to mStartValuesList
545 * and mEndValuesList and removes them from unmatchedStart and unmatchedEnd, using
546 * startItemIds and endItemIds as a guide for which Views have unique item IDs.
547 */
548 private void matchItemIds(ArrayMap<View, TransitionValues> unmatchedStart,
549 ArrayMap<View, TransitionValues> unmatchedEnd,
550 LongSparseArray<View> startItemIds, LongSparseArray<View> endItemIds) {
551 int numStartIds = startItemIds.size();
552 for (int i = 0; i < numStartIds; i++) {
553 View startView = startItemIds.valueAt(i);
554 if (startView != null && isValidTarget(startView)) {
555 View endView = endItemIds.get(startItemIds.keyAt(i));
556 if (endView != null && isValidTarget(endView)) {
557 TransitionValues startValues = unmatchedStart.get(startView);
558 TransitionValues endValues = unmatchedEnd.get(endView);
559 if (startValues != null && endValues != null) {
560 mStartValuesList.add(startValues);
561 mEndValuesList.add(endValues);
562 unmatchedStart.remove(startView);
563 unmatchedEnd.remove(endView);
564 }
565 }
566 }
567 }
568 }
569
570 /**
571 * Match start/end values by Adapter view ID. Adds matched values to mStartValuesList
572 * and mEndValuesList and removes them from unmatchedStart and unmatchedEnd, using
573 * startIds and endIds as a guide for which Views have unique IDs.
574 */
575 private void matchIds(ArrayMap<View, TransitionValues> unmatchedStart,
576 ArrayMap<View, TransitionValues> unmatchedEnd,
577 SparseArray<View> startIds, SparseArray<View> endIds) {
578 int numStartIds = startIds.size();
579 for (int i = 0; i < numStartIds; i++) {
580 View startView = startIds.valueAt(i);
581 if (startView != null && isValidTarget(startView)) {
582 View endView = endIds.get(startIds.keyAt(i));
583 if (endView != null && isValidTarget(endView)) {
584 TransitionValues startValues = unmatchedStart.get(startView);
585 TransitionValues endValues = unmatchedEnd.get(endView);
586 if (startValues != null && endValues != null) {
587 mStartValuesList.add(startValues);
588 mEndValuesList.add(endValues);
589 unmatchedStart.remove(startView);
590 unmatchedEnd.remove(endView);
591 }
592 }
593 }
594 }
595 }
596
597 /**
598 * Match start/end values by Adapter transitionName. Adds matched values to mStartValuesList
599 * and mEndValuesList and removes them from unmatchedStart and unmatchedEnd, using
600 * startNames and endNames as a guide for which Views have unique transitionNames.
601 */
602 private void matchNames(ArrayMap<View, TransitionValues> unmatchedStart,
603 ArrayMap<View, TransitionValues> unmatchedEnd,
604 ArrayMap<String, View> startNames, ArrayMap<String, View> endNames) {
605 int numStartNames = startNames.size();
606 for (int i = 0; i < numStartNames; i++) {
607 View startView = startNames.valueAt(i);
608 if (startView != null && isValidTarget(startView)) {
609 View endView = endNames.get(startNames.keyAt(i));
610 if (endView != null && isValidTarget(endView)) {
611 TransitionValues startValues = unmatchedStart.get(startView);
612 TransitionValues endValues = unmatchedEnd.get(endView);
613 if (startValues != null && endValues != null) {
614 mStartValuesList.add(startValues);
615 mEndValuesList.add(endValues);
616 unmatchedStart.remove(startView);
617 unmatchedEnd.remove(endView);
618 }
619 }
620 }
621 }
622 }
623
624 /**
625 * Adds all values from unmatchedStart and unmatchedEnd to mStartValuesList and mEndValuesList,
626 * assuming that there is no match between values in the list.
627 */
628 private void addUnmatched(ArrayMap<View, TransitionValues> unmatchedStart,
629 ArrayMap<View, TransitionValues> unmatchedEnd) {
630 // Views that only exist in the start Scene
631 for (int i = 0; i < unmatchedStart.size(); i++) {
632 final TransitionValues start = unmatchedStart.valueAt(i);
633 if (isValidTarget(start.view)) {
634 mStartValuesList.add(start);
635 mEndValuesList.add(null);
636 }
637 }
638
639 // Views that only exist in the end Scene
640 for (int i = 0; i < unmatchedEnd.size(); i++) {
641 final TransitionValues end = unmatchedEnd.valueAt(i);
642 if (isValidTarget(end.view)) {
643 mEndValuesList.add(end);
644 mStartValuesList.add(null);
645 }
646 }
647 }
648
649 private void matchStartAndEnd(TransitionValuesMaps startValues,
650 TransitionValuesMaps endValues) {
651 ArrayMap<View, TransitionValues> unmatchedStart =
652 new ArrayMap<View, TransitionValues>(startValues.viewValues);
653 ArrayMap<View, TransitionValues> unmatchedEnd =
654 new ArrayMap<View, TransitionValues>(endValues.viewValues);
655
656 for (int i = 0; i < mMatchOrder.length; i++) {
657 switch (mMatchOrder[i]) {
658 case MATCH_INSTANCE:
659 matchInstances(unmatchedStart, unmatchedEnd);
660 break;
661 case MATCH_NAME:
662 matchNames(unmatchedStart, unmatchedEnd,
663 startValues.nameValues, endValues.nameValues);
664 break;
665 case MATCH_ID:
666 matchIds(unmatchedStart, unmatchedEnd,
667 startValues.idValues, endValues.idValues);
668 break;
669 case MATCH_ITEM_ID:
670 matchItemIds(unmatchedStart, unmatchedEnd,
671 startValues.itemIdValues, endValues.itemIdValues);
672 break;
673 }
674 }
675 addUnmatched(unmatchedStart, unmatchedEnd);
676 }
677
678 /**
679 * This method, essentially a wrapper around all calls to createAnimator for all
680 * possible target views, is called with the entire set of start/end
681 * values. The implementation in Transition iterates through these lists
682 * and calls {@link #createAnimator(ViewGroup, TransitionValues, TransitionValues)}
683 * with each set of start/end values on this transition. The
684 * TransitionSet subclass overrides this method and delegates it to
685 * each of its children in succession.
686 *
687 * @hide
688 */
689 protected void createAnimators(ViewGroup sceneRoot, TransitionValuesMaps startValues,
690 TransitionValuesMaps endValues, ArrayList<TransitionValues> startValuesList,
691 ArrayList<TransitionValues> endValuesList) {
692 if (DBG) {
693 Log.d(LOG_TAG, "createAnimators() for " + this);
694 }
695 ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
696 long minStartDelay = Long.MAX_VALUE;
697 int minAnimator = mAnimators.size();
698 SparseLongArray startDelays = new SparseLongArray();
699 int startValuesListCount = startValuesList.size();
700 for (int i = 0; i < startValuesListCount; ++i) {
701 TransitionValues start = startValuesList.get(i);
702 TransitionValues end = endValuesList.get(i);
703 if (start != null && !start.targetedTransitions.contains(this)) {
704 start = null;
705 }
706 if (end != null && !end.targetedTransitions.contains(this)) {
707 end = null;
708 }
709 if (start == null && end == null) {
710 continue;
711 }
712 // Only bother trying to animate with values that differ between start/end
713 boolean isChanged = start == null || end == null || isTransitionRequired(start, end);
714 if (isChanged) {
715 if (DBG) {
716 View view = (end != null) ? end.view : start.view;
717 Log.d(LOG_TAG, " differing start/end values for view " + view);
718 if (start == null || end == null) {
719 Log.d(LOG_TAG, " " + ((start == null) ?
720 "start null, end non-null" : "start non-null, end null"));
721 } else {
722 for (String key : start.values.keySet()) {
723 Object startValue = start.values.get(key);
724 Object endValue = end.values.get(key);
725 if (startValue != endValue && !startValue.equals(endValue)) {
726 Log.d(LOG_TAG, " " + key + ": start(" + startValue +
727 "), end(" + endValue + ")");
728 }
729 }
730 }
731 }
732 // TODO: what to do about targetIds and itemIds?
733 Animator animator = createAnimator(sceneRoot, start, end);
734 if (animator != null) {
735 // Save animation info for future cancellation purposes
736 View view = null;
737 TransitionValues infoValues = null;
738 if (end != null) {
739 view = end.view;
740 String[] properties = getTransitionProperties();
741 if (properties != null && properties.length > 0) {
742 infoValues = new TransitionValues(view);
743 TransitionValues newValues = endValues.viewValues.get(view);
744 if (newValues != null) {
745 for (int j = 0; j < properties.length; ++j) {
746 infoValues.values.put(properties[j],
747 newValues.values.get(properties[j]));
748 }
749 }
750 int numExistingAnims = runningAnimators.size();
751 for (int j = 0; j < numExistingAnims; ++j) {
752 Animator anim = runningAnimators.keyAt(j);
753 AnimationInfo info = runningAnimators.get(anim);
754 if (info.values != null && info.view == view &&
755 ((info.name == null && getName() == null) ||
756 info.name.equals(getName()))) {
757 if (info.values.equals(infoValues)) {
758 // Favor the old animator
759 animator = null;
760 break;
761 }
762 }
763 }
764 }
765 } else {
766 view = (start != null) ? start.view : null;
767 }
768 if (animator != null) {
769 if (mPropagation != null) {
770 long delay = mPropagation
771 .getStartDelay(sceneRoot, this, start, end);
772 startDelays.put(mAnimators.size(), delay);
773 minStartDelay = Math.min(delay, minStartDelay);
774 }
775 AnimationInfo info = new AnimationInfo(view, getName(), this,
776 sceneRoot.getWindowId(), infoValues);
777 runningAnimators.put(animator, info);
778 mAnimators.add(animator);
779 }
780 }
781 }
782 }
783 if (startDelays.size() != 0) {
784 for (int i = 0; i < startDelays.size(); i++) {
785 int index = startDelays.keyAt(i);
786 Animator animator = mAnimators.get(index);
787 long delay = startDelays.valueAt(i) - minStartDelay + animator.getStartDelay();
788 animator.setStartDelay(delay);
789 }
790 }
791 }
792
793 /**
794 * Internal utility method for checking whether a given view/id
795 * is valid for this transition, where "valid" means that either
796 * the Transition has no target/targetId list (the default, in which
797 * cause the transition should act on all views in the hiearchy), or
798 * the given view is in the target list or the view id is in the
799 * targetId list. If the target parameter is null, then the target list
800 * is not checked (this is in the case of ListView items, where the
801 * views are ignored and only the ids are used).
802 *
803 * @hide
804 */
805 public boolean isValidTarget(View target) {
806 if (target == null) {
807 return false;
808 }
809 int targetId = target.getId();
810 if (mTargetIdExcludes != null && mTargetIdExcludes.contains(targetId)) {
811 return false;
812 }
813 if (mTargetExcludes != null && mTargetExcludes.contains(target)) {
814 return false;
815 }
816 if (mTargetTypeExcludes != null && target != null) {
817 int numTypes = mTargetTypeExcludes.size();
818 for (int i = 0; i < numTypes; ++i) {
819 Class type = mTargetTypeExcludes.get(i);
820 if (type.isInstance(target)) {
821 return false;
822 }
823 }
824 }
825 if (mTargetNameExcludes != null && target != null && target.getTransitionName() != null) {
826 if (mTargetNameExcludes.contains(target.getTransitionName())) {
827 return false;
828 }
829 }
830 if (mTargetIds.size() == 0 && mTargets.size() == 0 &&
831 (mTargetTypes == null || mTargetTypes.isEmpty()) &&
832 (mTargetNames == null || mTargetNames.isEmpty())) {
833 return true;
834 }
835 if (mTargetIds.contains(targetId) || mTargets.contains(target)) {
836 return true;
837 }
838 if (mTargetNames != null && mTargetNames.contains(target.getTransitionName())) {
839 return true;
840 }
841 if (mTargetTypes != null) {
842 for (int i = 0; i < mTargetTypes.size(); ++i) {
843 if (mTargetTypes.get(i).isInstance(target)) {
844 return true;
845 }
846 }
847 }
848 return false;
849 }
850
851 @UnsupportedAppUsage
852 private static ArrayMap<Animator, AnimationInfo> getRunningAnimators() {
853 ArrayMap<Animator, AnimationInfo> runningAnimators = sRunningAnimators.get();
854 if (runningAnimators == null) {
855 runningAnimators = new ArrayMap<Animator, AnimationInfo>();
856 sRunningAnimators.set(runningAnimators);
857 }
858 return runningAnimators;
859 }
860
861 /**
862 * This is called internally once all animations have been set up by the
863 * transition hierarchy.
864 *
865 * @hide
866 */
867 protected void runAnimators() {
868 if (DBG) {
869 Log.d(LOG_TAG, "runAnimators() on " + this);
870 }
871 start();
872 ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
873 // Now start every Animator that was previously created for this transition
874 for (Animator anim : mAnimators) {
875 if (DBG) {
876 Log.d(LOG_TAG, " anim: " + anim);
877 }
878 if (runningAnimators.containsKey(anim)) {
879 start();
880 runAnimator(anim, runningAnimators);
881 }
882 }
883 mAnimators.clear();
884 end();
885 }
886
887 private void runAnimator(Animator animator,
888 final ArrayMap<Animator, AnimationInfo> runningAnimators) {
889 if (animator != null) {
890 // TODO: could be a single listener instance for all of them since it uses the param
891 animator.addListener(new AnimatorListenerAdapter() {
892 @Override
893 public void onAnimationStart(Animator animation) {
894 mCurrentAnimators.add(animation);
895 }
896 @Override
897 public void onAnimationEnd(Animator animation) {
898 runningAnimators.remove(animation);
899 mCurrentAnimators.remove(animation);
900 }
901 });
902 animate(animator);
903 }
904 }
905
906 /**
907 * Captures the values in the start scene for the properties that this
908 * transition monitors. These values are then passed as the startValues
909 * structure in a later call to
910 * {@link #createAnimator(ViewGroup, TransitionValues, TransitionValues)}.
911 * The main concern for an implementation is what the
912 * properties are that the transition cares about and what the values are
913 * for all of those properties. The start and end values will be compared
914 * later during the
915 * {@link #createAnimator(android.view.ViewGroup, TransitionValues, TransitionValues)}
916 * method to determine what, if any, animations, should be run.
917 *
918 * <p>Subclasses must implement this method. The method should only be called by the
919 * transition system; it is not intended to be called from external classes.</p>
920 *
921 * @param transitionValues The holder for any values that the Transition
922 * wishes to store. Values are stored in the <code>values</code> field
923 * of this TransitionValues object and are keyed from
924 * a String value. For example, to store a view's rotation value,
925 * a transition might call
926 * <code>transitionValues.values.put("appname:transitionname:rotation",
927 * view.getRotation())</code>. The target view will already be stored in
928 * the transitionValues structure when this method is called.
929 *
930 * @see #captureEndValues(TransitionValues)
931 * @see #createAnimator(ViewGroup, TransitionValues, TransitionValues)
932 */
933 public abstract void captureStartValues(TransitionValues transitionValues);
934
935 /**
936 * Captures the values in the end scene for the properties that this
937 * transition monitors. These values are then passed as the endValues
938 * structure in a later call to
939 * {@link #createAnimator(ViewGroup, TransitionValues, TransitionValues)}.
940 * The main concern for an implementation is what the
941 * properties are that the transition cares about and what the values are
942 * for all of those properties. The start and end values will be compared
943 * later during the
944 * {@link #createAnimator(android.view.ViewGroup, TransitionValues, TransitionValues)}
945 * method to determine what, if any, animations, should be run.
946 *
947 * <p>Subclasses must implement this method. The method should only be called by the
948 * transition system; it is not intended to be called from external classes.</p>
949 *
950 * @param transitionValues The holder for any values that the Transition
951 * wishes to store. Values are stored in the <code>values</code> field
952 * of this TransitionValues object and are keyed from
953 * a String value. For example, to store a view's rotation value,
954 * a transition might call
955 * <code>transitionValues.values.put("appname:transitionname:rotation",
956 * view.getRotation())</code>. The target view will already be stored in
957 * the transitionValues structure when this method is called.
958 *
959 * @see #captureStartValues(TransitionValues)
960 * @see #createAnimator(ViewGroup, TransitionValues, TransitionValues)
961 */
962 public abstract void captureEndValues(TransitionValues transitionValues);
963
964 /**
965 * Adds the id of a target view that this Transition is interested in
966 * animating. By default, there are no targetIds, and a Transition will
967 * listen for changes on every view in the hierarchy below the sceneRoot
968 * of the Scene being transitioned into. Setting targetIds constrains
969 * the Transition to only listen for, and act on, views with these IDs.
970 * Views with different IDs, or no IDs whatsoever, will be ignored.
971 *
972 * <p>Note that using ids to specify targets implies that ids should be unique
973 * within the view hierarchy underneath the scene root.</p>
974 *
975 * @see View#getId()
976 * @param targetId The id of a target view, must be a positive number.
977 * @return The Transition to which the targetId is added.
978 * Returning the same object makes it easier to chain calls during
979 * construction, such as
980 * <code>transitionSet.addTransitions(new Fade()).addTarget(someId);</code>
981 */
982 public Transition addTarget(int targetId) {
983 if (targetId > 0) {
984 mTargetIds.add(targetId);
985 }
986 return this;
987 }
988
989 /**
990 * Adds the transitionName of a target view that this Transition is interested in
991 * animating. By default, there are no targetNames, and a Transition will
992 * listen for changes on every view in the hierarchy below the sceneRoot
993 * of the Scene being transitioned into. Setting targetNames constrains
994 * the Transition to only listen for, and act on, views with these transitionNames.
995 * Views with different transitionNames, or no transitionName whatsoever, will be ignored.
996 *
997 * <p>Note that transitionNames should be unique within the view hierarchy.</p>
998 *
999 * @see android.view.View#getTransitionName()
1000 * @param targetName The transitionName of a target view, must be non-null.
1001 * @return The Transition to which the target transitionName is added.
1002 * Returning the same object makes it easier to chain calls during
1003 * construction, such as
1004 * <code>transitionSet.addTransitions(new Fade()).addTarget(someName);</code>
1005 */
1006 public Transition addTarget(String targetName) {
1007 if (targetName != null) {
1008 if (mTargetNames == null) {
1009 mTargetNames = new ArrayList<String>();
1010 }
1011 mTargetNames.add(targetName);
1012 }
1013 return this;
1014 }
1015
1016 /**
1017 * Adds the Class of a target view that this Transition is interested in
1018 * animating. By default, there are no targetTypes, and a Transition will
1019 * listen for changes on every view in the hierarchy below the sceneRoot
1020 * of the Scene being transitioned into. Setting targetTypes constrains
1021 * the Transition to only listen for, and act on, views with these classes.
1022 * Views with different classes will be ignored.
1023 *
1024 * <p>Note that any View that can be cast to targetType will be included, so
1025 * if targetType is <code>View.class</code>, all Views will be included.</p>
1026 *
1027 * @see #addTarget(int)
1028 * @see #addTarget(android.view.View)
1029 * @see #excludeTarget(Class, boolean)
1030 * @see #excludeChildren(Class, boolean)
1031 *
1032 * @param targetType The type to include when running this transition.
1033 * @return The Transition to which the target class was added.
1034 * Returning the same object makes it easier to chain calls during
1035 * construction, such as
1036 * <code>transitionSet.addTransitions(new Fade()).addTarget(ImageView.class);</code>
1037 */
1038 public Transition addTarget(Class targetType) {
1039 if (targetType != null) {
1040 if (mTargetTypes == null) {
1041 mTargetTypes = new ArrayList<Class>();
1042 }
1043 mTargetTypes.add(targetType);
1044 }
1045 return this;
1046 }
1047
1048 /**
1049 * Removes the given targetId from the list of ids that this Transition
1050 * is interested in animating.
1051 *
1052 * @param targetId The id of a target view, must be a positive number.
1053 * @return The Transition from which the targetId is removed.
1054 * Returning the same object makes it easier to chain calls during
1055 * construction, such as
1056 * <code>transitionSet.addTransitions(new Fade()).removeTargetId(someId);</code>
1057 */
1058 public Transition removeTarget(int targetId) {
1059 if (targetId > 0) {
1060 mTargetIds.remove((Integer)targetId);
1061 }
1062 return this;
1063 }
1064
1065 /**
1066 * Removes the given targetName from the list of transitionNames that this Transition
1067 * is interested in animating.
1068 *
1069 * @param targetName The transitionName of a target view, must not be null.
1070 * @return The Transition from which the targetName is removed.
1071 * Returning the same object makes it easier to chain calls during
1072 * construction, such as
1073 * <code>transitionSet.addTransitions(new Fade()).removeTargetName(someName);</code>
1074 */
1075 public Transition removeTarget(String targetName) {
1076 if (targetName != null && mTargetNames != null) {
1077 mTargetNames.remove(targetName);
1078 }
1079 return this;
1080 }
1081
1082 /**
1083 * Whether to add the given id to the list of target ids to exclude from this
1084 * transition. The <code>exclude</code> parameter specifies whether the target
1085 * should be added to or removed from the excluded list.
1086 *
1087 * <p>Excluding targets is a general mechanism for allowing transitions to run on
1088 * a view hierarchy while skipping target views that should not be part of
1089 * the transition. For example, you may want to avoid animating children
1090 * of a specific ListView or Spinner. Views can be excluded either by their
1091 * id, or by their instance reference, or by the Class of that view
1092 * (eg, {@link Spinner}).</p>
1093 *
1094 * @see #excludeChildren(int, boolean)
1095 * @see #excludeTarget(View, boolean)
1096 * @see #excludeTarget(Class, boolean)
1097 *
1098 * @param targetId The id of a target to ignore when running this transition.
1099 * @param exclude Whether to add the target to or remove the target from the
1100 * current list of excluded targets.
1101 * @return This transition object.
1102 */
1103 public Transition excludeTarget(int targetId, boolean exclude) {
1104 if (targetId >= 0) {
1105 mTargetIdExcludes = excludeObject(mTargetIdExcludes, targetId, exclude);
1106 }
1107 return this;
1108 }
1109
1110 /**
1111 * Whether to add the given transitionName to the list of target transitionNames to exclude
1112 * from this transition. The <code>exclude</code> parameter specifies whether the target
1113 * should be added to or removed from the excluded list.
1114 *
1115 * <p>Excluding targets is a general mechanism for allowing transitions to run on
1116 * a view hierarchy while skipping target views that should not be part of
1117 * the transition. For example, you may want to avoid animating children
1118 * of a specific ListView or Spinner. Views can be excluded by their
1119 * id, their instance reference, their transitionName, or by the Class of that view
1120 * (eg, {@link Spinner}).</p>
1121 *
1122 * @see #excludeTarget(View, boolean)
1123 * @see #excludeTarget(int, boolean)
1124 * @see #excludeTarget(Class, boolean)
1125 *
1126 * @param targetName The name of a target to ignore when running this transition.
1127 * @param exclude Whether to add the target to or remove the target from the
1128 * current list of excluded targets.
1129 * @return This transition object.
1130 */
1131 public Transition excludeTarget(String targetName, boolean exclude) {
1132 mTargetNameExcludes = excludeObject(mTargetNameExcludes, targetName, exclude);
1133 return this;
1134 }
1135
1136 /**
1137 * Whether to add the children of the given id to the list of targets to exclude
1138 * from this transition. The <code>exclude</code> parameter specifies whether
1139 * the children of the target should be added to or removed from the excluded list.
1140 * Excluding children in this way provides a simple mechanism for excluding all
1141 * children of specific targets, rather than individually excluding each
1142 * child individually.
1143 *
1144 * <p>Excluding targets is a general mechanism for allowing transitions to run on
1145 * a view hierarchy while skipping target views that should not be part of
1146 * the transition. For example, you may want to avoid animating children
1147 * of a specific ListView or Spinner. Views can be excluded either by their
1148 * id, or by their instance reference, or by the Class of that view
1149 * (eg, {@link Spinner}).</p>
1150 *
1151 * @see #excludeTarget(int, boolean)
1152 * @see #excludeChildren(View, boolean)
1153 * @see #excludeChildren(Class, boolean)
1154 *
1155 * @param targetId The id of a target whose children should be ignored when running
1156 * this transition.
1157 * @param exclude Whether to add the target to or remove the target from the
1158 * current list of excluded-child targets.
1159 * @return This transition object.
1160 */
1161 public Transition excludeChildren(int targetId, boolean exclude) {
1162 if (targetId >= 0) {
1163 mTargetIdChildExcludes = excludeObject(mTargetIdChildExcludes, targetId, exclude);
1164 }
1165 return this;
1166 }
1167
1168 /**
1169 * Whether to add the given target to the list of targets to exclude from this
1170 * transition. The <code>exclude</code> parameter specifies whether the target
1171 * should be added to or removed from the excluded list.
1172 *
1173 * <p>Excluding targets is a general mechanism for allowing transitions to run on
1174 * a view hierarchy while skipping target views that should not be part of
1175 * the transition. For example, you may want to avoid animating children
1176 * of a specific ListView or Spinner. Views can be excluded either by their
1177 * id, or by their instance reference, or by the Class of that view
1178 * (eg, {@link Spinner}).</p>
1179 *
1180 * @see #excludeChildren(View, boolean)
1181 * @see #excludeTarget(int, boolean)
1182 * @see #excludeTarget(Class, boolean)
1183 *
1184 * @param target The target to ignore when running this transition.
1185 * @param exclude Whether to add the target to or remove the target from the
1186 * current list of excluded targets.
1187 * @return This transition object.
1188 */
1189 public Transition excludeTarget(View target, boolean exclude) {
1190 mTargetExcludes = excludeObject(mTargetExcludes, target, exclude);
1191 return this;
1192 }
1193
1194 /**
1195 * Whether to add the children of given target to the list of target children
1196 * to exclude from this transition. The <code>exclude</code> parameter specifies
1197 * whether the target should be added to or removed from the excluded list.
1198 *
1199 * <p>Excluding targets is a general mechanism for allowing transitions to run on
1200 * a view hierarchy while skipping target views that should not be part of
1201 * the transition. For example, you may want to avoid animating children
1202 * of a specific ListView or Spinner. Views can be excluded either by their
1203 * id, or by their instance reference, or by the Class of that view
1204 * (eg, {@link Spinner}).</p>
1205 *
1206 * @see #excludeTarget(View, boolean)
1207 * @see #excludeChildren(int, boolean)
1208 * @see #excludeChildren(Class, boolean)
1209 *
1210 * @param target The target to ignore when running this transition.
1211 * @param exclude Whether to add the target to or remove the target from the
1212 * current list of excluded targets.
1213 * @return This transition object.
1214 */
1215 public Transition excludeChildren(View target, boolean exclude) {
1216 mTargetChildExcludes = excludeObject(mTargetChildExcludes, target, exclude);
1217 return this;
1218 }
1219
1220 /**
1221 * Utility method to manage the boilerplate code that is the same whether we
1222 * are excluding targets or their children.
1223 */
1224 private static <T> ArrayList<T> excludeObject(ArrayList<T> list, T target, boolean exclude) {
1225 if (target != null) {
1226 if (exclude) {
1227 list = ArrayListManager.add(list, target);
1228 } else {
1229 list = ArrayListManager.remove(list, target);
1230 }
1231 }
1232 return list;
1233 }
1234
1235 /**
1236 * Whether to add the given type to the list of types to exclude from this
1237 * transition. The <code>exclude</code> parameter specifies whether the target
1238 * type should be added to or removed from the excluded list.
1239 *
1240 * <p>Excluding targets is a general mechanism for allowing transitions to run on
1241 * a view hierarchy while skipping target views that should not be part of
1242 * the transition. For example, you may want to avoid animating children
1243 * of a specific ListView or Spinner. Views can be excluded either by their
1244 * id, or by their instance reference, or by the Class of that view
1245 * (eg, {@link Spinner}).</p>
1246 *
1247 * @see #excludeChildren(Class, boolean)
1248 * @see #excludeTarget(int, boolean)
1249 * @see #excludeTarget(View, boolean)
1250 *
1251 * @param type The type to ignore when running this transition.
1252 * @param exclude Whether to add the target type to or remove it from the
1253 * current list of excluded target types.
1254 * @return This transition object.
1255 */
1256 public Transition excludeTarget(Class type, boolean exclude) {
1257 mTargetTypeExcludes = excludeObject(mTargetTypeExcludes, type, exclude);
1258 return this;
1259 }
1260
1261 /**
1262 * Whether to add the given type to the list of types whose children should
1263 * be excluded from this transition. The <code>exclude</code> parameter
1264 * specifies whether the target type should be added to or removed from
1265 * the excluded list.
1266 *
1267 * <p>Excluding targets is a general mechanism for allowing transitions to run on
1268 * a view hierarchy while skipping target views that should not be part of
1269 * the transition. For example, you may want to avoid animating children
1270 * of a specific ListView or Spinner. Views can be excluded either by their
1271 * id, or by their instance reference, or by the Class of that view
1272 * (eg, {@link Spinner}).</p>
1273 *
1274 * @see #excludeTarget(Class, boolean)
1275 * @see #excludeChildren(int, boolean)
1276 * @see #excludeChildren(View, boolean)
1277 *
1278 * @param type The type to ignore when running this transition.
1279 * @param exclude Whether to add the target type to or remove it from the
1280 * current list of excluded target types.
1281 * @return This transition object.
1282 */
1283 public Transition excludeChildren(Class type, boolean exclude) {
1284 mTargetTypeChildExcludes = excludeObject(mTargetTypeChildExcludes, type, exclude);
1285 return this;
1286 }
1287
1288 /**
1289 * Sets the target view instances that this Transition is interested in
1290 * animating. By default, there are no targets, and a Transition will
1291 * listen for changes on every view in the hierarchy below the sceneRoot
1292 * of the Scene being transitioned into. Setting targets constrains
1293 * the Transition to only listen for, and act on, these views.
1294 * All other views will be ignored.
1295 *
1296 * <p>The target list is like the {@link #addTarget(int) targetId}
1297 * list except this list specifies the actual View instances, not the ids
1298 * of the views. This is an important distinction when scene changes involve
1299 * view hierarchies which have been inflated separately; different views may
1300 * share the same id but not actually be the same instance. If the transition
1301 * should treat those views as the same, then {@link #addTarget(int)} should be used
1302 * instead of {@link #addTarget(View)}. If, on the other hand, scene changes involve
1303 * changes all within the same view hierarchy, among views which do not
1304 * necessarily have ids set on them, then the target list of views may be more
1305 * convenient.</p>
1306 *
1307 * @see #addTarget(int)
1308 * @param target A View on which the Transition will act, must be non-null.
1309 * @return The Transition to which the target is added.
1310 * Returning the same object makes it easier to chain calls during
1311 * construction, such as
1312 * <code>transitionSet.addTransitions(new Fade()).addTarget(someView);</code>
1313 */
1314 public Transition addTarget(View target) {
1315 mTargets.add(target);
1316 return this;
1317 }
1318
1319 /**
1320 * Removes the given target from the list of targets that this Transition
1321 * is interested in animating.
1322 *
1323 * @param target The target view, must be non-null.
1324 * @return Transition The Transition from which the target is removed.
1325 * Returning the same object makes it easier to chain calls during
1326 * construction, such as
1327 * <code>transitionSet.addTransitions(new Fade()).removeTarget(someView);</code>
1328 */
1329 public Transition removeTarget(View target) {
1330 if (target != null) {
1331 mTargets.remove(target);
1332 }
1333 return this;
1334 }
1335
1336 /**
1337 * Removes the given target from the list of targets that this Transition
1338 * is interested in animating.
1339 *
1340 * @param target The type of the target view, must be non-null.
1341 * @return Transition The Transition from which the target is removed.
1342 * Returning the same object makes it easier to chain calls during
1343 * construction, such as
1344 * <code>transitionSet.addTransitions(new Fade()).removeTarget(someType);</code>
1345 */
1346 public Transition removeTarget(Class target) {
1347 if (target != null) {
1348 mTargetTypes.remove(target);
1349 }
1350 return this;
1351 }
1352
1353 /**
1354 * Returns the list of target IDs that this transition limits itself to
1355 * tracking and animating. If the list is null or empty for
1356 * {@link #getTargetIds()}, {@link #getTargets()}, {@link #getTargetNames()}, and
1357 * {@link #getTargetTypes()} then this transition is
1358 * not limited to specific views, and will handle changes to any views
1359 * in the hierarchy of a scene change.
1360 *
1361 * @return the list of target IDs
1362 */
1363 public List<Integer> getTargetIds() {
1364 return mTargetIds;
1365 }
1366
1367 /**
1368 * Returns the list of target views that this transition limits itself to
1369 * tracking and animating. If the list is null or empty for
1370 * {@link #getTargetIds()}, {@link #getTargets()}, {@link #getTargetNames()}, and
1371 * {@link #getTargetTypes()} then this transition is
1372 * not limited to specific views, and will handle changes to any views
1373 * in the hierarchy of a scene change.
1374 *
1375 * @return the list of target views
1376 */
1377 public List<View> getTargets() {
1378 return mTargets;
1379 }
1380
1381 /**
1382 * Returns the list of target transitionNames that this transition limits itself to
1383 * tracking and animating. If the list is null or empty for
1384 * {@link #getTargetIds()}, {@link #getTargets()}, {@link #getTargetNames()}, and
1385 * {@link #getTargetTypes()} then this transition is
1386 * not limited to specific views, and will handle changes to any views
1387 * in the hierarchy of a scene change.
1388 *
1389 * @return the list of target transitionNames
1390 */
1391 public List<String> getTargetNames() {
1392 return mTargetNames;
1393 }
1394
1395 /**
1396 * To be removed before L release.
1397 * @hide
1398 */
1399 public List<String> getTargetViewNames() {
1400 return mTargetNames;
1401 }
1402
1403 /**
1404 * Returns the list of target transitionNames that this transition limits itself to
1405 * tracking and animating. If the list is null or empty for
1406 * {@link #getTargetIds()}, {@link #getTargets()}, {@link #getTargetNames()}, and
1407 * {@link #getTargetTypes()} then this transition is
1408 * not limited to specific views, and will handle changes to any views
1409 * in the hierarchy of a scene change.
1410 *
1411 * @return the list of target Types
1412 */
1413 public List<Class> getTargetTypes() {
1414 return mTargetTypes;
1415 }
1416
1417 /**
1418 * Recursive method that captures values for the given view and the
1419 * hierarchy underneath it.
1420 * @param sceneRoot The root of the view hierarchy being captured
1421 * @param start true if this capture is happening before the scene change,
1422 * false otherwise
1423 */
1424 void captureValues(ViewGroup sceneRoot, boolean start) {
1425 clearValues(start);
1426 if ((mTargetIds.size() > 0 || mTargets.size() > 0)
1427 && (mTargetNames == null || mTargetNames.isEmpty())
1428 && (mTargetTypes == null || mTargetTypes.isEmpty())) {
1429 for (int i = 0; i < mTargetIds.size(); ++i) {
1430 int id = mTargetIds.get(i);
1431 View view = sceneRoot.findViewById(id);
1432 if (view != null) {
1433 TransitionValues values = new TransitionValues(view);
1434 if (start) {
1435 captureStartValues(values);
1436 } else {
1437 captureEndValues(values);
1438 }
1439 values.targetedTransitions.add(this);
1440 capturePropagationValues(values);
1441 if (start) {
1442 addViewValues(mStartValues, view, values);
1443 } else {
1444 addViewValues(mEndValues, view, values);
1445 }
1446 }
1447 }
1448 for (int i = 0; i < mTargets.size(); ++i) {
1449 View view = mTargets.get(i);
1450 TransitionValues values = new TransitionValues(view);
1451 if (start) {
1452 captureStartValues(values);
1453 } else {
1454 captureEndValues(values);
1455 }
1456 values.targetedTransitions.add(this);
1457 capturePropagationValues(values);
1458 if (start) {
1459 addViewValues(mStartValues, view, values);
1460 } else {
1461 addViewValues(mEndValues, view, values);
1462 }
1463 }
1464 } else {
1465 captureHierarchy(sceneRoot, start);
1466 }
1467 if (!start && mNameOverrides != null) {
1468 int numOverrides = mNameOverrides.size();
1469 ArrayList<View> overriddenViews = new ArrayList<View>(numOverrides);
1470 for (int i = 0; i < numOverrides; i++) {
1471 String fromName = mNameOverrides.keyAt(i);
1472 overriddenViews.add(mStartValues.nameValues.remove(fromName));
1473 }
1474 for (int i = 0; i < numOverrides; i++) {
1475 View view = overriddenViews.get(i);
1476 if (view != null) {
1477 String toName = mNameOverrides.valueAt(i);
1478 mStartValues.nameValues.put(toName, view);
1479 }
1480 }
1481 }
1482 }
1483
1484 static void addViewValues(TransitionValuesMaps transitionValuesMaps,
1485 View view, TransitionValues transitionValues) {
1486 transitionValuesMaps.viewValues.put(view, transitionValues);
1487 int id = view.getId();
1488 if (id >= 0) {
1489 if (transitionValuesMaps.idValues.indexOfKey(id) >= 0) {
1490 // Duplicate IDs cannot match by ID.
1491 transitionValuesMaps.idValues.put(id, null);
1492 } else {
1493 transitionValuesMaps.idValues.put(id, view);
1494 }
1495 }
1496 String name = view.getTransitionName();
1497 if (name != null) {
1498 if (transitionValuesMaps.nameValues.containsKey(name)) {
1499 // Duplicate transitionNames: cannot match by transitionName.
1500 transitionValuesMaps.nameValues.put(name, null);
1501 } else {
1502 transitionValuesMaps.nameValues.put(name, view);
1503 }
1504 }
1505 if (view.getParent() instanceof ListView) {
1506 ListView listview = (ListView) view.getParent();
1507 if (listview.getAdapter().hasStableIds()) {
1508 int position = listview.getPositionForView(view);
1509 long itemId = listview.getItemIdAtPosition(position);
1510 if (transitionValuesMaps.itemIdValues.indexOfKey(itemId) >= 0) {
1511 // Duplicate item IDs: cannot match by item ID.
1512 View alreadyMatched = transitionValuesMaps.itemIdValues.get(itemId);
1513 if (alreadyMatched != null) {
1514 alreadyMatched.setHasTransientState(false);
1515 transitionValuesMaps.itemIdValues.put(itemId, null);
1516 }
1517 } else {
1518 view.setHasTransientState(true);
1519 transitionValuesMaps.itemIdValues.put(itemId, view);
1520 }
1521 }
1522 }
1523 }
1524
1525 /**
1526 * Clear valuesMaps for specified start/end state
1527 *
1528 * @param start true if the start values should be cleared, false otherwise
1529 */
1530 void clearValues(boolean start) {
1531 if (start) {
1532 mStartValues.viewValues.clear();
1533 mStartValues.idValues.clear();
1534 mStartValues.itemIdValues.clear();
1535 mStartValues.nameValues.clear();
1536 mStartValuesList = null;
1537 } else {
1538 mEndValues.viewValues.clear();
1539 mEndValues.idValues.clear();
1540 mEndValues.itemIdValues.clear();
1541 mEndValues.nameValues.clear();
1542 mEndValuesList = null;
1543 }
1544 }
1545
1546 /**
1547 * Recursive method which captures values for an entire view hierarchy,
1548 * starting at some root view. Transitions without targetIDs will use this
1549 * method to capture values for all possible views.
1550 *
1551 * @param view The view for which to capture values. Children of this View
1552 * will also be captured, recursively down to the leaf nodes.
1553 * @param start true if values are being captured in the start scene, false
1554 * otherwise.
1555 */
1556 private void captureHierarchy(View view, boolean start) {
1557 if (view == null) {
1558 return;
1559 }
1560 int id = view.getId();
1561 if (mTargetIdExcludes != null && mTargetIdExcludes.contains(id)) {
1562 return;
1563 }
1564 if (mTargetExcludes != null && mTargetExcludes.contains(view)) {
1565 return;
1566 }
1567 if (mTargetTypeExcludes != null && view != null) {
1568 int numTypes = mTargetTypeExcludes.size();
1569 for (int i = 0; i < numTypes; ++i) {
1570 if (mTargetTypeExcludes.get(i).isInstance(view)) {
1571 return;
1572 }
1573 }
1574 }
1575 if (view.getParent() instanceof ViewGroup) {
1576 TransitionValues values = new TransitionValues(view);
1577 if (start) {
1578 captureStartValues(values);
1579 } else {
1580 captureEndValues(values);
1581 }
1582 values.targetedTransitions.add(this);
1583 capturePropagationValues(values);
1584 if (start) {
1585 addViewValues(mStartValues, view, values);
1586 } else {
1587 addViewValues(mEndValues, view, values);
1588 }
1589 }
1590 if (view instanceof ViewGroup) {
1591 // Don't traverse child hierarchy if there are any child-excludes on this view
1592 if (mTargetIdChildExcludes != null && mTargetIdChildExcludes.contains(id)) {
1593 return;
1594 }
1595 if (mTargetChildExcludes != null && mTargetChildExcludes.contains(view)) {
1596 return;
1597 }
1598 if (mTargetTypeChildExcludes != null) {
1599 int numTypes = mTargetTypeChildExcludes.size();
1600 for (int i = 0; i < numTypes; ++i) {
1601 if (mTargetTypeChildExcludes.get(i).isInstance(view)) {
1602 return;
1603 }
1604 }
1605 }
1606 ViewGroup parent = (ViewGroup) view;
1607 for (int i = 0; i < parent.getChildCount(); ++i) {
1608 captureHierarchy(parent.getChildAt(i), start);
1609 }
1610 }
1611 }
1612
1613 /**
1614 * This method can be called by transitions to get the TransitionValues for
1615 * any particular view during the transition-playing process. This might be
1616 * necessary, for example, to query the before/after state of related views
1617 * for a given transition.
1618 */
1619 public TransitionValues getTransitionValues(View view, boolean start) {
1620 if (mParent != null) {
1621 return mParent.getTransitionValues(view, start);
1622 }
1623 TransitionValuesMaps valuesMaps = start ? mStartValues : mEndValues;
1624 return valuesMaps.viewValues.get(view);
1625 }
1626
1627 /**
1628 * Find the matched start or end value for a given View. This is only valid
1629 * after playTransition starts. For example, it will be valid in
1630 * {@link #createAnimator(android.view.ViewGroup, TransitionValues, TransitionValues)}, but not
1631 * in {@link #captureStartValues(TransitionValues)}.
1632 *
1633 * @param view The view to find the match for.
1634 * @param viewInStart Is View from the start values or end values.
1635 * @return The matching TransitionValues for view in either start or end values, depending
1636 * on viewInStart or null if there is no match for the given view.
1637 */
1638 TransitionValues getMatchedTransitionValues(View view, boolean viewInStart) {
1639 if (mParent != null) {
1640 return mParent.getMatchedTransitionValues(view, viewInStart);
1641 }
1642 ArrayList<TransitionValues> lookIn = viewInStart ? mStartValuesList : mEndValuesList;
1643 if (lookIn == null) {
1644 return null;
1645 }
1646 int count = lookIn.size();
1647 int index = -1;
1648 for (int i = 0; i < count; i++) {
1649 TransitionValues values = lookIn.get(i);
1650 if (values == null) {
1651 // Null values are always added to the end of the list, so we know to stop now.
1652 return null;
1653 }
1654 if (values.view == view) {
1655 index = i;
1656 break;
1657 }
1658 }
1659 TransitionValues values = null;
1660 if (index >= 0) {
1661 ArrayList<TransitionValues> matchIn = viewInStart ? mEndValuesList : mStartValuesList;
1662 values = matchIn.get(index);
1663 }
1664 return values;
1665 }
1666
1667 /**
1668 * Pauses this transition, sending out calls to {@link
1669 * TransitionListener#onTransitionPause(Transition)} to all listeners
1670 * and pausing all running animators started by this transition.
1671 *
1672 * @hide
1673 */
1674 public void pause(View sceneRoot) {
1675 if (!mEnded) {
1676 ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
1677 int numOldAnims = runningAnimators.size();
1678 if (sceneRoot != null) {
1679 WindowId windowId = sceneRoot.getWindowId();
1680 for (int i = numOldAnims - 1; i >= 0; i--) {
1681 AnimationInfo info = runningAnimators.valueAt(i);
1682 if (info.view != null && windowId != null && windowId.equals(info.windowId)) {
1683 Animator anim = runningAnimators.keyAt(i);
1684 anim.pause();
1685 }
1686 }
1687 }
1688 if (mListeners != null && mListeners.size() > 0) {
1689 ArrayList<TransitionListener> tmpListeners =
1690 (ArrayList<TransitionListener>) mListeners.clone();
1691 int numListeners = tmpListeners.size();
1692 for (int i = 0; i < numListeners; ++i) {
1693 tmpListeners.get(i).onTransitionPause(this);
1694 }
1695 }
1696 mPaused = true;
1697 }
1698 }
1699
1700 /**
1701 * Resumes this transition, sending out calls to {@link
1702 * TransitionListener#onTransitionPause(Transition)} to all listeners
1703 * and pausing all running animators started by this transition.
1704 *
1705 * @hide
1706 */
1707 public void resume(View sceneRoot) {
1708 if (mPaused) {
1709 if (!mEnded) {
1710 ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
1711 int numOldAnims = runningAnimators.size();
1712 WindowId windowId = sceneRoot.getWindowId();
1713 for (int i = numOldAnims - 1; i >= 0; i--) {
1714 AnimationInfo info = runningAnimators.valueAt(i);
1715 if (info.view != null && windowId != null && windowId.equals(info.windowId)) {
1716 Animator anim = runningAnimators.keyAt(i);
1717 anim.resume();
1718 }
1719 }
1720 if (mListeners != null && mListeners.size() > 0) {
1721 ArrayList<TransitionListener> tmpListeners =
1722 (ArrayList<TransitionListener>) mListeners.clone();
1723 int numListeners = tmpListeners.size();
1724 for (int i = 0; i < numListeners; ++i) {
1725 tmpListeners.get(i).onTransitionResume(this);
1726 }
1727 }
1728 }
1729 mPaused = false;
1730 }
1731 }
1732
1733 /**
1734 * Called by TransitionManager to play the transition. This calls
1735 * createAnimators() to set things up and create all of the animations and then
1736 * runAnimations() to actually start the animations.
1737 */
1738 void playTransition(ViewGroup sceneRoot) {
1739 mStartValuesList = new ArrayList<TransitionValues>();
1740 mEndValuesList = new ArrayList<TransitionValues>();
1741 matchStartAndEnd(mStartValues, mEndValues);
1742
1743 ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
1744 int numOldAnims = runningAnimators.size();
1745 WindowId windowId = sceneRoot.getWindowId();
1746 for (int i = numOldAnims - 1; i >= 0; i--) {
1747 Animator anim = runningAnimators.keyAt(i);
1748 if (anim != null) {
1749 AnimationInfo oldInfo = runningAnimators.get(anim);
1750 if (oldInfo != null && oldInfo.view != null && oldInfo.windowId == windowId) {
1751 TransitionValues oldValues = oldInfo.values;
1752 View oldView = oldInfo.view;
1753 TransitionValues startValues = getTransitionValues(oldView, true);
1754 TransitionValues endValues = getMatchedTransitionValues(oldView, true);
1755 if (startValues == null && endValues == null) {
1756 endValues = mEndValues.viewValues.get(oldView);
1757 }
1758 boolean cancel = (startValues != null || endValues != null) &&
1759 oldInfo.transition.isTransitionRequired(oldValues, endValues);
1760 if (cancel) {
1761 if (anim.isRunning() || anim.isStarted()) {
1762 if (DBG) {
1763 Log.d(LOG_TAG, "Canceling anim " + anim);
1764 }
1765 anim.cancel();
1766 } else {
1767 if (DBG) {
1768 Log.d(LOG_TAG, "removing anim from info list: " + anim);
1769 }
1770 runningAnimators.remove(anim);
1771 }
1772 }
1773 }
1774 }
1775 }
1776
1777 createAnimators(sceneRoot, mStartValues, mEndValues, mStartValuesList, mEndValuesList);
1778 runAnimators();
1779 }
1780
1781 /**
1782 * Returns whether or not the transition should create an Animator, based on the values
1783 * captured during {@link #captureStartValues(TransitionValues)} and
1784 * {@link #captureEndValues(TransitionValues)}. The default implementation compares the
1785 * property values returned from {@link #getTransitionProperties()}, or all property values if
1786 * {@code getTransitionProperties()} returns null. Subclasses may override this method to
1787 * provide logic more specific to the transition implementation.
1788 *
1789 * @param startValues the values from captureStartValues, This may be {@code null} if the
1790 * View did not exist in the start state.
1791 * @param endValues the values from captureEndValues. This may be {@code null} if the View
1792 * did not exist in the end state.
1793 */
1794 public boolean isTransitionRequired(@Nullable TransitionValues startValues,
1795 @Nullable TransitionValues endValues) {
1796 boolean valuesChanged = false;
1797 // if startValues null, then transition didn't care to stash values,
1798 // and won't get canceled
1799 if (startValues != null && endValues != null) {
1800 String[] properties = getTransitionProperties();
1801 if (properties != null) {
1802 int count = properties.length;
1803 for (int i = 0; i < count; i++) {
1804 if (isValueChanged(startValues, endValues, properties[i])) {
1805 valuesChanged = true;
1806 break;
1807 }
1808 }
1809 } else {
1810 for (String key : startValues.values.keySet()) {
1811 if (isValueChanged(startValues, endValues, key)) {
1812 valuesChanged = true;
1813 break;
1814 }
1815 }
1816 }
1817 }
1818 return valuesChanged;
1819 }
1820
1821 private static boolean isValueChanged(TransitionValues oldValues, TransitionValues newValues,
1822 String key) {
1823 if (oldValues.values.containsKey(key) != newValues.values.containsKey(key)) {
1824 // The transition didn't care about this particular value, so we don't care, either.
1825 return false;
1826 }
1827 Object oldValue = oldValues.values.get(key);
1828 Object newValue = newValues.values.get(key);
1829 boolean changed;
1830 if (oldValue == null && newValue == null) {
1831 // both are null
1832 changed = false;
1833 } else if (oldValue == null || newValue == null) {
1834 // one is null
1835 changed = true;
1836 } else {
1837 // neither is null
1838 changed = !oldValue.equals(newValue);
1839 }
1840 if (DBG && changed) {
1841 Log.d(LOG_TAG, "Transition.playTransition: " +
1842 "oldValue != newValue for " + key +
1843 ": old, new = " + oldValue + ", " + newValue);
1844 }
1845 return changed;
1846 }
1847
1848 /**
1849 * This is a utility method used by subclasses to handle standard parts of
1850 * setting up and running an Animator: it sets the {@link #getDuration()
1851 * duration} and the {@link #getStartDelay() startDelay}, starts the
1852 * animation, and, when the animator ends, calls {@link #end()}.
1853 *
1854 * @param animator The Animator to be run during this transition.
1855 *
1856 * @hide
1857 */
1858 protected void animate(Animator animator) {
1859 // TODO: maybe pass auto-end as a boolean parameter?
1860 if (animator == null) {
1861 end();
1862 } else {
1863 if (getDuration() >= 0) {
1864 animator.setDuration(getDuration());
1865 }
1866 if (getStartDelay() >= 0) {
1867 animator.setStartDelay(getStartDelay() + animator.getStartDelay());
1868 }
1869 if (getInterpolator() != null) {
1870 animator.setInterpolator(getInterpolator());
1871 }
1872 animator.addListener(new AnimatorListenerAdapter() {
1873 @Override
1874 public void onAnimationEnd(Animator animation) {
1875 end();
1876 animation.removeListener(this);
1877 }
1878 });
1879 animator.start();
1880 }
1881 }
1882
1883 /**
1884 * This method is called automatically by the transition and
1885 * TransitionSet classes prior to a Transition subclass starting;
1886 * subclasses should not need to call it directly.
1887 *
1888 * @hide
1889 */
1890 protected void start() {
1891 if (mNumInstances == 0) {
1892 if (mListeners != null && mListeners.size() > 0) {
1893 ArrayList<TransitionListener> tmpListeners =
1894 (ArrayList<TransitionListener>) mListeners.clone();
1895 int numListeners = tmpListeners.size();
1896 for (int i = 0; i < numListeners; ++i) {
1897 tmpListeners.get(i).onTransitionStart(this);
1898 }
1899 }
1900 mEnded = false;
1901 }
1902 mNumInstances++;
1903 }
1904
1905 /**
1906 * This method is called automatically by the Transition and
1907 * TransitionSet classes when a transition finishes, either because
1908 * a transition did nothing (returned a null Animator from
1909 * {@link Transition#createAnimator(ViewGroup, TransitionValues,
1910 * TransitionValues)}) or because the transition returned a valid
1911 * Animator and end() was called in the onAnimationEnd()
1912 * callback of the AnimatorListener.
1913 *
1914 * @hide
1915 */
1916 @UnsupportedAppUsage
1917 protected void end() {
1918 --mNumInstances;
1919 if (mNumInstances == 0) {
1920 if (mListeners != null && mListeners.size() > 0) {
1921 ArrayList<TransitionListener> tmpListeners =
1922 (ArrayList<TransitionListener>) mListeners.clone();
1923 int numListeners = tmpListeners.size();
1924 for (int i = 0; i < numListeners; ++i) {
1925 tmpListeners.get(i).onTransitionEnd(this);
1926 }
1927 }
1928 for (int i = 0; i < mStartValues.itemIdValues.size(); ++i) {
1929 View view = mStartValues.itemIdValues.valueAt(i);
1930 if (view != null) {
1931 view.setHasTransientState(false);
1932 }
1933 }
1934 for (int i = 0; i < mEndValues.itemIdValues.size(); ++i) {
1935 View view = mEndValues.itemIdValues.valueAt(i);
1936 if (view != null) {
1937 view.setHasTransientState(false);
1938 }
1939 }
1940 mEnded = true;
1941 }
1942 }
1943
1944 /**
1945 * Force the transition to move to its end state, ending all the animators.
1946 *
1947 * @hide
1948 */
1949 void forceToEnd(ViewGroup sceneRoot) {
1950 final ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
1951 int numOldAnims = runningAnimators.size();
1952 if (sceneRoot == null || numOldAnims == 0) {
1953 return;
1954 }
1955
1956 WindowId windowId = sceneRoot.getWindowId();
1957 final ArrayMap<Animator, AnimationInfo> oldAnimators = new ArrayMap(runningAnimators);
1958 runningAnimators.clear();
1959
1960 for (int i = numOldAnims - 1; i >= 0; i--) {
1961 AnimationInfo info = oldAnimators.valueAt(i);
1962 if (info.view != null && windowId != null && windowId.equals(info.windowId)) {
1963 Animator anim = oldAnimators.keyAt(i);
1964 anim.end();
1965 }
1966 }
1967 }
1968
1969 /**
1970 * This method cancels a transition that is currently running.
1971 *
1972 * @hide
1973 */
1974 @UnsupportedAppUsage
1975 protected void cancel() {
1976 int numAnimators = mCurrentAnimators.size();
1977 for (int i = numAnimators - 1; i >= 0; i--) {
1978 Animator animator = mCurrentAnimators.get(i);
1979 animator.cancel();
1980 }
1981 if (mListeners != null && mListeners.size() > 0) {
1982 ArrayList<TransitionListener> tmpListeners =
1983 (ArrayList<TransitionListener>) mListeners.clone();
1984 int numListeners = tmpListeners.size();
1985 for (int i = 0; i < numListeners; ++i) {
1986 tmpListeners.get(i).onTransitionCancel(this);
1987 }
1988 }
1989 }
1990
1991 /**
1992 * Adds a listener to the set of listeners that are sent events through the
1993 * life of an animation, such as start, repeat, and end.
1994 *
1995 * @param listener the listener to be added to the current set of listeners
1996 * for this animation.
1997 * @return This transition object.
1998 */
1999 public Transition addListener(TransitionListener listener) {
2000 if (mListeners == null) {
2001 mListeners = new ArrayList<TransitionListener>();
2002 }
2003 mListeners.add(listener);
2004 return this;
2005 }
2006
2007 /**
2008 * Removes a listener from the set listening to this animation.
2009 *
2010 * @param listener the listener to be removed from the current set of
2011 * listeners for this transition.
2012 * @return This transition object.
2013 */
2014 public Transition removeListener(TransitionListener listener) {
2015 if (mListeners == null) {
2016 return this;
2017 }
2018 mListeners.remove(listener);
2019 if (mListeners.size() == 0) {
2020 mListeners = null;
2021 }
2022 return this;
2023 }
2024
2025 /**
2026 * Sets the callback to use to find the epicenter of a Transition. A null value indicates
2027 * that there is no epicenter in the Transition and onGetEpicenter() will return null.
2028 * Transitions like {@link android.transition.Explode} use a point or Rect to orient
2029 * the direction of travel. This is called the epicenter of the Transition and is
2030 * typically centered on a touched View. The
2031 * {@link android.transition.Transition.EpicenterCallback} allows a Transition to
2032 * dynamically retrieve the epicenter during a Transition.
2033 * @param epicenterCallback The callback to use to find the epicenter of the Transition.
2034 */
2035 public void setEpicenterCallback(EpicenterCallback epicenterCallback) {
2036 mEpicenterCallback = epicenterCallback;
2037 }
2038
2039 /**
2040 * Returns the callback used to find the epicenter of the Transition.
2041 * Transitions like {@link android.transition.Explode} use a point or Rect to orient
2042 * the direction of travel. This is called the epicenter of the Transition and is
2043 * typically centered on a touched View. The
2044 * {@link android.transition.Transition.EpicenterCallback} allows a Transition to
2045 * dynamically retrieve the epicenter during a Transition.
2046 * @return the callback used to find the epicenter of the Transition.
2047 */
2048 public EpicenterCallback getEpicenterCallback() {
2049 return mEpicenterCallback;
2050 }
2051
2052 /**
2053 * Returns the epicenter as specified by the
2054 * {@link android.transition.Transition.EpicenterCallback} or null if no callback exists.
2055 * @return the epicenter as specified by the
2056 * {@link android.transition.Transition.EpicenterCallback} or null if no callback exists.
2057 * @see #setEpicenterCallback(android.transition.Transition.EpicenterCallback)
2058 */
2059 public Rect getEpicenter() {
2060 if (mEpicenterCallback == null) {
2061 return null;
2062 }
2063 return mEpicenterCallback.onGetEpicenter(this);
2064 }
2065
2066 /**
2067 * Sets the algorithm used to calculate two-dimensional interpolation.
2068 * <p>
2069 * Transitions such as {@link android.transition.ChangeBounds} move Views, typically
2070 * in a straight path between the start and end positions. Applications that desire to
2071 * have these motions move in a curve can change how Views interpolate in two dimensions
2072 * by extending PathMotion and implementing
2073 * {@link android.transition.PathMotion#getPath(float, float, float, float)}.
2074 * </p>
2075 * <p>
2076 * When describing in XML, use a nested XML tag for the path motion. It can be one of
2077 * the built-in tags <code>arcMotion</code> or <code>patternPathMotion</code> or it can
2078 * be a custom PathMotion using <code>pathMotion</code> with the <code>class</code>
2079 * attributed with the fully-described class name. For example:</p>
2080 * <pre>
2081 * {@code
2082 * <changeBounds>
2083 * <pathMotion class="my.app.transition.MyPathMotion"/>
2084 * </changeBounds>
2085 * }
2086 * </pre>
2087 * <p>or</p>
2088 * <pre>
2089 * {@code
2090 * <changeBounds>
2091 * <arcMotion android:minimumHorizontalAngle="15"
2092 * android:minimumVerticalAngle="0" android:maximumAngle="90"/>
2093 * </changeBounds>
2094 * }
2095 * </pre>
2096 *
2097 * @param pathMotion Algorithm object to use for determining how to interpolate in two
2098 * dimensions. If null, a straight-path algorithm will be used.
2099 * @see android.transition.ArcMotion
2100 * @see PatternPathMotion
2101 * @see android.transition.PathMotion
2102 */
2103 public void setPathMotion(PathMotion pathMotion) {
2104 if (pathMotion == null) {
2105 mPathMotion = STRAIGHT_PATH_MOTION;
2106 } else {
2107 mPathMotion = pathMotion;
2108 }
2109 }
2110
2111 /**
2112 * Returns the algorithm object used to interpolate along two dimensions. This is typically
2113 * used to determine the View motion between two points.
2114 *
2115 * <p>
2116 * When describing in XML, use a nested XML tag for the path motion. It can be one of
2117 * the built-in tags <code>arcMotion</code> or <code>patternPathMotion</code> or it can
2118 * be a custom PathMotion using <code>pathMotion</code> with the <code>class</code>
2119 * attributed with the fully-described class name. For example:</p>
2120 * <pre>{@code
2121 * <changeBounds>
2122 * <pathMotion class="my.app.transition.MyPathMotion"/>
2123 * </changeBounds>}
2124 * </pre>
2125 * <p>or</p>
2126 * <pre>{@code
2127 * <changeBounds>
2128 * <arcMotion android:minimumHorizontalAngle="15"
2129 * android:minimumVerticalAngle="0"
2130 * android:maximumAngle="90"/>
2131 * </changeBounds>}
2132 * </pre>
2133 *
2134 * @return The algorithm object used to interpolate along two dimensions.
2135 * @see android.transition.ArcMotion
2136 * @see PatternPathMotion
2137 * @see android.transition.PathMotion
2138 */
2139 public PathMotion getPathMotion() {
2140 return mPathMotion;
2141 }
2142
2143 /**
2144 * Sets the method for determining Animator start delays.
2145 * When a Transition affects several Views like {@link android.transition.Explode} or
2146 * {@link android.transition.Slide}, there may be a desire to have a "wave-front" effect
2147 * such that the Animator start delay depends on position of the View. The
2148 * TransitionPropagation specifies how the start delays are calculated.
2149 * @param transitionPropagation The class used to determine the start delay of
2150 * Animators created by this Transition. A null value
2151 * indicates that no delay should be used.
2152 */
2153 public void setPropagation(TransitionPropagation transitionPropagation) {
2154 mPropagation = transitionPropagation;
2155 }
2156
2157 /**
2158 * Returns the {@link android.transition.TransitionPropagation} used to calculate Animator start
2159 * delays.
2160 * When a Transition affects several Views like {@link android.transition.Explode} or
2161 * {@link android.transition.Slide}, there may be a desire to have a "wave-front" effect
2162 * such that the Animator start delay depends on position of the View. The
2163 * TransitionPropagation specifies how the start delays are calculated.
2164 * @return the {@link android.transition.TransitionPropagation} used to calculate Animator start
2165 * delays. This is null by default.
2166 */
2167 public TransitionPropagation getPropagation() {
2168 return mPropagation;
2169 }
2170
2171 /**
2172 * Captures TransitionPropagation values for the given view and the
2173 * hierarchy underneath it.
2174 */
2175 void capturePropagationValues(TransitionValues transitionValues) {
2176 if (mPropagation != null && !transitionValues.values.isEmpty()) {
2177 String[] propertyNames = mPropagation.getPropagationProperties();
2178 if (propertyNames == null) {
2179 return;
2180 }
2181 boolean containsAll = true;
2182 for (int i = 0; i < propertyNames.length; i++) {
2183 if (!transitionValues.values.containsKey(propertyNames[i])) {
2184 containsAll = false;
2185 break;
2186 }
2187 }
2188 if (!containsAll) {
2189 mPropagation.captureValues(transitionValues);
2190 }
2191 }
2192 }
2193
2194 Transition setSceneRoot(ViewGroup sceneRoot) {
2195 mSceneRoot = sceneRoot;
2196 return this;
2197 }
2198
2199 void setCanRemoveViews(boolean canRemoveViews) {
2200 mCanRemoveViews = canRemoveViews;
2201 }
2202
2203 public boolean canRemoveViews() {
2204 return mCanRemoveViews;
2205 }
2206
2207 /**
2208 * Sets the shared element names -- a mapping from a name at the start state to
2209 * a different name at the end state.
2210 * @hide
2211 */
2212 public void setNameOverrides(ArrayMap<String, String> overrides) {
2213 mNameOverrides = overrides;
2214 }
2215
2216 /** @hide */
2217 public ArrayMap<String, String> getNameOverrides() {
2218 return mNameOverrides;
2219 }
2220
2221 @Override
2222 public String toString() {
2223 return toString("");
2224 }
2225
2226 @Override
2227 public Transition clone() {
2228 Transition clone = null;
2229 try {
2230 clone = (Transition) super.clone();
2231 clone.mAnimators = new ArrayList<Animator>();
2232 clone.mStartValues = new TransitionValuesMaps();
2233 clone.mEndValues = new TransitionValuesMaps();
2234 clone.mStartValuesList = null;
2235 clone.mEndValuesList = null;
2236 } catch (CloneNotSupportedException e) {}
2237
2238 return clone;
2239 }
2240
2241 /**
2242 * Returns the name of this Transition. This name is used internally to distinguish
2243 * between different transitions to determine when interrupting transitions overlap.
2244 * For example, a ChangeBounds running on the same target view as another ChangeBounds
2245 * should determine whether the old transition is animating to different end values
2246 * and should be canceled in favor of the new transition.
2247 *
2248 * <p>By default, a Transition's name is simply the value of {@link Class#getName()},
2249 * but subclasses are free to override and return something different.</p>
2250 *
2251 * @return The name of this transition.
2252 */
2253 public String getName() {
2254 return mName;
2255 }
2256
2257 String toString(String indent) {
2258 String result = indent + getClass().getSimpleName() + "@" +
2259 Integer.toHexString(hashCode()) + ": ";
2260 if (mDuration != -1) {
2261 result += "dur(" + mDuration + ") ";
2262 }
2263 if (mStartDelay != -1) {
2264 result += "dly(" + mStartDelay + ") ";
2265 }
2266 if (mInterpolator != null) {
2267 result += "interp(" + mInterpolator + ") ";
2268 }
2269 if (mTargetIds.size() > 0 || mTargets.size() > 0) {
2270 result += "tgts(";
2271 if (mTargetIds.size() > 0) {
2272 for (int i = 0; i < mTargetIds.size(); ++i) {
2273 if (i > 0) {
2274 result += ", ";
2275 }
2276 result += mTargetIds.get(i);
2277 }
2278 }
2279 if (mTargets.size() > 0) {
2280 for (int i = 0; i < mTargets.size(); ++i) {
2281 if (i > 0) {
2282 result += ", ";
2283 }
2284 result += mTargets.get(i);
2285 }
2286 }
2287 result += ")";
2288 }
2289 return result;
2290 }
2291
2292 /**
2293 * A transition listener receives notifications from a transition.
2294 * Notifications indicate transition lifecycle events.
2295 */
2296 public static interface TransitionListener {
2297 /**
2298 * Notification about the start of the transition.
2299 *
2300 * @param transition The started transition.
2301 */
2302 void onTransitionStart(Transition transition);
2303
2304 /**
2305 * Notification about the end of the transition. Canceled transitions
2306 * will always notify listeners of both the cancellation and end
2307 * events. That is, {@link #onTransitionEnd(Transition)} is always called,
2308 * regardless of whether the transition was canceled or played
2309 * through to completion.
2310 *
2311 * @param transition The transition which reached its end.
2312 */
2313 void onTransitionEnd(Transition transition);
2314
2315 /**
2316 * Notification about the cancellation of the transition.
2317 * Note that cancel may be called by a parent {@link TransitionSet} on
2318 * a child transition which has not yet started. This allows the child
2319 * transition to restore state on target objects which was set at
2320 * {@link #createAnimator(android.view.ViewGroup, TransitionValues, TransitionValues)
2321 * createAnimator()} time.
2322 *
2323 * @param transition The transition which was canceled.
2324 */
2325 void onTransitionCancel(Transition transition);
2326
2327 /**
2328 * Notification when a transition is paused.
2329 * Note that createAnimator() may be called by a parent {@link TransitionSet} on
2330 * a child transition which has not yet started. This allows the child
2331 * transition to restore state on target objects which was set at
2332 * {@link #createAnimator(android.view.ViewGroup, TransitionValues, TransitionValues)
2333 * createAnimator()} time.
2334 *
2335 * @param transition The transition which was paused.
2336 */
2337 void onTransitionPause(Transition transition);
2338
2339 /**
2340 * Notification when a transition is resumed.
2341 * Note that resume() may be called by a parent {@link TransitionSet} on
2342 * a child transition which has not yet started. This allows the child
2343 * transition to restore state which may have changed in an earlier call
2344 * to {@link #onTransitionPause(Transition)}.
2345 *
2346 * @param transition The transition which was resumed.
2347 */
2348 void onTransitionResume(Transition transition);
2349 }
2350
2351 /**
2352 * Holds information about each animator used when a new transition starts
2353 * while other transitions are still running to determine whether a running
2354 * animation should be canceled or a new animation noop'd. The structure holds
2355 * information about the state that an animation is going to, to be compared to
2356 * end state of a new animation.
2357 * @hide
2358 */
2359 public static class AnimationInfo {
2360 public View view;
2361 String name;
2362 TransitionValues values;
2363 WindowId windowId;
2364 Transition transition;
2365
2366 AnimationInfo(View view, String name, Transition transition,
2367 WindowId windowId, TransitionValues values) {
2368 this.view = view;
2369 this.name = name;
2370 this.values = values;
2371 this.windowId = windowId;
2372 this.transition = transition;
2373 }
2374 }
2375
2376 /**
2377 * Utility class for managing typed ArrayLists efficiently. In particular, this
2378 * can be useful for lists that we don't expect to be used often (eg, the exclude
2379 * lists), so we'd like to keep them nulled out by default. This causes the code to
2380 * become tedious, with constant null checks, code to allocate when necessary,
2381 * and code to null out the reference when the list is empty. This class encapsulates
2382 * all of that functionality into simple add()/remove() methods which perform the
2383 * necessary checks, allocation/null-out as appropriate, and return the
2384 * resulting list.
2385 */
2386 private static class ArrayListManager {
2387
2388 /**
2389 * Add the specified item to the list, returning the resulting list.
2390 * The returned list can either the be same list passed in or, if that
2391 * list was null, the new list that was created.
2392 *
2393 * Note that the list holds unique items; if the item already exists in the
2394 * list, the list is not modified.
2395 */
2396 static <T> ArrayList<T> add(ArrayList<T> list, T item) {
2397 if (list == null) {
2398 list = new ArrayList<T>();
2399 }
2400 if (!list.contains(item)) {
2401 list.add(item);
2402 }
2403 return list;
2404 }
2405
2406 /**
2407 * Remove the specified item from the list, returning the resulting list.
2408 * The returned list can either the be same list passed in or, if that
2409 * list becomes empty as a result of the remove(), the new list was created.
2410 */
2411 static <T> ArrayList<T> remove(ArrayList<T> list, T item) {
2412 if (list != null) {
2413 list.remove(item);
2414 if (list.isEmpty()) {
2415 list = null;
2416 }
2417 }
2418 return list;
2419 }
2420 }
2421
2422 /**
2423 * Class to get the epicenter of Transition. Use
2424 * {@link #setEpicenterCallback(android.transition.Transition.EpicenterCallback)} to
2425 * set the callback used to calculate the epicenter of the Transition. Override
2426 * {@link #getEpicenter()} to return the rectangular region in screen coordinates of
2427 * the epicenter of the transition.
2428 * @see #setEpicenterCallback(android.transition.Transition.EpicenterCallback)
2429 */
2430 public static abstract class EpicenterCallback {
2431
2432 /**
2433 * Implementers must override to return the epicenter of the Transition in screen
2434 * coordinates. Transitions like {@link android.transition.Explode} depend upon
2435 * an epicenter for the Transition. In Explode, Views move toward or away from the
2436 * center of the epicenter Rect along the vector between the epicenter and the center
2437 * of the View appearing and disappearing. Some Transitions, such as
2438 * {@link android.transition.Fade} pay no attention to the epicenter.
2439 *
2440 * @param transition The transition for which the epicenter applies.
2441 * @return The Rect region of the epicenter of <code>transition</code> or null if
2442 * there is no epicenter.
2443 */
2444 public abstract Rect onGetEpicenter(Transition transition);
2445 }
2446}