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