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