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