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