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