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