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