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