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