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