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