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