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