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 * @hide 804 */ 805 public boolean isValidTarget(View target) { 806 if (target == null) { 807 return false; 808 } 809 int targetId = target.getId(); 810 if (mTargetIdExcludes != null && mTargetIdExcludes.contains(targetId)) { 811 return false; 812 } 813 if (mTargetExcludes != null && mTargetExcludes.contains(target)) { 814 return false; 815 } 816 if (mTargetTypeExcludes != null && target != null) { 817 int numTypes = mTargetTypeExcludes.size(); 818 for (int i = 0; i < numTypes; ++i) { 819 Class type = mTargetTypeExcludes.get(i); 820 if (type.isInstance(target)) { 821 return false; 822 } 823 } 824 } 825 if (mTargetNameExcludes != null && target != null && target.getTransitionName() != null) { 826 if (mTargetNameExcludes.contains(target.getTransitionName())) { 827 return false; 828 } 829 } 830 if (mTargetIds.size() == 0 && mTargets.size() == 0 && 831 (mTargetTypes == null || mTargetTypes.isEmpty()) && 832 (mTargetNames == null || mTargetNames.isEmpty())) { 833 return true; 834 } 835 if (mTargetIds.contains(targetId) || mTargets.contains(target)) { 836 return true; 837 } 838 if (mTargetNames != null && mTargetNames.contains(target.getTransitionName())) { 839 return true; 840 } 841 if (mTargetTypes != null) { 842 for (int i = 0; i < mTargetTypes.size(); ++i) { 843 if (mTargetTypes.get(i).isInstance(target)) { 844 return true; 845 } 846 } 847 } 848 return false; 849 } 850 851 private static ArrayMap<Animator, AnimationInfo> getRunningAnimators() { 852 ArrayMap<Animator, AnimationInfo> runningAnimators = sRunningAnimators.get(); 853 if (runningAnimators == null) { 854 runningAnimators = new ArrayMap<Animator, AnimationInfo>(); 855 sRunningAnimators.set(runningAnimators); 856 } 857 return runningAnimators; 858 } 859 860 /** 861 * This is called internally once all animations have been set up by the 862 * transition hierarchy. 863 * 864 * @hide 865 */ 866 protected void runAnimators() { 867 if (DBG) { 868 Log.d(LOG_TAG, "runAnimators() on " + this); 869 } 870 start(); 871 ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators(); 872 // Now start every Animator that was previously created for this transition 873 for (Animator anim : mAnimators) { 874 if (DBG) { 875 Log.d(LOG_TAG, " anim: " + anim); 876 } 877 if (runningAnimators.containsKey(anim)) { 878 start(); 879 runAnimator(anim, runningAnimators); 880 } 881 } 882 mAnimators.clear(); 883 end(); 884 } 885 886 private void runAnimator(Animator animator, 887 final ArrayMap<Animator, AnimationInfo> runningAnimators) { 888 if (animator != null) { 889 // TODO: could be a single listener instance for all of them since it uses the param 890 animator.addListener(new AnimatorListenerAdapter() { 891 @Override 892 public void onAnimationStart(Animator animation) { 893 mCurrentAnimators.add(animation); 894 } 895 @Override 896 public void onAnimationEnd(Animator animation) { 897 runningAnimators.remove(animation); 898 mCurrentAnimators.remove(animation); 899 } 900 }); 901 animate(animator); 902 } 903 } 904 905 /** 906 * Captures the values in the start scene for the properties that this 907 * transition monitors. These values are then passed as the startValues 908 * structure in a later call to 909 * {@link #createAnimator(ViewGroup, TransitionValues, TransitionValues)}. 910 * The main concern for an implementation is what the 911 * properties are that the transition cares about and what the values are 912 * for all of those properties. The start and end values will be compared 913 * later during the 914 * {@link #createAnimator(android.view.ViewGroup, TransitionValues, TransitionValues)} 915 * method to determine what, if any, animations, should be run. 916 * 917 * <p>Subclasses must implement this method. The method should only be called by the 918 * transition system; it is not intended to be called from external classes.</p> 919 * 920 * @param transitionValues The holder for any values that the Transition 921 * wishes to store. Values are stored in the <code>values</code> field 922 * of this TransitionValues object and are keyed from 923 * a String value. For example, to store a view's rotation value, 924 * a transition might call 925 * <code>transitionValues.values.put("appname:transitionname:rotation", 926 * view.getRotation())</code>. The target view will already be stored in 927 * the transitionValues structure when this method is called. 928 * 929 * @see #captureEndValues(TransitionValues) 930 * @see #createAnimator(ViewGroup, TransitionValues, TransitionValues) 931 */ 932 public abstract void captureStartValues(TransitionValues transitionValues); 933 934 /** 935 * Captures the values in the end scene for the properties that this 936 * transition monitors. These values are then passed as the endValues 937 * structure in a later call to 938 * {@link #createAnimator(ViewGroup, TransitionValues, TransitionValues)}. 939 * The main concern for an implementation is what the 940 * properties are that the transition cares about and what the values are 941 * for all of those properties. The start and end values will be compared 942 * later during the 943 * {@link #createAnimator(android.view.ViewGroup, TransitionValues, TransitionValues)} 944 * method to determine what, if any, animations, should be run. 945 * 946 * <p>Subclasses must implement this method. The method should only be called by the 947 * transition system; it is not intended to be called from external classes.</p> 948 * 949 * @param transitionValues The holder for any values that the Transition 950 * wishes to store. Values are stored in the <code>values</code> field 951 * of this TransitionValues object and are keyed from 952 * a String value. For example, to store a view's rotation value, 953 * a transition might call 954 * <code>transitionValues.values.put("appname:transitionname:rotation", 955 * view.getRotation())</code>. The target view will already be stored in 956 * the transitionValues structure when this method is called. 957 * 958 * @see #captureStartValues(TransitionValues) 959 * @see #createAnimator(ViewGroup, TransitionValues, TransitionValues) 960 */ 961 public abstract void captureEndValues(TransitionValues transitionValues); 962 963 /** 964 * Adds the id of a target view that this Transition is interested in 965 * animating. By default, there are no targetIds, and a Transition will 966 * listen for changes on every view in the hierarchy below the sceneRoot 967 * of the Scene being transitioned into. Setting targetIds constrains 968 * the Transition to only listen for, and act on, views with these IDs. 969 * Views with different IDs, or no IDs whatsoever, will be ignored. 970 * 971 * <p>Note that using ids to specify targets implies that ids should be unique 972 * within the view hierarchy underneath the scene root.</p> 973 * 974 * @see View#getId() 975 * @param targetId The id of a target view, must be a positive number. 976 * @return The Transition to which the targetId is added. 977 * Returning the same object makes it easier to chain calls during 978 * construction, such as 979 * <code>transitionSet.addTransitions(new Fade()).addTarget(someId);</code> 980 */ 981 public Transition addTarget(int targetId) { 982 if (targetId > 0) { 983 mTargetIds.add(targetId); 984 } 985 return this; 986 } 987 988 /** 989 * Adds the transitionName of a target view that this Transition is interested in 990 * animating. By default, there are no targetNames, and a Transition will 991 * listen for changes on every view in the hierarchy below the sceneRoot 992 * of the Scene being transitioned into. Setting targetNames constrains 993 * the Transition to only listen for, and act on, views with these transitionNames. 994 * Views with different transitionNames, or no transitionName whatsoever, will be ignored. 995 * 996 * <p>Note that transitionNames should be unique within the view hierarchy.</p> 997 * 998 * @see android.view.View#getTransitionName() 999 * @param targetName The transitionName of a target view, must be non-null. 1000 * @return The Transition to which the target transitionName is added. 1001 * Returning the same object makes it easier to chain calls during 1002 * construction, such as 1003 * <code>transitionSet.addTransitions(new Fade()).addTarget(someName);</code> 1004 */ 1005 public Transition addTarget(String targetName) { 1006 if (targetName != null) { 1007 if (mTargetNames == null) { 1008 mTargetNames = new ArrayList<String>(); 1009 } 1010 mTargetNames.add(targetName); 1011 } 1012 return this; 1013 } 1014 1015 /** 1016 * Adds the Class of a target view that this Transition is interested in 1017 * animating. By default, there are no targetTypes, and a Transition will 1018 * listen for changes on every view in the hierarchy below the sceneRoot 1019 * of the Scene being transitioned into. Setting targetTypes constrains 1020 * the Transition to only listen for, and act on, views with these classes. 1021 * Views with different classes will be ignored. 1022 * 1023 * <p>Note that any View that can be cast to targetType will be included, so 1024 * if targetType is <code>View.class</code>, all Views will be included.</p> 1025 * 1026 * @see #addTarget(int) 1027 * @see #addTarget(android.view.View) 1028 * @see #excludeTarget(Class, boolean) 1029 * @see #excludeChildren(Class, boolean) 1030 * 1031 * @param targetType The type to include when running this transition. 1032 * @return The Transition to which the target class was added. 1033 * Returning the same object makes it easier to chain calls during 1034 * construction, such as 1035 * <code>transitionSet.addTransitions(new Fade()).addTarget(ImageView.class);</code> 1036 */ 1037 public Transition addTarget(Class targetType) { 1038 if (targetType != null) { 1039 if (mTargetTypes == null) { 1040 mTargetTypes = new ArrayList<Class>(); 1041 } 1042 mTargetTypes.add(targetType); 1043 } 1044 return this; 1045 } 1046 1047 /** 1048 * Removes the given targetId from the list of ids that this Transition 1049 * is interested in animating. 1050 * 1051 * @param targetId The id of a target view, must be a positive number. 1052 * @return The Transition from which the targetId is removed. 1053 * Returning the same object makes it easier to chain calls during 1054 * construction, such as 1055 * <code>transitionSet.addTransitions(new Fade()).removeTargetId(someId);</code> 1056 */ 1057 public Transition removeTarget(int targetId) { 1058 if (targetId > 0) { 1059 mTargetIds.remove((Integer)targetId); 1060 } 1061 return this; 1062 } 1063 1064 /** 1065 * Removes the given targetName from the list of transitionNames that this Transition 1066 * is interested in animating. 1067 * 1068 * @param targetName The transitionName of a target view, must not be null. 1069 * @return The Transition from which the targetName is removed. 1070 * Returning the same object makes it easier to chain calls during 1071 * construction, such as 1072 * <code>transitionSet.addTransitions(new Fade()).removeTargetName(someName);</code> 1073 */ 1074 public Transition removeTarget(String targetName) { 1075 if (targetName != null && mTargetNames != null) { 1076 mTargetNames.remove(targetName); 1077 } 1078 return this; 1079 } 1080 1081 /** 1082 * Whether to add the given id to the list of target ids to exclude from this 1083 * transition. The <code>exclude</code> parameter specifies whether the target 1084 * should be added to or removed from the excluded list. 1085 * 1086 * <p>Excluding targets is a general mechanism for allowing transitions to run on 1087 * a view hierarchy while skipping target views that should not be part of 1088 * the transition. For example, you may want to avoid animating children 1089 * of a specific ListView or Spinner. Views can be excluded either by their 1090 * id, or by their instance reference, or by the Class of that view 1091 * (eg, {@link Spinner}).</p> 1092 * 1093 * @see #excludeChildren(int, boolean) 1094 * @see #excludeTarget(View, boolean) 1095 * @see #excludeTarget(Class, boolean) 1096 * 1097 * @param targetId The id of a target to ignore when running this transition. 1098 * @param exclude Whether to add the target to or remove the target from the 1099 * current list of excluded targets. 1100 * @return This transition object. 1101 */ 1102 public Transition excludeTarget(int targetId, boolean exclude) { 1103 if (targetId >= 0) { 1104 mTargetIdExcludes = excludeObject(mTargetIdExcludes, targetId, exclude); 1105 } 1106 return this; 1107 } 1108 1109 /** 1110 * Whether to add the given transitionName to the list of target transitionNames to exclude 1111 * from this transition. The <code>exclude</code> parameter specifies whether the target 1112 * should be added to or removed from the excluded list. 1113 * 1114 * <p>Excluding targets is a general mechanism for allowing transitions to run on 1115 * a view hierarchy while skipping target views that should not be part of 1116 * the transition. For example, you may want to avoid animating children 1117 * of a specific ListView or Spinner. Views can be excluded by their 1118 * id, their instance reference, their transitionName, or by the Class of that view 1119 * (eg, {@link Spinner}).</p> 1120 * 1121 * @see #excludeTarget(View, boolean) 1122 * @see #excludeTarget(int, boolean) 1123 * @see #excludeTarget(Class, boolean) 1124 * 1125 * @param targetName The name of a target to ignore when running this transition. 1126 * @param exclude Whether to add the target to or remove the target from the 1127 * current list of excluded targets. 1128 * @return This transition object. 1129 */ 1130 public Transition excludeTarget(String targetName, boolean exclude) { 1131 mTargetNameExcludes = excludeObject(mTargetNameExcludes, targetName, exclude); 1132 return this; 1133 } 1134 1135 /** 1136 * Whether to add the children of the given id to the list of targets to exclude 1137 * from this transition. The <code>exclude</code> parameter specifies whether 1138 * the children of the target should be added to or removed from the excluded list. 1139 * Excluding children in this way provides a simple mechanism for excluding all 1140 * children of specific targets, rather than individually excluding each 1141 * child individually. 1142 * 1143 * <p>Excluding targets is a general mechanism for allowing transitions to run on 1144 * a view hierarchy while skipping target views that should not be part of 1145 * the transition. For example, you may want to avoid animating children 1146 * of a specific ListView or Spinner. Views can be excluded either by their 1147 * id, or by their instance reference, or by the Class of that view 1148 * (eg, {@link Spinner}).</p> 1149 * 1150 * @see #excludeTarget(int, boolean) 1151 * @see #excludeChildren(View, boolean) 1152 * @see #excludeChildren(Class, boolean) 1153 * 1154 * @param targetId The id of a target whose children should be ignored when running 1155 * this transition. 1156 * @param exclude Whether to add the target to or remove the target from the 1157 * current list of excluded-child targets. 1158 * @return This transition object. 1159 */ 1160 public Transition excludeChildren(int targetId, boolean exclude) { 1161 if (targetId >= 0) { 1162 mTargetIdChildExcludes = excludeObject(mTargetIdChildExcludes, targetId, exclude); 1163 } 1164 return this; 1165 } 1166 1167 /** 1168 * Whether to add the given target to the list of targets to exclude from this 1169 * transition. The <code>exclude</code> parameter specifies whether the target 1170 * should be added to or removed from the excluded list. 1171 * 1172 * <p>Excluding targets is a general mechanism for allowing transitions to run on 1173 * a view hierarchy while skipping target views that should not be part of 1174 * the transition. For example, you may want to avoid animating children 1175 * of a specific ListView or Spinner. Views can be excluded either by their 1176 * id, or by their instance reference, or by the Class of that view 1177 * (eg, {@link Spinner}).</p> 1178 * 1179 * @see #excludeChildren(View, boolean) 1180 * @see #excludeTarget(int, boolean) 1181 * @see #excludeTarget(Class, boolean) 1182 * 1183 * @param target The target to ignore when running this transition. 1184 * @param exclude Whether to add the target to or remove the target from the 1185 * current list of excluded targets. 1186 * @return This transition object. 1187 */ 1188 public Transition excludeTarget(View target, boolean exclude) { 1189 mTargetExcludes = excludeObject(mTargetExcludes, target, exclude); 1190 return this; 1191 } 1192 1193 /** 1194 * Whether to add the children of given target to the list of target children 1195 * to exclude from this transition. The <code>exclude</code> parameter specifies 1196 * whether the target should be added to or removed from the excluded list. 1197 * 1198 * <p>Excluding targets is a general mechanism for allowing transitions to run on 1199 * a view hierarchy while skipping target views that should not be part of 1200 * the transition. For example, you may want to avoid animating children 1201 * of a specific ListView or Spinner. Views can be excluded either by their 1202 * id, or by their instance reference, or by the Class of that view 1203 * (eg, {@link Spinner}).</p> 1204 * 1205 * @see #excludeTarget(View, boolean) 1206 * @see #excludeChildren(int, boolean) 1207 * @see #excludeChildren(Class, boolean) 1208 * 1209 * @param target The target to ignore when running this transition. 1210 * @param exclude Whether to add the target to or remove the target from the 1211 * current list of excluded targets. 1212 * @return This transition object. 1213 */ 1214 public Transition excludeChildren(View target, boolean exclude) { 1215 mTargetChildExcludes = excludeObject(mTargetChildExcludes, target, exclude); 1216 return this; 1217 } 1218 1219 /** 1220 * Utility method to manage the boilerplate code that is the same whether we 1221 * are excluding targets or their children. 1222 */ 1223 private static <T> ArrayList<T> excludeObject(ArrayList<T> list, T target, boolean exclude) { 1224 if (target != null) { 1225 if (exclude) { 1226 list = ArrayListManager.add(list, target); 1227 } else { 1228 list = ArrayListManager.remove(list, target); 1229 } 1230 } 1231 return list; 1232 } 1233 1234 /** 1235 * Whether to add the given type to the list of types to exclude from this 1236 * transition. The <code>exclude</code> parameter specifies whether the target 1237 * type should be added to or removed from the excluded list. 1238 * 1239 * <p>Excluding targets is a general mechanism for allowing transitions to run on 1240 * a view hierarchy while skipping target views that should not be part of 1241 * the transition. For example, you may want to avoid animating children 1242 * of a specific ListView or Spinner. Views can be excluded either by their 1243 * id, or by their instance reference, or by the Class of that view 1244 * (eg, {@link Spinner}).</p> 1245 * 1246 * @see #excludeChildren(Class, boolean) 1247 * @see #excludeTarget(int, boolean) 1248 * @see #excludeTarget(View, boolean) 1249 * 1250 * @param type The type to ignore when running this transition. 1251 * @param exclude Whether to add the target type to or remove it from the 1252 * current list of excluded target types. 1253 * @return This transition object. 1254 */ 1255 public Transition excludeTarget(Class type, boolean exclude) { 1256 mTargetTypeExcludes = excludeObject(mTargetTypeExcludes, type, exclude); 1257 return this; 1258 } 1259 1260 /** 1261 * Whether to add the given type to the list of types whose children should 1262 * be excluded from this transition. The <code>exclude</code> parameter 1263 * specifies whether the target type should be added to or removed from 1264 * the excluded list. 1265 * 1266 * <p>Excluding targets is a general mechanism for allowing transitions to run on 1267 * a view hierarchy while skipping target views that should not be part of 1268 * the transition. For example, you may want to avoid animating children 1269 * of a specific ListView or Spinner. Views can be excluded either by their 1270 * id, or by their instance reference, or by the Class of that view 1271 * (eg, {@link Spinner}).</p> 1272 * 1273 * @see #excludeTarget(Class, boolean) 1274 * @see #excludeChildren(int, boolean) 1275 * @see #excludeChildren(View, boolean) 1276 * 1277 * @param type The type to ignore when running this transition. 1278 * @param exclude Whether to add the target type to or remove it from the 1279 * current list of excluded target types. 1280 * @return This transition object. 1281 */ 1282 public Transition excludeChildren(Class type, boolean exclude) { 1283 mTargetTypeChildExcludes = excludeObject(mTargetTypeChildExcludes, type, exclude); 1284 return this; 1285 } 1286 1287 /** 1288 * Sets the target view instances that this Transition is interested in 1289 * animating. By default, there are no targets, and a Transition will 1290 * listen for changes on every view in the hierarchy below the sceneRoot 1291 * of the Scene being transitioned into. Setting targets constrains 1292 * the Transition to only listen for, and act on, these views. 1293 * All other views will be ignored. 1294 * 1295 * <p>The target list is like the {@link #addTarget(int) targetId} 1296 * list except this list specifies the actual View instances, not the ids 1297 * of the views. This is an important distinction when scene changes involve 1298 * view hierarchies which have been inflated separately; different views may 1299 * share the same id but not actually be the same instance. If the transition 1300 * should treat those views as the same, then {@link #addTarget(int)} should be used 1301 * instead of {@link #addTarget(View)}. If, on the other hand, scene changes involve 1302 * changes all within the same view hierarchy, among views which do not 1303 * necessarily have ids set on them, then the target list of views may be more 1304 * convenient.</p> 1305 * 1306 * @see #addTarget(int) 1307 * @param target A View on which the Transition will act, must be non-null. 1308 * @return The Transition to which the target is added. 1309 * Returning the same object makes it easier to chain calls during 1310 * construction, such as 1311 * <code>transitionSet.addTransitions(new Fade()).addTarget(someView);</code> 1312 */ 1313 public Transition addTarget(View target) { 1314 mTargets.add(target); 1315 return this; 1316 } 1317 1318 /** 1319 * Removes the given target from the list of targets that this Transition 1320 * is interested in animating. 1321 * 1322 * @param target The target view, must be non-null. 1323 * @return Transition The Transition from which the target is removed. 1324 * Returning the same object makes it easier to chain calls during 1325 * construction, such as 1326 * <code>transitionSet.addTransitions(new Fade()).removeTarget(someView);</code> 1327 */ 1328 public Transition removeTarget(View target) { 1329 if (target != null) { 1330 mTargets.remove(target); 1331 } 1332 return this; 1333 } 1334 1335 /** 1336 * Removes the given target from the list of targets that this Transition 1337 * is interested in animating. 1338 * 1339 * @param target The type of the target view, must be non-null. 1340 * @return Transition The Transition from which the target is removed. 1341 * Returning the same object makes it easier to chain calls during 1342 * construction, such as 1343 * <code>transitionSet.addTransitions(new Fade()).removeTarget(someType);</code> 1344 */ 1345 public Transition removeTarget(Class target) { 1346 if (target != null) { 1347 mTargetTypes.remove(target); 1348 } 1349 return this; 1350 } 1351 1352 /** 1353 * Returns the list of target IDs that this transition limits itself to 1354 * tracking and animating. If the list is null or empty for 1355 * {@link #getTargetIds()}, {@link #getTargets()}, {@link #getTargetNames()}, and 1356 * {@link #getTargetTypes()} then this transition is 1357 * not limited to specific views, and will handle changes to any views 1358 * in the hierarchy of a scene change. 1359 * 1360 * @return the list of target IDs 1361 */ 1362 public List<Integer> getTargetIds() { 1363 return mTargetIds; 1364 } 1365 1366 /** 1367 * Returns the list of target views that this transition limits itself to 1368 * tracking and animating. If the list is null or empty for 1369 * {@link #getTargetIds()}, {@link #getTargets()}, {@link #getTargetNames()}, and 1370 * {@link #getTargetTypes()} then this transition is 1371 * not limited to specific views, and will handle changes to any views 1372 * in the hierarchy of a scene change. 1373 * 1374 * @return the list of target views 1375 */ 1376 public List<View> getTargets() { 1377 return mTargets; 1378 } 1379 1380 /** 1381 * Returns the list of target transitionNames that this transition limits itself to 1382 * tracking and animating. If the list is null or empty for 1383 * {@link #getTargetIds()}, {@link #getTargets()}, {@link #getTargetNames()}, and 1384 * {@link #getTargetTypes()} then this transition is 1385 * not limited to specific views, and will handle changes to any views 1386 * in the hierarchy of a scene change. 1387 * 1388 * @return the list of target transitionNames 1389 */ 1390 public List<String> getTargetNames() { 1391 return mTargetNames; 1392 } 1393 1394 /** 1395 * To be removed before L release. 1396 * @hide 1397 */ 1398 public List<String> getTargetViewNames() { 1399 return mTargetNames; 1400 } 1401 1402 /** 1403 * Returns the list of target transitionNames that this transition limits itself to 1404 * tracking and animating. If the list is null or empty for 1405 * {@link #getTargetIds()}, {@link #getTargets()}, {@link #getTargetNames()}, and 1406 * {@link #getTargetTypes()} then this transition is 1407 * not limited to specific views, and will handle changes to any views 1408 * in the hierarchy of a scene change. 1409 * 1410 * @return the list of target Types 1411 */ 1412 public List<Class> getTargetTypes() { 1413 return mTargetTypes; 1414 } 1415 1416 /** 1417 * Recursive method that captures values for the given view and the 1418 * hierarchy underneath it. 1419 * @param sceneRoot The root of the view hierarchy being captured 1420 * @param start true if this capture is happening before the scene change, 1421 * false otherwise 1422 */ 1423 void captureValues(ViewGroup sceneRoot, boolean start) { 1424 clearValues(start); 1425 if ((mTargetIds.size() > 0 || mTargets.size() > 0) 1426 && (mTargetNames == null || mTargetNames.isEmpty()) 1427 && (mTargetTypes == null || mTargetTypes.isEmpty())) { 1428 for (int i = 0; i < mTargetIds.size(); ++i) { 1429 int id = mTargetIds.get(i); 1430 View view = sceneRoot.findViewById(id); 1431 if (view != null) { 1432 TransitionValues values = new TransitionValues(); 1433 values.view = view; 1434 if (start) { 1435 captureStartValues(values); 1436 } else { 1437 captureEndValues(values); 1438 } 1439 values.targetedTransitions.add(this); 1440 capturePropagationValues(values); 1441 if (start) { 1442 addViewValues(mStartValues, view, values); 1443 } else { 1444 addViewValues(mEndValues, view, values); 1445 } 1446 } 1447 } 1448 for (int i = 0; i < mTargets.size(); ++i) { 1449 View view = mTargets.get(i); 1450 TransitionValues values = new TransitionValues(); 1451 values.view = view; 1452 if (start) { 1453 captureStartValues(values); 1454 } else { 1455 captureEndValues(values); 1456 } 1457 values.targetedTransitions.add(this); 1458 capturePropagationValues(values); 1459 if (start) { 1460 addViewValues(mStartValues, view, values); 1461 } else { 1462 addViewValues(mEndValues, view, values); 1463 } 1464 } 1465 } else { 1466 captureHierarchy(sceneRoot, start); 1467 } 1468 if (!start && mNameOverrides != null) { 1469 int numOverrides = mNameOverrides.size(); 1470 ArrayList<View> overriddenViews = new ArrayList<View>(numOverrides); 1471 for (int i = 0; i < numOverrides; i++) { 1472 String fromName = mNameOverrides.keyAt(i); 1473 overriddenViews.add(mStartValues.nameValues.remove(fromName)); 1474 } 1475 for (int i = 0; i < numOverrides; i++) { 1476 View view = overriddenViews.get(i); 1477 if (view != null) { 1478 String toName = mNameOverrides.valueAt(i); 1479 mStartValues.nameValues.put(toName, view); 1480 } 1481 } 1482 } 1483 } 1484 1485 static void addViewValues(TransitionValuesMaps transitionValuesMaps, 1486 View view, TransitionValues transitionValues) { 1487 transitionValuesMaps.viewValues.put(view, transitionValues); 1488 int id = view.getId(); 1489 if (id >= 0) { 1490 if (transitionValuesMaps.idValues.indexOfKey(id) >= 0) { 1491 // Duplicate IDs cannot match by ID. 1492 transitionValuesMaps.idValues.put(id, null); 1493 } else { 1494 transitionValuesMaps.idValues.put(id, view); 1495 } 1496 } 1497 String name = view.getTransitionName(); 1498 if (name != null) { 1499 if (transitionValuesMaps.nameValues.containsKey(name)) { 1500 // Duplicate transitionNames: cannot match by transitionName. 1501 transitionValuesMaps.nameValues.put(name, null); 1502 } else { 1503 transitionValuesMaps.nameValues.put(name, view); 1504 } 1505 } 1506 if (view.getParent() instanceof ListView) { 1507 ListView listview = (ListView) view.getParent(); 1508 if (listview.getAdapter().hasStableIds()) { 1509 int position = listview.getPositionForView(view); 1510 long itemId = listview.getItemIdAtPosition(position); 1511 if (transitionValuesMaps.itemIdValues.indexOfKey(itemId) >= 0) { 1512 // Duplicate item IDs: cannot match by item ID. 1513 View alreadyMatched = transitionValuesMaps.itemIdValues.get(itemId); 1514 if (alreadyMatched != null) { 1515 alreadyMatched.setHasTransientState(false); 1516 transitionValuesMaps.itemIdValues.put(itemId, null); 1517 } 1518 } else { 1519 view.setHasTransientState(true); 1520 transitionValuesMaps.itemIdValues.put(itemId, view); 1521 } 1522 } 1523 } 1524 } 1525 1526 /** 1527 * Clear valuesMaps for specified start/end state 1528 * 1529 * @param start true if the start values should be cleared, false otherwise 1530 */ 1531 void clearValues(boolean start) { 1532 if (start) { 1533 mStartValues.viewValues.clear(); 1534 mStartValues.idValues.clear(); 1535 mStartValues.itemIdValues.clear(); 1536 mStartValues.nameValues.clear(); 1537 mStartValuesList = null; 1538 } else { 1539 mEndValues.viewValues.clear(); 1540 mEndValues.idValues.clear(); 1541 mEndValues.itemIdValues.clear(); 1542 mEndValues.nameValues.clear(); 1543 mEndValuesList = null; 1544 } 1545 } 1546 1547 /** 1548 * Recursive method which captures values for an entire view hierarchy, 1549 * starting at some root view. Transitions without targetIDs will use this 1550 * method to capture values for all possible views. 1551 * 1552 * @param view The view for which to capture values. Children of this View 1553 * will also be captured, recursively down to the leaf nodes. 1554 * @param start true if values are being captured in the start scene, false 1555 * otherwise. 1556 */ 1557 private void captureHierarchy(View view, boolean start) { 1558 if (view == null) { 1559 return; 1560 } 1561 int id = view.getId(); 1562 if (mTargetIdExcludes != null && mTargetIdExcludes.contains(id)) { 1563 return; 1564 } 1565 if (mTargetExcludes != null && mTargetExcludes.contains(view)) { 1566 return; 1567 } 1568 if (mTargetTypeExcludes != null && view != null) { 1569 int numTypes = mTargetTypeExcludes.size(); 1570 for (int i = 0; i < numTypes; ++i) { 1571 if (mTargetTypeExcludes.get(i).isInstance(view)) { 1572 return; 1573 } 1574 } 1575 } 1576 if (view.getParent() instanceof ViewGroup) { 1577 TransitionValues values = new TransitionValues(); 1578 values.view = view; 1579 if (start) { 1580 captureStartValues(values); 1581 } else { 1582 captureEndValues(values); 1583 } 1584 values.targetedTransitions.add(this); 1585 capturePropagationValues(values); 1586 if (start) { 1587 addViewValues(mStartValues, view, values); 1588 } else { 1589 addViewValues(mEndValues, view, values); 1590 } 1591 } 1592 if (view instanceof ViewGroup) { 1593 // Don't traverse child hierarchy if there are any child-excludes on this view 1594 if (mTargetIdChildExcludes != null && mTargetIdChildExcludes.contains(id)) { 1595 return; 1596 } 1597 if (mTargetChildExcludes != null && mTargetChildExcludes.contains(view)) { 1598 return; 1599 } 1600 if (mTargetTypeChildExcludes != null) { 1601 int numTypes = mTargetTypeChildExcludes.size(); 1602 for (int i = 0; i < numTypes; ++i) { 1603 if (mTargetTypeChildExcludes.get(i).isInstance(view)) { 1604 return; 1605 } 1606 } 1607 } 1608 ViewGroup parent = (ViewGroup) view; 1609 for (int i = 0; i < parent.getChildCount(); ++i) { 1610 captureHierarchy(parent.getChildAt(i), start); 1611 } 1612 } 1613 } 1614 1615 /** 1616 * This method can be called by transitions to get the TransitionValues for 1617 * any particular view during the transition-playing process. This might be 1618 * necessary, for example, to query the before/after state of related views 1619 * for a given transition. 1620 */ 1621 public TransitionValues getTransitionValues(View view, boolean start) { 1622 if (mParent != null) { 1623 return mParent.getTransitionValues(view, start); 1624 } 1625 TransitionValuesMaps valuesMaps = start ? mStartValues : mEndValues; 1626 return valuesMaps.viewValues.get(view); 1627 } 1628 1629 /** 1630 * Find the matched start or end value for a given View. This is only valid 1631 * after playTransition starts. For example, it will be valid in 1632 * {@link #createAnimator(android.view.ViewGroup, TransitionValues, TransitionValues)}, but not 1633 * in {@link #captureStartValues(TransitionValues)}. 1634 * 1635 * @param view The view to find the match for. 1636 * @param viewInStart Is View from the start values or end values. 1637 * @return The matching TransitionValues for view in either start or end values, depending 1638 * on viewInStart or null if there is no match for the given view. 1639 */ 1640 TransitionValues getMatchedTransitionValues(View view, boolean viewInStart) { 1641 if (mParent != null) { 1642 return mParent.getMatchedTransitionValues(view, viewInStart); 1643 } 1644 ArrayList<TransitionValues> lookIn = viewInStart ? mStartValuesList : mEndValuesList; 1645 if (lookIn == null) { 1646 return null; 1647 } 1648 int count = lookIn.size(); 1649 int index = -1; 1650 for (int i = 0; i < count; i++) { 1651 TransitionValues values = lookIn.get(i); 1652 if (values == null) { 1653 // Null values are always added to the end of the list, so we know to stop now. 1654 return null; 1655 } 1656 if (values.view == view) { 1657 index = i; 1658 break; 1659 } 1660 } 1661 TransitionValues values = null; 1662 if (index >= 0) { 1663 ArrayList<TransitionValues> matchIn = viewInStart ? mEndValuesList : mStartValuesList; 1664 values = matchIn.get(index); 1665 } 1666 return values; 1667 } 1668 1669 /** 1670 * Pauses this transition, sending out calls to {@link 1671 * TransitionListener#onTransitionPause(Transition)} to all listeners 1672 * and pausing all running animators started by this transition. 1673 * 1674 * @hide 1675 */ 1676 public void pause(View sceneRoot) { 1677 if (!mEnded) { 1678 ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators(); 1679 int numOldAnims = runningAnimators.size(); 1680 if (sceneRoot != null) { 1681 WindowId windowId = sceneRoot.getWindowId(); 1682 for (int i = numOldAnims - 1; i >= 0; i--) { 1683 AnimationInfo info = runningAnimators.valueAt(i); 1684 if (info.view != null && windowId != null && windowId.equals(info.windowId)) { 1685 Animator anim = runningAnimators.keyAt(i); 1686 anim.pause(); 1687 } 1688 } 1689 } 1690 if (mListeners != null && mListeners.size() > 0) { 1691 ArrayList<TransitionListener> tmpListeners = 1692 (ArrayList<TransitionListener>) mListeners.clone(); 1693 int numListeners = tmpListeners.size(); 1694 for (int i = 0; i < numListeners; ++i) { 1695 tmpListeners.get(i).onTransitionPause(this); 1696 } 1697 } 1698 mPaused = true; 1699 } 1700 } 1701 1702 /** 1703 * Resumes this transition, sending out calls to {@link 1704 * TransitionListener#onTransitionPause(Transition)} to all listeners 1705 * and pausing all running animators started by this transition. 1706 * 1707 * @hide 1708 */ 1709 public void resume(View sceneRoot) { 1710 if (mPaused) { 1711 if (!mEnded) { 1712 ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators(); 1713 int numOldAnims = runningAnimators.size(); 1714 WindowId windowId = sceneRoot.getWindowId(); 1715 for (int i = numOldAnims - 1; i >= 0; i--) { 1716 AnimationInfo info = runningAnimators.valueAt(i); 1717 if (info.view != null && windowId != null && windowId.equals(info.windowId)) { 1718 Animator anim = runningAnimators.keyAt(i); 1719 anim.resume(); 1720 } 1721 } 1722 if (mListeners != null && mListeners.size() > 0) { 1723 ArrayList<TransitionListener> tmpListeners = 1724 (ArrayList<TransitionListener>) mListeners.clone(); 1725 int numListeners = tmpListeners.size(); 1726 for (int i = 0; i < numListeners; ++i) { 1727 tmpListeners.get(i).onTransitionResume(this); 1728 } 1729 } 1730 } 1731 mPaused = false; 1732 } 1733 } 1734 1735 /** 1736 * Called by TransitionManager to play the transition. This calls 1737 * createAnimators() to set things up and create all of the animations and then 1738 * runAnimations() to actually start the animations. 1739 */ 1740 void playTransition(ViewGroup sceneRoot) { 1741 mStartValuesList = new ArrayList<TransitionValues>(); 1742 mEndValuesList = new ArrayList<TransitionValues>(); 1743 matchStartAndEnd(mStartValues, mEndValues); 1744 1745 ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators(); 1746 int numOldAnims = runningAnimators.size(); 1747 WindowId windowId = sceneRoot.getWindowId(); 1748 for (int i = numOldAnims - 1; i >= 0; i--) { 1749 Animator anim = runningAnimators.keyAt(i); 1750 if (anim != null) { 1751 AnimationInfo oldInfo = runningAnimators.get(anim); 1752 if (oldInfo != null && oldInfo.view != null && oldInfo.windowId == windowId) { 1753 TransitionValues oldValues = oldInfo.values; 1754 View oldView = oldInfo.view; 1755 TransitionValues startValues = getTransitionValues(oldView, true); 1756 TransitionValues endValues = getMatchedTransitionValues(oldView, true); 1757 if (startValues == null && endValues == null) { 1758 endValues = mEndValues.viewValues.get(oldView); 1759 } 1760 boolean cancel = (startValues != null || endValues != null) && 1761 oldInfo.transition.isTransitionRequired(oldValues, endValues); 1762 if (cancel) { 1763 if (anim.isRunning() || anim.isStarted()) { 1764 if (DBG) { 1765 Log.d(LOG_TAG, "Canceling anim " + anim); 1766 } 1767 anim.cancel(); 1768 } else { 1769 if (DBG) { 1770 Log.d(LOG_TAG, "removing anim from info list: " + anim); 1771 } 1772 runningAnimators.remove(anim); 1773 } 1774 } 1775 } 1776 } 1777 } 1778 1779 createAnimators(sceneRoot, mStartValues, mEndValues, mStartValuesList, mEndValuesList); 1780 runAnimators(); 1781 } 1782 1783 /** 1784 * Returns whether or not the transition should create an Animator, based on the values 1785 * captured during {@link #captureStartValues(TransitionValues)} and 1786 * {@link #captureEndValues(TransitionValues)}. The default implementation compares the 1787 * property values returned from {@link #getTransitionProperties()}, or all property values if 1788 * {@code getTransitionProperties()} returns null. Subclasses may override this method to 1789 * provide logic more specific to the transition implementation. 1790 * 1791 * @param startValues the values from captureStartValues, This may be {@code null} if the 1792 * View did not exist in the start state. 1793 * @param endValues the values from captureEndValues. This may be {@code null} if the View 1794 * did not exist in the end state. 1795 */ 1796 public boolean isTransitionRequired(@Nullable TransitionValues startValues, 1797 @Nullable TransitionValues endValues) { 1798 boolean valuesChanged = false; 1799 // if startValues null, then transition didn't care to stash values, 1800 // and won't get canceled 1801 if (startValues != null && endValues != null) { 1802 String[] properties = getTransitionProperties(); 1803 if (properties != null) { 1804 int count = properties.length; 1805 for (int i = 0; i < count; i++) { 1806 if (isValueChanged(startValues, endValues, properties[i])) { 1807 valuesChanged = true; 1808 break; 1809 } 1810 } 1811 } else { 1812 for (String key : startValues.values.keySet()) { 1813 if (isValueChanged(startValues, endValues, key)) { 1814 valuesChanged = true; 1815 break; 1816 } 1817 } 1818 } 1819 } 1820 return valuesChanged; 1821 } 1822 1823 private static boolean isValueChanged(TransitionValues oldValues, TransitionValues newValues, 1824 String key) { 1825 if (oldValues.values.containsKey(key) != newValues.values.containsKey(key)) { 1826 // The transition didn't care about this particular value, so we don't care, either. 1827 return false; 1828 } 1829 Object oldValue = oldValues.values.get(key); 1830 Object newValue = newValues.values.get(key); 1831 boolean changed; 1832 if (oldValue == null && newValue == null) { 1833 // both are null 1834 changed = false; 1835 } else if (oldValue == null || newValue == null) { 1836 // one is null 1837 changed = true; 1838 } else { 1839 // neither is null 1840 changed = !oldValue.equals(newValue); 1841 } 1842 if (DBG && changed) { 1843 Log.d(LOG_TAG, "Transition.playTransition: " + 1844 "oldValue != newValue for " + key + 1845 ": old, new = " + oldValue + ", " + newValue); 1846 } 1847 return changed; 1848 } 1849 1850 /** 1851 * This is a utility method used by subclasses to handle standard parts of 1852 * setting up and running an Animator: it sets the {@link #getDuration() 1853 * duration} and the {@link #getStartDelay() startDelay}, starts the 1854 * animation, and, when the animator ends, calls {@link #end()}. 1855 * 1856 * @param animator The Animator to be run during this transition. 1857 * 1858 * @hide 1859 */ 1860 protected void animate(Animator animator) { 1861 // TODO: maybe pass auto-end as a boolean parameter? 1862 if (animator == null) { 1863 end(); 1864 } else { 1865 if (getDuration() >= 0) { 1866 animator.setDuration(getDuration()); 1867 } 1868 if (getStartDelay() >= 0) { 1869 animator.setStartDelay(getStartDelay() + animator.getStartDelay()); 1870 } 1871 if (getInterpolator() != null) { 1872 animator.setInterpolator(getInterpolator()); 1873 } 1874 animator.addListener(new AnimatorListenerAdapter() { 1875 @Override 1876 public void onAnimationEnd(Animator animation) { 1877 end(); 1878 animation.removeListener(this); 1879 } 1880 }); 1881 animator.start(); 1882 } 1883 } 1884 1885 /** 1886 * This method is called automatically by the transition and 1887 * TransitionSet classes prior to a Transition subclass starting; 1888 * subclasses should not need to call it directly. 1889 * 1890 * @hide 1891 */ 1892 protected void start() { 1893 if (mNumInstances == 0) { 1894 if (mListeners != null && mListeners.size() > 0) { 1895 ArrayList<TransitionListener> tmpListeners = 1896 (ArrayList<TransitionListener>) mListeners.clone(); 1897 int numListeners = tmpListeners.size(); 1898 for (int i = 0; i < numListeners; ++i) { 1899 tmpListeners.get(i).onTransitionStart(this); 1900 } 1901 } 1902 mEnded = false; 1903 } 1904 mNumInstances++; 1905 } 1906 1907 /** 1908 * This method is called automatically by the Transition and 1909 * TransitionSet classes when a transition finishes, either because 1910 * a transition did nothing (returned a null Animator from 1911 * {@link Transition#createAnimator(ViewGroup, TransitionValues, 1912 * TransitionValues)}) or because the transition returned a valid 1913 * Animator and end() was called in the onAnimationEnd() 1914 * callback of the AnimatorListener. 1915 * 1916 * @hide 1917 */ 1918 protected void end() { 1919 --mNumInstances; 1920 if (mNumInstances == 0) { 1921 if (mListeners != null && mListeners.size() > 0) { 1922 ArrayList<TransitionListener> tmpListeners = 1923 (ArrayList<TransitionListener>) mListeners.clone(); 1924 int numListeners = tmpListeners.size(); 1925 for (int i = 0; i < numListeners; ++i) { 1926 tmpListeners.get(i).onTransitionEnd(this); 1927 } 1928 } 1929 for (int i = 0; i < mStartValues.itemIdValues.size(); ++i) { 1930 View view = mStartValues.itemIdValues.valueAt(i); 1931 if (view != null) { 1932 view.setHasTransientState(false); 1933 } 1934 } 1935 for (int i = 0; i < mEndValues.itemIdValues.size(); ++i) { 1936 View view = mEndValues.itemIdValues.valueAt(i); 1937 if (view != null) { 1938 view.setHasTransientState(false); 1939 } 1940 } 1941 mEnded = true; 1942 } 1943 } 1944 1945 /** 1946 * Force the transition to move to its end state, ending all the animators. 1947 * 1948 * @hide 1949 */ 1950 void forceToEnd(ViewGroup sceneRoot) { 1951 ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators(); 1952 int numOldAnims = runningAnimators.size(); 1953 if (sceneRoot != null) { 1954 WindowId windowId = sceneRoot.getWindowId(); 1955 for (int i = numOldAnims - 1; i >= 0; i--) { 1956 AnimationInfo info = runningAnimators.valueAt(i); 1957 if (info.view != null && windowId != null && windowId.equals(info.windowId)) { 1958 Animator anim = runningAnimators.keyAt(i); 1959 anim.end(); 1960 } 1961 } 1962 } 1963 } 1964 1965 /** 1966 * This method cancels a transition that is currently running. 1967 * 1968 * @hide 1969 */ 1970 protected void cancel() { 1971 int numAnimators = mCurrentAnimators.size(); 1972 for (int i = numAnimators - 1; i >= 0; i--) { 1973 Animator animator = mCurrentAnimators.get(i); 1974 animator.cancel(); 1975 } 1976 if (mListeners != null && mListeners.size() > 0) { 1977 ArrayList<TransitionListener> tmpListeners = 1978 (ArrayList<TransitionListener>) mListeners.clone(); 1979 int numListeners = tmpListeners.size(); 1980 for (int i = 0; i < numListeners; ++i) { 1981 tmpListeners.get(i).onTransitionCancel(this); 1982 } 1983 } 1984 } 1985 1986 /** 1987 * Adds a listener to the set of listeners that are sent events through the 1988 * life of an animation, such as start, repeat, and end. 1989 * 1990 * @param listener the listener to be added to the current set of listeners 1991 * for this animation. 1992 * @return This transition object. 1993 */ 1994 public Transition addListener(TransitionListener listener) { 1995 if (mListeners == null) { 1996 mListeners = new ArrayList<TransitionListener>(); 1997 } 1998 mListeners.add(listener); 1999 return this; 2000 } 2001 2002 /** 2003 * Removes a listener from the set listening to this animation. 2004 * 2005 * @param listener the listener to be removed from the current set of 2006 * listeners for this transition. 2007 * @return This transition object. 2008 */ 2009 public Transition removeListener(TransitionListener listener) { 2010 if (mListeners == null) { 2011 return this; 2012 } 2013 mListeners.remove(listener); 2014 if (mListeners.size() == 0) { 2015 mListeners = null; 2016 } 2017 return this; 2018 } 2019 2020 /** 2021 * Sets the callback to use to find the epicenter of a Transition. A null value indicates 2022 * that there is no epicenter in the Transition and onGetEpicenter() will return null. 2023 * Transitions like {@link android.transition.Explode} use a point or Rect to orient 2024 * the direction of travel. This is called the epicenter of the Transition and is 2025 * typically centered on a touched View. The 2026 * {@link android.transition.Transition.EpicenterCallback} allows a Transition to 2027 * dynamically retrieve the epicenter during a Transition. 2028 * @param epicenterCallback The callback to use to find the epicenter of the Transition. 2029 */ 2030 public void setEpicenterCallback(EpicenterCallback epicenterCallback) { 2031 mEpicenterCallback = epicenterCallback; 2032 } 2033 2034 /** 2035 * Returns the callback used to find the epicenter of the Transition. 2036 * Transitions like {@link android.transition.Explode} use a point or Rect to orient 2037 * the direction of travel. This is called the epicenter of the Transition and is 2038 * typically centered on a touched View. The 2039 * {@link android.transition.Transition.EpicenterCallback} allows a Transition to 2040 * dynamically retrieve the epicenter during a Transition. 2041 * @return the callback used to find the epicenter of the Transition. 2042 */ 2043 public EpicenterCallback getEpicenterCallback() { 2044 return mEpicenterCallback; 2045 } 2046 2047 /** 2048 * Returns the epicenter as specified by the 2049 * {@link android.transition.Transition.EpicenterCallback} or null if no callback exists. 2050 * @return the epicenter as specified by the 2051 * {@link android.transition.Transition.EpicenterCallback} or null if no callback exists. 2052 * @see #setEpicenterCallback(android.transition.Transition.EpicenterCallback) 2053 */ 2054 public Rect getEpicenter() { 2055 if (mEpicenterCallback == null) { 2056 return null; 2057 } 2058 return mEpicenterCallback.onGetEpicenter(this); 2059 } 2060 2061 /** 2062 * Sets the algorithm used to calculate two-dimensional interpolation. 2063 * <p> 2064 * Transitions such as {@link android.transition.ChangeBounds} move Views, typically 2065 * in a straight path between the start and end positions. Applications that desire to 2066 * have these motions move in a curve can change how Views interpolate in two dimensions 2067 * by extending PathMotion and implementing 2068 * {@link android.transition.PathMotion#getPath(float, float, float, float)}. 2069 * </p> 2070 * <p> 2071 * When describing in XML, use a nested XML tag for the path motion. It can be one of 2072 * the built-in tags <code>arcMotion</code> or <code>patternPathMotion</code> or it can 2073 * be a custom PathMotion using <code>pathMotion</code> with the <code>class</code> 2074 * attributed with the fully-described class name. For example:</p> 2075 * <pre> 2076 * {@code 2077 * <changeBounds> 2078 * <pathMotion class="my.app.transition.MyPathMotion"/> 2079 * </changeBounds> 2080 * } 2081 * </pre> 2082 * <p>or</p> 2083 * <pre> 2084 * {@code 2085 * <changeBounds> 2086 * <arcMotion android:minimumHorizontalAngle="15" 2087 * android:minimumVerticalAngle="0" android:maximumAngle="90"/> 2088 * </changeBounds> 2089 * } 2090 * </pre> 2091 * 2092 * @param pathMotion Algorithm object to use for determining how to interpolate in two 2093 * dimensions. If null, a straight-path algorithm will be used. 2094 * @see android.transition.ArcMotion 2095 * @see PatternPathMotion 2096 * @see android.transition.PathMotion 2097 */ 2098 public void setPathMotion(PathMotion pathMotion) { 2099 if (pathMotion == null) { 2100 mPathMotion = STRAIGHT_PATH_MOTION; 2101 } else { 2102 mPathMotion = pathMotion; 2103 } 2104 } 2105 2106 /** 2107 * Returns the algorithm object used to interpolate along two dimensions. This is typically 2108 * used to determine the View motion between two points. 2109 * 2110 * <p> 2111 * When describing in XML, use a nested XML tag for the path motion. It can be one of 2112 * the built-in tags <code>arcMotion</code> or <code>patternPathMotion</code> or it can 2113 * be a custom PathMotion using <code>pathMotion</code> with the <code>class</code> 2114 * attributed with the fully-described class name. For example:</p> 2115 * <pre>{@code 2116 * <changeBounds> 2117 * <pathMotion class="my.app.transition.MyPathMotion"/> 2118 * </changeBounds>} 2119 * </pre> 2120 * <p>or</p> 2121 * <pre>{@code 2122 * <changeBounds> 2123 * <arcMotion android:minimumHorizontalAngle="15" 2124 * android:minimumVerticalAngle="0" 2125 * android:maximumAngle="90"/> 2126 * </changeBounds>} 2127 * </pre> 2128 * 2129 * @return The algorithm object used to interpolate along two dimensions. 2130 * @see android.transition.ArcMotion 2131 * @see PatternPathMotion 2132 * @see android.transition.PathMotion 2133 */ 2134 public PathMotion getPathMotion() { 2135 return mPathMotion; 2136 } 2137 2138 /** 2139 * Sets the method for determining Animator start delays. 2140 * When a Transition affects several Views like {@link android.transition.Explode} or 2141 * {@link android.transition.Slide}, there may be a desire to have a "wave-front" effect 2142 * such that the Animator start delay depends on position of the View. The 2143 * TransitionPropagation specifies how the start delays are calculated. 2144 * @param transitionPropagation The class used to determine the start delay of 2145 * Animators created by this Transition. A null value 2146 * indicates that no delay should be used. 2147 */ 2148 public void setPropagation(TransitionPropagation transitionPropagation) { 2149 mPropagation = transitionPropagation; 2150 } 2151 2152 /** 2153 * Returns the {@link android.transition.TransitionPropagation} used to calculate Animator start 2154 * delays. 2155 * When a Transition affects several Views like {@link android.transition.Explode} or 2156 * {@link android.transition.Slide}, there may be a desire to have a "wave-front" effect 2157 * such that the Animator start delay depends on position of the View. The 2158 * TransitionPropagation specifies how the start delays are calculated. 2159 * @return the {@link android.transition.TransitionPropagation} used to calculate Animator start 2160 * delays. This is null by default. 2161 */ 2162 public TransitionPropagation getPropagation() { 2163 return mPropagation; 2164 } 2165 2166 /** 2167 * Captures TransitionPropagation values for the given view and the 2168 * hierarchy underneath it. 2169 */ 2170 void capturePropagationValues(TransitionValues transitionValues) { 2171 if (mPropagation != null && !transitionValues.values.isEmpty()) { 2172 String[] propertyNames = mPropagation.getPropagationProperties(); 2173 if (propertyNames == null) { 2174 return; 2175 } 2176 boolean containsAll = true; 2177 for (int i = 0; i < propertyNames.length; i++) { 2178 if (!transitionValues.values.containsKey(propertyNames[i])) { 2179 containsAll = false; 2180 break; 2181 } 2182 } 2183 if (!containsAll) { 2184 mPropagation.captureValues(transitionValues); 2185 } 2186 } 2187 } 2188 2189 Transition setSceneRoot(ViewGroup sceneRoot) { 2190 mSceneRoot = sceneRoot; 2191 return this; 2192 } 2193 2194 void setCanRemoveViews(boolean canRemoveViews) { 2195 mCanRemoveViews = canRemoveViews; 2196 } 2197 2198 public boolean canRemoveViews() { 2199 return mCanRemoveViews; 2200 } 2201 2202 /** 2203 * Sets the shared element names -- a mapping from a name at the start state to 2204 * a different name at the end state. 2205 * @hide 2206 */ 2207 public void setNameOverrides(ArrayMap<String, String> overrides) { 2208 mNameOverrides = overrides; 2209 } 2210 2211 /** @hide */ 2212 public ArrayMap<String, String> getNameOverrides() { 2213 return mNameOverrides; 2214 } 2215 2216 @Override 2217 public String toString() { 2218 return toString(""); 2219 } 2220 2221 @Override 2222 public Transition clone() { 2223 Transition clone = null; 2224 try { 2225 clone = (Transition) super.clone(); 2226 clone.mAnimators = new ArrayList<Animator>(); 2227 clone.mStartValues = new TransitionValuesMaps(); 2228 clone.mEndValues = new TransitionValuesMaps(); 2229 clone.mStartValuesList = null; 2230 clone.mEndValuesList = null; 2231 } catch (CloneNotSupportedException e) {} 2232 2233 return clone; 2234 } 2235 2236 /** 2237 * Returns the name of this Transition. This name is used internally to distinguish 2238 * between different transitions to determine when interrupting transitions overlap. 2239 * For example, a ChangeBounds running on the same target view as another ChangeBounds 2240 * should determine whether the old transition is animating to different end values 2241 * and should be canceled in favor of the new transition. 2242 * 2243 * <p>By default, a Transition's name is simply the value of {@link Class#getName()}, 2244 * but subclasses are free to override and return something different.</p> 2245 * 2246 * @return The name of this transition. 2247 */ 2248 public String getName() { 2249 return mName; 2250 } 2251 2252 String toString(String indent) { 2253 String result = indent + getClass().getSimpleName() + "@" + 2254 Integer.toHexString(hashCode()) + ": "; 2255 if (mDuration != -1) { 2256 result += "dur(" + mDuration + ") "; 2257 } 2258 if (mStartDelay != -1) { 2259 result += "dly(" + mStartDelay + ") "; 2260 } 2261 if (mInterpolator != null) { 2262 result += "interp(" + mInterpolator + ") "; 2263 } 2264 if (mTargetIds.size() > 0 || mTargets.size() > 0) { 2265 result += "tgts("; 2266 if (mTargetIds.size() > 0) { 2267 for (int i = 0; i < mTargetIds.size(); ++i) { 2268 if (i > 0) { 2269 result += ", "; 2270 } 2271 result += mTargetIds.get(i); 2272 } 2273 } 2274 if (mTargets.size() > 0) { 2275 for (int i = 0; i < mTargets.size(); ++i) { 2276 if (i > 0) { 2277 result += ", "; 2278 } 2279 result += mTargets.get(i); 2280 } 2281 } 2282 result += ")"; 2283 } 2284 return result; 2285 } 2286 2287 /** 2288 * A transition listener receives notifications from a transition. 2289 * Notifications indicate transition lifecycle events. 2290 */ 2291 public static interface TransitionListener { 2292 /** 2293 * Notification about the start of the transition. 2294 * 2295 * @param transition The started transition. 2296 */ 2297 void onTransitionStart(Transition transition); 2298 2299 /** 2300 * Notification about the end of the transition. Canceled transitions 2301 * will always notify listeners of both the cancellation and end 2302 * events. That is, {@link #onTransitionEnd(Transition)} is always called, 2303 * regardless of whether the transition was canceled or played 2304 * through to completion. 2305 * 2306 * @param transition The transition which reached its end. 2307 */ 2308 void onTransitionEnd(Transition transition); 2309 2310 /** 2311 * Notification about the cancellation of the transition. 2312 * Note that cancel may be called by a parent {@link TransitionSet} on 2313 * a child transition which has not yet started. This allows the child 2314 * transition to restore state on target objects which was set at 2315 * {@link #createAnimator(android.view.ViewGroup, TransitionValues, TransitionValues) 2316 * createAnimator()} time. 2317 * 2318 * @param transition The transition which was canceled. 2319 */ 2320 void onTransitionCancel(Transition transition); 2321 2322 /** 2323 * Notification when a transition is paused. 2324 * Note that createAnimator() may be called by a parent {@link TransitionSet} on 2325 * a child transition which has not yet started. This allows the child 2326 * transition to restore state on target objects which was set at 2327 * {@link #createAnimator(android.view.ViewGroup, TransitionValues, TransitionValues) 2328 * createAnimator()} time. 2329 * 2330 * @param transition The transition which was paused. 2331 */ 2332 void onTransitionPause(Transition transition); 2333 2334 /** 2335 * Notification when a transition is resumed. 2336 * Note that resume() may be called by a parent {@link TransitionSet} on 2337 * a child transition which has not yet started. This allows the child 2338 * transition to restore state which may have changed in an earlier call 2339 * to {@link #onTransitionPause(Transition)}. 2340 * 2341 * @param transition The transition which was resumed. 2342 */ 2343 void onTransitionResume(Transition transition); 2344 } 2345 2346 /** 2347 * Holds information about each animator used when a new transition starts 2348 * while other transitions are still running to determine whether a running 2349 * animation should be canceled or a new animation noop'd. The structure holds 2350 * information about the state that an animation is going to, to be compared to 2351 * end state of a new animation. 2352 * @hide 2353 */ 2354 public static class AnimationInfo { 2355 public View view; 2356 String name; 2357 TransitionValues values; 2358 WindowId windowId; 2359 Transition transition; 2360 2361 AnimationInfo(View view, String name, Transition transition, 2362 WindowId windowId, TransitionValues values) { 2363 this.view = view; 2364 this.name = name; 2365 this.values = values; 2366 this.windowId = windowId; 2367 this.transition = transition; 2368 } 2369 } 2370 2371 /** 2372 * Utility class for managing typed ArrayLists efficiently. In particular, this 2373 * can be useful for lists that we don't expect to be used often (eg, the exclude 2374 * lists), so we'd like to keep them nulled out by default. This causes the code to 2375 * become tedious, with constant null checks, code to allocate when necessary, 2376 * and code to null out the reference when the list is empty. This class encapsulates 2377 * all of that functionality into simple add()/remove() methods which perform the 2378 * necessary checks, allocation/null-out as appropriate, and return the 2379 * resulting list. 2380 */ 2381 private static class ArrayListManager { 2382 2383 /** 2384 * Add the specified item to the list, returning the resulting list. 2385 * The returned list can either the be same list passed in or, if that 2386 * list was null, the new list that was created. 2387 * 2388 * Note that the list holds unique items; if the item already exists in the 2389 * list, the list is not modified. 2390 */ 2391 static <T> ArrayList<T> add(ArrayList<T> list, T item) { 2392 if (list == null) { 2393 list = new ArrayList<T>(); 2394 } 2395 if (!list.contains(item)) { 2396 list.add(item); 2397 } 2398 return list; 2399 } 2400 2401 /** 2402 * Remove the specified item from the list, returning the resulting list. 2403 * The returned list can either the be same list passed in or, if that 2404 * list becomes empty as a result of the remove(), the new list was created. 2405 */ 2406 static <T> ArrayList<T> remove(ArrayList<T> list, T item) { 2407 if (list != null) { 2408 list.remove(item); 2409 if (list.isEmpty()) { 2410 list = null; 2411 } 2412 } 2413 return list; 2414 } 2415 } 2416 2417 /** 2418 * Class to get the epicenter of Transition. Use 2419 * {@link #setEpicenterCallback(android.transition.Transition.EpicenterCallback)} to 2420 * set the callback used to calculate the epicenter of the Transition. Override 2421 * {@link #getEpicenter()} to return the rectangular region in screen coordinates of 2422 * the epicenter of the transition. 2423 * @see #setEpicenterCallback(android.transition.Transition.EpicenterCallback) 2424 */ 2425 public static abstract class EpicenterCallback { 2426 2427 /** 2428 * Implementers must override to return the epicenter of the Transition in screen 2429 * coordinates. Transitions like {@link android.transition.Explode} depend upon 2430 * an epicenter for the Transition. In Explode, Views move toward or away from the 2431 * center of the epicenter Rect along the vector between the epicenter and the center 2432 * of the View appearing and disappearing. Some Transitions, such as 2433 * {@link android.transition.Fade} pay no attention to the epicenter. 2434 * 2435 * @param transition The transition for which the epicenter applies. 2436 * @return The Rect region of the epicenter of <code>transition</code> or null if 2437 * there is no epicenter. 2438 */ 2439 public abstract Rect onGetEpicenter(Transition transition); 2440 } 2441} 2442