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