Transition.java revision c46181a963be736186ae29101625a05b5c1f0ba8
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.util.ArrayMap; 23import android.util.Log; 24import android.util.LongSparseArray; 25import android.util.SparseArray; 26import android.view.SurfaceView; 27import android.view.TextureView; 28import android.view.View; 29import android.view.ViewGroup; 30import android.view.ViewOverlay; 31import android.widget.ListView; 32import android.widget.Spinner; 33 34import java.util.ArrayList; 35import java.util.List; 36 37/** 38 * A Transition holds information about animations that will be run on its 39 * targets during a scene change. Subclasses of this abstract class may 40 * choreograph several child transitions ({@link TransitionSet} or they may 41 * perform custom animations themselves. Any Transition has two main jobs: 42 * (1) capture property values, and (2) play animations based on changes to 43 * captured property values. A custom transition knows what property values 44 * on View objects are of interest to it, and also knows how to animate 45 * changes to those values. For example, the {@link Fade} transition tracks 46 * changes to visibility-related properties and is able to construct and run 47 * animations that fade items in or out based on changes to those properties. 48 * 49 * <p>Note: Transitions may not work correctly with either {@link SurfaceView} 50 * or {@link TextureView}, due to the way that these views are displayed 51 * on the screen. For SurfaceView, the problem is that the view is updated from 52 * a non-UI thread, so changes to the view due to transitions (such as moving 53 * and resizing the view) may be out of sync with the display inside those bounds. 54 * TextureView is more compatible with transitions in general, but some 55 * specific transitions (such as {@link Fade}) may not be compatible 56 * with TextureView because they rely on {@link ViewOverlay} functionality, 57 * which does not currently work with TextureView.</p> 58 * 59 * <p>Transitions can be declared in XML resource files inside the <code>res/transition</code> 60 * directory. Transition resources consist of a tag name for one of the Transition 61 * subclasses along with attributes to define some of the attributes of that transition. 62 * For example, here is a minimal resource file that declares a {@link ChangeBounds} transition:</p> 63 * 64 * {@sample development/samples/ApiDemos/res/transition/changebounds.xml ChangeBounds} 65 * 66 * <p>Note that attributes for the transition are not required, just as they are 67 * optional when declared in code; Transitions created from XML resources will use 68 * the same defaults as their code-created equivalents. Here is a slightly more 69 * elaborate example which declares a {@link TransitionSet} transition with 70 * {@link ChangeBounds} and {@link Fade} child transitions:</p> 71 * 72 * {@sample 73 * development/samples/ApiDemos/res/transition/changebounds_fadeout_sequential.xml TransitionSet} 74 * 75 * <p>In this example, the transitionOrdering attribute is used on the TransitionSet 76 * object to change from the default {@link TransitionSet#ORDERING_TOGETHER} behavior 77 * to be {@link TransitionSet#ORDERING_SEQUENTIAL} instead. Also, the {@link Fade} 78 * transition uses a fadingMode of {@link Fade#OUT} instead of the default 79 * out-in behavior. Finally, note the use of the <code>targets</code> sub-tag, which 80 * takes a set of {@link android.R.styleable#TransitionTarget target} tags, each 81 * of which lists a specific <code>targetId</code> which this transition acts upon. 82 * Use of targets is optional, but can be used to either limit the time spent checking 83 * attributes on unchanging views, or limiting the types of animations run on specific views. 84 * In this case, we know that only the <code>grayscaleContainer</code> will be 85 * disappearing, so we choose to limit the {@link Fade} transition to only that view.</p> 86 * 87 * Further information on XML resource descriptions for transitions can be found for 88 * {@link android.R.styleable#Transition}, {@link android.R.styleable#TransitionSet}, 89 * {@link android.R.styleable#TransitionTarget}, and {@link android.R.styleable#Fade}. 90 * 91 */ 92public abstract class Transition implements Cloneable { 93 94 private static final String LOG_TAG = "Transition"; 95 static final boolean DBG = false; 96 97 private String mName = getClass().getName(); 98 99 long mStartDelay = -1; 100 long mDuration = -1; 101 TimeInterpolator mInterpolator = null; 102 ArrayList<Integer> mTargetIds = new ArrayList<Integer>(); 103 ArrayList<View> mTargets = new ArrayList<View>(); 104 ArrayList<Integer> mTargetIdExcludes = null; 105 ArrayList<View> mTargetExcludes = null; 106 ArrayList<Class> mTargetTypeExcludes = null; 107 ArrayList<Integer> mTargetIdChildExcludes = null; 108 ArrayList<View> mTargetChildExcludes = null; 109 ArrayList<Class> mTargetTypeChildExcludes = null; 110 private TransitionValuesMaps mStartValues = new TransitionValuesMaps(); 111 private TransitionValuesMaps mEndValues = new TransitionValuesMaps(); 112 TransitionSet mParent = null; 113 114 // Per-animator information used for later canceling when future transitions overlap 115 private static ThreadLocal<ArrayMap<Animator, AnimationInfo>> sRunningAnimators = 116 new ThreadLocal<ArrayMap<Animator, AnimationInfo>>(); 117 118 // Scene Root is set at createAnimator() time in the cloned Transition 119 ViewGroup mSceneRoot = null; 120 121 // Track all animators in use in case the transition gets canceled and needs to 122 // cancel running animators 123 private ArrayList<Animator> mCurrentAnimators = new ArrayList<Animator>(); 124 125 // Number of per-target instances of this Transition currently running. This count is 126 // determined by calls to start() and end() 127 int mNumInstances = 0; 128 129 // Whether this transition is currently paused, due to a call to pause() 130 boolean mPaused = false; 131 132 // Whether this transition has ended. Used to avoid pause/resume on transitions 133 // that have completed 134 private boolean mEnded = false; 135 136 // The set of listeners to be sent transition lifecycle events. 137 ArrayList<TransitionListener> mListeners = null; 138 139 // The set of animators collected from calls to createAnimator(), 140 // to be run in runAnimators() 141 ArrayList<Animator> mAnimators = new ArrayList<Animator>(); 142 143 /** 144 * Constructs a Transition object with no target objects. A transition with 145 * no targets defaults to running on all target objects in the scene hierarchy 146 * (if the transition is not contained in a TransitionSet), or all target 147 * objects passed down from its parent (if it is in a TransitionSet). 148 */ 149 public Transition() {} 150 151 /** 152 * Sets the duration of this transition. By default, there is no duration 153 * (indicated by a negative number), which means that the Animator created by 154 * the transition will have its own specified duration. If the duration of a 155 * Transition is set, that duration will override the Animator duration. 156 * 157 * @param duration The length of the animation, in milliseconds. 158 * @return This transition object. 159 * @attr ref android.R.styleable#Transition_duration 160 */ 161 public Transition setDuration(long duration) { 162 mDuration = duration; 163 return this; 164 } 165 166 /** 167 * Returns the duration set on this transition. If no duration has been set, 168 * the returned value will be negative, indicating that resulting animators will 169 * retain their own durations. 170 * 171 * @return The duration set on this transition, in milliseconds, if one has been 172 * set, otherwise returns a negative number. 173 */ 174 public long getDuration() { 175 return mDuration; 176 } 177 178 /** 179 * Sets the startDelay of this transition. By default, there is no delay 180 * (indicated by a negative number), which means that the Animator created by 181 * the transition will have its own specified startDelay. If the delay of a 182 * Transition is set, that delay will override the Animator delay. 183 * 184 * @param startDelay The length of the delay, in milliseconds. 185 * @return This transition object. 186 * @attr ref android.R.styleable#Transition_startDelay 187 */ 188 public Transition setStartDelay(long startDelay) { 189 mStartDelay = startDelay; 190 return this; 191 } 192 193 /** 194 * Returns the startDelay set on this transition. If no startDelay has been set, 195 * the returned value will be negative, indicating that resulting animators will 196 * retain their own startDelays. 197 * 198 * @return The startDelay set on this transition, in milliseconds, if one has 199 * been set, otherwise returns a negative number. 200 */ 201 public long getStartDelay() { 202 return mStartDelay; 203 } 204 205 /** 206 * Sets the interpolator of this transition. By default, the interpolator 207 * is null, which means that the Animator created by the transition 208 * will have its own specified interpolator. If the interpolator of a 209 * Transition is set, that interpolator will override the Animator interpolator. 210 * 211 * @param interpolator The time interpolator used by the transition 212 * @return This transition object. 213 * @attr ref android.R.styleable#Transition_interpolator 214 */ 215 public Transition setInterpolator(TimeInterpolator interpolator) { 216 mInterpolator = interpolator; 217 return this; 218 } 219 220 /** 221 * Returns the interpolator set on this transition. If no interpolator has been set, 222 * the returned value will be null, indicating that resulting animators will 223 * retain their own interpolators. 224 * 225 * @return The interpolator set on this transition, if one has been set, otherwise 226 * returns null. 227 */ 228 public TimeInterpolator getInterpolator() { 229 return mInterpolator; 230 } 231 232 /** 233 * Returns the set of property names used stored in the {@link TransitionValues} 234 * object passed into {@link #captureStartValues(TransitionValues)} that 235 * this transition cares about for the purposes of canceling overlapping animations. 236 * When any transition is started on a given scene root, all transitions 237 * currently running on that same scene root are checked to see whether the 238 * properties on which they based their animations agree with the end values of 239 * the same properties in the new transition. If the end values are not equal, 240 * then the old animation is canceled since the new transition will start a new 241 * animation to these new values. If the values are equal, the old animation is 242 * allowed to continue and no new animation is started for that transition. 243 * 244 * <p>A transition does not need to override this method. However, not doing so 245 * will mean that the cancellation logic outlined in the previous paragraph 246 * will be skipped for that transition, possibly leading to artifacts as 247 * old transitions and new transitions on the same targets run in parallel, 248 * animating views toward potentially different end values.</p> 249 * 250 * @return An array of property names as described in the class documentation for 251 * {@link TransitionValues}. The default implementation returns <code>null</code>. 252 */ 253 public String[] getTransitionProperties() { 254 return null; 255 } 256 257 /** 258 * This method creates an animation that will be run for this transition 259 * given the information in the startValues and endValues structures captured 260 * earlier for the start and end scenes. Subclasses of Transition should override 261 * this method. The method should only be called by the transition system; it is 262 * not intended to be called from external classes. 263 * 264 * <p>This method is called by the transition's parent (all the way up to the 265 * topmost Transition in the hierarchy) with the sceneRoot and start/end 266 * values that the transition may need to set up initial target values 267 * and construct an appropriate animation. For example, if an overall 268 * Transition is a {@link TransitionSet} consisting of several 269 * child transitions in sequence, then some of the child transitions may 270 * want to set initial values on target views prior to the overall 271 * Transition commencing, to put them in an appropriate state for the 272 * delay between that start and the child Transition start time. For 273 * example, a transition that fades an item in may wish to set the starting 274 * alpha value to 0, to avoid it blinking in prior to the transition 275 * actually starting the animation. This is necessary because the scene 276 * change that triggers the Transition will automatically set the end-scene 277 * on all target views, so a Transition that wants to animate from a 278 * different value should set that value prior to returning from this method.</p> 279 * 280 * <p>Additionally, a Transition can perform logic to determine whether 281 * the transition needs to run on the given target and start/end values. 282 * For example, a transition that resizes objects on the screen may wish 283 * to avoid running for views which are not present in either the start 284 * or end scenes.</p> 285 * 286 * <p>If there is an animator created and returned from this method, the 287 * transition mechanism will apply any applicable duration, startDelay, 288 * and interpolator to that animation and start it. A return value of 289 * <code>null</code> indicates that no animation should run. The default 290 * implementation returns null.</p> 291 * 292 * <p>The method is called for every applicable target object, which is 293 * stored in the {@link TransitionValues#view} field.</p> 294 * 295 * 296 * @param sceneRoot The root of the transition hierarchy. 297 * @param startValues The values for a specific target in the start scene. 298 * @param endValues The values for the target in the end scene. 299 * @return A Animator to be started at the appropriate time in the 300 * overall transition for this scene change. A null value means no animation 301 * should be run. 302 */ 303 public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, 304 TransitionValues endValues) { 305 return null; 306 } 307 308 /** 309 * This method, essentially a wrapper around all calls to createAnimator for all 310 * possible target views, is called with the entire set of start/end 311 * values. The implementation in Transition iterates through these lists 312 * and calls {@link #createAnimator(ViewGroup, TransitionValues, TransitionValues)} 313 * with each set of start/end values on this transition. The 314 * TransitionSet subclass overrides this method and delegates it to 315 * each of its children in succession. 316 * 317 * @hide 318 */ 319 protected void createAnimators(ViewGroup sceneRoot, TransitionValuesMaps startValues, 320 TransitionValuesMaps endValues) { 321 if (DBG) { 322 Log.d(LOG_TAG, "createAnimators() for " + this); 323 } 324 ArrayMap<View, TransitionValues> endCopy = 325 new ArrayMap<View, TransitionValues>(endValues.viewValues); 326 SparseArray<TransitionValues> endIdCopy = 327 new SparseArray<TransitionValues>(endValues.idValues.size()); 328 for (int i = 0; i < endValues.idValues.size(); ++i) { 329 int id = endValues.idValues.keyAt(i); 330 endIdCopy.put(id, endValues.idValues.valueAt(i)); 331 } 332 LongSparseArray<TransitionValues> endItemIdCopy = 333 new LongSparseArray<TransitionValues>(endValues.itemIdValues.size()); 334 for (int i = 0; i < endValues.itemIdValues.size(); ++i) { 335 long id = endValues.itemIdValues.keyAt(i); 336 endItemIdCopy.put(id, endValues.itemIdValues.valueAt(i)); 337 } 338 // Walk through the start values, playing everything we find 339 // Remove from the end set as we go 340 ArrayList<TransitionValues> startValuesList = new ArrayList<TransitionValues>(); 341 ArrayList<TransitionValues> endValuesList = new ArrayList<TransitionValues>(); 342 for (View view : startValues.viewValues.keySet()) { 343 TransitionValues start = null; 344 TransitionValues end = null; 345 boolean isInListView = false; 346 if (view.getParent() instanceof ListView) { 347 isInListView = true; 348 } 349 if (!isInListView) { 350 int id = view.getId(); 351 start = startValues.viewValues.get(view) != null ? 352 startValues.viewValues.get(view) : startValues.idValues.get(id); 353 if (endValues.viewValues.get(view) != null) { 354 end = endValues.viewValues.get(view); 355 endCopy.remove(view); 356 } else if (id != View.NO_ID) { 357 end = endValues.idValues.get(id); 358 View removeView = null; 359 for (View viewToRemove : endCopy.keySet()) { 360 if (viewToRemove.getId() == id) { 361 removeView = viewToRemove; 362 } 363 } 364 if (removeView != null) { 365 endCopy.remove(removeView); 366 } 367 } 368 endIdCopy.remove(id); 369 if (isValidTarget(view, id)) { 370 startValuesList.add(start); 371 endValuesList.add(end); 372 } 373 } else { 374 ListView parent = (ListView) view.getParent(); 375 if (parent.getAdapter().hasStableIds()) { 376 int position = parent.getPositionForView(view); 377 long itemId = parent.getItemIdAtPosition(position); 378 start = startValues.itemIdValues.get(itemId); 379 endItemIdCopy.remove(itemId); 380 // TODO: deal with targetIDs for itemIDs for ListView items 381 startValuesList.add(start); 382 endValuesList.add(end); 383 } 384 } 385 } 386 int startItemIdCopySize = startValues.itemIdValues.size(); 387 for (int i = 0; i < startItemIdCopySize; ++i) { 388 long id = startValues.itemIdValues.keyAt(i); 389 if (isValidTarget(null, id)) { 390 TransitionValues start = startValues.itemIdValues.get(id); 391 TransitionValues end = endValues.itemIdValues.get(id); 392 endItemIdCopy.remove(id); 393 startValuesList.add(start); 394 endValuesList.add(end); 395 } 396 } 397 // Now walk through the remains of the end set 398 for (View view : endCopy.keySet()) { 399 int id = view.getId(); 400 if (isValidTarget(view, id)) { 401 TransitionValues start = startValues.viewValues.get(view) != null ? 402 startValues.viewValues.get(view) : startValues.idValues.get(id); 403 TransitionValues end = endCopy.get(view); 404 endIdCopy.remove(id); 405 startValuesList.add(start); 406 endValuesList.add(end); 407 } 408 } 409 int endIdCopySize = endIdCopy.size(); 410 for (int i = 0; i < endIdCopySize; ++i) { 411 int id = endIdCopy.keyAt(i); 412 if (isValidTarget(null, id)) { 413 TransitionValues start = startValues.idValues.get(id); 414 TransitionValues end = endIdCopy.get(id); 415 startValuesList.add(start); 416 endValuesList.add(end); 417 } 418 } 419 int endItemIdCopySize = endItemIdCopy.size(); 420 for (int i = 0; i < endItemIdCopySize; ++i) { 421 long id = endItemIdCopy.keyAt(i); 422 // TODO: Deal with targetIDs and itemIDs 423 TransitionValues start = startValues.itemIdValues.get(id); 424 TransitionValues end = endItemIdCopy.get(id); 425 startValuesList.add(start); 426 endValuesList.add(end); 427 } 428 ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators(); 429 for (int i = 0; i < startValuesList.size(); ++i) { 430 TransitionValues start = startValuesList.get(i); 431 TransitionValues end = endValuesList.get(i); 432 // Only bother trying to animate with values that differ between start/end 433 if (start != null || end != null) { 434 if (start == null || !start.equals(end)) { 435 if (DBG) { 436 View view = (end != null) ? end.view : start.view; 437 Log.d(LOG_TAG, " differing start/end values for view " + 438 view); 439 if (start == null || end == null) { 440 Log.d(LOG_TAG, " " + ((start == null) ? 441 "start null, end non-null" : "start non-null, end null")); 442 } else { 443 for (String key : start.values.keySet()) { 444 Object startValue = start.values.get(key); 445 Object endValue = end.values.get(key); 446 if (startValue != endValue && !startValue.equals(endValue)) { 447 Log.d(LOG_TAG, " " + key + ": start(" + startValue + 448 "), end(" + endValue +")"); 449 } 450 } 451 } 452 } 453 // TODO: what to do about targetIds and itemIds? 454 Animator animator = createAnimator(sceneRoot, start, end); 455 if (animator != null) { 456 // Save animation info for future cancellation purposes 457 View view = null; 458 TransitionValues infoValues = null; 459 if (end != null) { 460 view = end.view; 461 String[] properties = getTransitionProperties(); 462 if (view != null && properties != null && properties.length > 0) { 463 infoValues = new TransitionValues(); 464 infoValues.view = view; 465 TransitionValues newValues = endValues.viewValues.get(view); 466 if (newValues != null) { 467 for (int j = 0; j < properties.length; ++j) { 468 infoValues.values.put(properties[j], 469 newValues.values.get(properties[j])); 470 } 471 } 472 int numExistingAnims = runningAnimators.size(); 473 for (int j = 0; j < numExistingAnims; ++j) { 474 Animator anim = runningAnimators.keyAt(j); 475 AnimationInfo info = runningAnimators.get(anim); 476 if (info.values != null && info.view == view && 477 ((info.name == null && getName() == null) || 478 info.name.equals(getName()))) { 479 if (info.values.equals(infoValues)) { 480 // Favor the old animator 481 animator = null; 482 break; 483 } 484 } 485 } 486 } 487 } else { 488 view = (start != null) ? start.view : null; 489 } 490 if (animator != null) { 491 AnimationInfo info = new AnimationInfo(view, getName(), infoValues); 492 runningAnimators.put(animator, info); 493 mAnimators.add(animator); 494 } 495 } 496 } 497 } 498 } 499 } 500 501 /** 502 * Internal utility method for checking whether a given view/id 503 * is valid for this transition, where "valid" means that either 504 * the Transition has no target/targetId list (the default, in which 505 * cause the transition should act on all views in the hiearchy), or 506 * the given view is in the target list or the view id is in the 507 * targetId list. If the target parameter is null, then the target list 508 * is not checked (this is in the case of ListView items, where the 509 * views are ignored and only the ids are used). 510 */ 511 boolean isValidTarget(View target, long targetId) { 512 if (mTargetIdExcludes != null && mTargetIdExcludes.contains(targetId)) { 513 return false; 514 } 515 if (mTargetExcludes != null && mTargetExcludes.contains(target)) { 516 return false; 517 } 518 if (mTargetTypeExcludes != null && target != null) { 519 int numTypes = mTargetTypeExcludes.size(); 520 for (int i = 0; i < numTypes; ++i) { 521 Class type = mTargetTypeExcludes.get(i); 522 if (type.isInstance(target)) { 523 return false; 524 } 525 } 526 } 527 if (mTargetIds.size() == 0 && mTargets.size() == 0) { 528 return true; 529 } 530 if (mTargetIds.size() > 0) { 531 for (int i = 0; i < mTargetIds.size(); ++i) { 532 if (mTargetIds.get(i) == targetId) { 533 return true; 534 } 535 } 536 } 537 if (target != null && mTargets.size() > 0) { 538 for (int i = 0; i < mTargets.size(); ++i) { 539 if (mTargets.get(i) == target) { 540 return true; 541 } 542 } 543 } 544 return false; 545 } 546 547 private static ArrayMap<Animator, AnimationInfo> getRunningAnimators() { 548 ArrayMap<Animator, AnimationInfo> runningAnimators = sRunningAnimators.get(); 549 if (runningAnimators == null) { 550 runningAnimators = new ArrayMap<Animator, AnimationInfo>(); 551 sRunningAnimators.set(runningAnimators); 552 } 553 return runningAnimators; 554 } 555 556 /** 557 * This is called internally once all animations have been set up by the 558 * transition hierarchy. \ 559 * 560 * @hide 561 */ 562 protected void runAnimators() { 563 if (DBG) { 564 Log.d(LOG_TAG, "runAnimators() on " + this); 565 } 566 start(); 567 ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators(); 568 // Now start every Animator that was previously created for this transition 569 for (Animator anim : mAnimators) { 570 if (DBG) { 571 Log.d(LOG_TAG, " anim: " + anim); 572 } 573 if (runningAnimators.containsKey(anim)) { 574 start(); 575 runAnimator(anim, runningAnimators); 576 } 577 } 578 mAnimators.clear(); 579 end(); 580 } 581 582 private void runAnimator(Animator animator, 583 final ArrayMap<Animator, AnimationInfo> runningAnimators) { 584 if (animator != null) { 585 // TODO: could be a single listener instance for all of them since it uses the param 586 animator.addListener(new AnimatorListenerAdapter() { 587 @Override 588 public void onAnimationStart(Animator animation) { 589 mCurrentAnimators.add(animation); 590 } 591 @Override 592 public void onAnimationEnd(Animator animation) { 593 runningAnimators.remove(animation); 594 mCurrentAnimators.remove(animation); 595 } 596 }); 597 animate(animator); 598 } 599 } 600 601 /** 602 * Captures the values in the start scene for the properties that this 603 * transition monitors. These values are then passed as the startValues 604 * structure in a later call to 605 * {@link #createAnimator(ViewGroup, TransitionValues, TransitionValues)}. 606 * The main concern for an implementation is what the 607 * properties are that the transition cares about and what the values are 608 * for all of those properties. The start and end values will be compared 609 * later during the 610 * {@link #createAnimator(android.view.ViewGroup, TransitionValues, TransitionValues)} 611 * method to determine what, if any, animations, should be run. 612 * 613 * <p>Subclasses must implement this method. The method should only be called by the 614 * transition system; it is not intended to be called from external classes.</p> 615 * 616 * @param transitionValues The holder for any values that the Transition 617 * wishes to store. Values are stored in the <code>values</code> field 618 * of this TransitionValues object and are keyed from 619 * a String value. For example, to store a view's rotation value, 620 * a transition might call 621 * <code>transitionValues.values.put("appname:transitionname:rotation", 622 * view.getRotation())</code>. The target view will already be stored in 623 * the transitionValues structure when this method is called. 624 * 625 * @see #captureEndValues(TransitionValues) 626 * @see #createAnimator(ViewGroup, TransitionValues, TransitionValues) 627 */ 628 public abstract void captureStartValues(TransitionValues transitionValues); 629 630 /** 631 * Captures the values in the end scene for the properties that this 632 * transition monitors. These values are then passed as the endValues 633 * structure in a later call to 634 * {@link #createAnimator(ViewGroup, TransitionValues, TransitionValues)}. 635 * The main concern for an implementation is what the 636 * properties are that the transition cares about and what the values are 637 * for all of those properties. The start and end values will be compared 638 * later during the 639 * {@link #createAnimator(android.view.ViewGroup, TransitionValues, TransitionValues)} 640 * method to determine what, if any, animations, should be run. 641 * 642 * <p>Subclasses must implement this method. The method should only be called by the 643 * transition system; it is not intended to be called from external classes.</p> 644 * 645 * @param transitionValues The holder for any values that the Transition 646 * wishes to store. Values are stored in the <code>values</code> field 647 * of this TransitionValues object and are keyed from 648 * a String value. For example, to store a view's rotation value, 649 * a transition might call 650 * <code>transitionValues.values.put("appname:transitionname:rotation", 651 * view.getRotation())</code>. The target view will already be stored in 652 * the transitionValues structure when this method is called. 653 * 654 * @see #captureStartValues(TransitionValues) 655 * @see #createAnimator(ViewGroup, TransitionValues, TransitionValues) 656 */ 657 public abstract void captureEndValues(TransitionValues transitionValues); 658 659 /** 660 * Adds the id of a target view that this Transition is interested in 661 * animating. By default, there are no targetIds, and a Transition will 662 * listen for changes on every view in the hierarchy below the sceneRoot 663 * of the Scene being transitioned into. Setting targetIds constrains 664 * the Transition to only listen for, and act on, views with these IDs. 665 * Views with different IDs, or no IDs whatsoever, will be ignored. 666 * 667 * <p>Note that using ids to specify targets implies that ids should be unique 668 * within the view hierarchy underneat the scene root.</p> 669 * 670 * @see View#getId() 671 * @param targetId The id of a target view, must be a positive number. 672 * @return The Transition to which the targetId is added. 673 * Returning the same object makes it easier to chain calls during 674 * construction, such as 675 * <code>transitionSet.addTransitions(new Fade()).addTarget(someId);</code> 676 */ 677 public Transition addTarget(int targetId) { 678 if (targetId > 0) { 679 mTargetIds.add(targetId); 680 } 681 return this; 682 } 683 684 /** 685 * Removes the given targetId from the list of ids that this Transition 686 * is interested in animating. 687 * 688 * @param targetId The id of a target view, must be a positive number. 689 * @return The Transition from which the targetId is removed. 690 * Returning the same object makes it easier to chain calls during 691 * construction, such as 692 * <code>transitionSet.addTransitions(new Fade()).removeTargetId(someId);</code> 693 */ 694 public Transition removeTarget(int targetId) { 695 if (targetId > 0) { 696 mTargetIds.remove(targetId); 697 } 698 return this; 699 } 700 701 /** 702 * Whether to add the given id to the list of target ids to exclude from this 703 * transition. The <code>exclude</code> parameter specifies whether the target 704 * should be added to or removed from the excluded list. 705 * 706 * <p>Excluding targets is a general mechanism for allowing transitions to run on 707 * a view hierarchy while skipping target views that should not be part of 708 * the transition. For example, you may want to avoid animating children 709 * of a specific ListView or Spinner. Views can be excluded either by their 710 * id, or by their instance reference, or by the Class of that view 711 * (eg, {@link Spinner}).</p> 712 * 713 * @see #excludeChildren(int, boolean) 714 * @see #excludeTarget(View, boolean) 715 * @see #excludeTarget(Class, boolean) 716 * 717 * @param targetId The id of a target to ignore when running this transition. 718 * @param exclude Whether to add the target to or remove the target from the 719 * current list of excluded targets. 720 * @return This transition object. 721 */ 722 public Transition excludeTarget(int targetId, boolean exclude) { 723 mTargetIdExcludes = excludeId(mTargetIdExcludes, targetId, exclude); 724 return this; 725 } 726 727 /** 728 * Whether to add the children of the given id to the list of targets to exclude 729 * from this transition. The <code>exclude</code> parameter specifies whether 730 * the children of the target should be added to or removed from the excluded list. 731 * Excluding children in this way provides a simple mechanism for excluding all 732 * children of specific targets, rather than individually excluding each 733 * child individually. 734 * 735 * <p>Excluding targets is a general mechanism for allowing transitions to run on 736 * a view hierarchy while skipping target views that should not be part of 737 * the transition. For example, you may want to avoid animating children 738 * of a specific ListView or Spinner. Views can be excluded either by their 739 * id, or by their instance reference, or by the Class of that view 740 * (eg, {@link Spinner}).</p> 741 * 742 * @see #excludeTarget(int, boolean) 743 * @see #excludeChildren(View, boolean) 744 * @see #excludeChildren(Class, boolean) 745 * 746 * @param targetId The id of a target whose children should be ignored when running 747 * this transition. 748 * @param exclude Whether to add the target to or remove the target from the 749 * current list of excluded-child targets. 750 * @return This transition object. 751 */ 752 public Transition excludeChildren(int targetId, boolean exclude) { 753 mTargetIdChildExcludes = excludeId(mTargetIdChildExcludes, targetId, exclude); 754 return this; 755 } 756 757 /** 758 * Utility method to manage the boilerplate code that is the same whether we 759 * are excluding targets or their children. 760 */ 761 private ArrayList<Integer> excludeId(ArrayList<Integer> list, int targetId, boolean exclude) { 762 if (targetId > 0) { 763 if (exclude) { 764 list = ArrayListManager.add(list, targetId); 765 } else { 766 list = ArrayListManager.remove(list, targetId); 767 } 768 } 769 return list; 770 } 771 772 /** 773 * Whether to add the given target to the list of targets to exclude from this 774 * transition. The <code>exclude</code> parameter specifies whether the target 775 * should be added to or removed from the excluded list. 776 * 777 * <p>Excluding targets is a general mechanism for allowing transitions to run on 778 * a view hierarchy while skipping target views that should not be part of 779 * the transition. For example, you may want to avoid animating children 780 * of a specific ListView or Spinner. Views can be excluded either by their 781 * id, or by their instance reference, or by the Class of that view 782 * (eg, {@link Spinner}).</p> 783 * 784 * @see #excludeChildren(View, boolean) 785 * @see #excludeTarget(int, boolean) 786 * @see #excludeTarget(Class, boolean) 787 * 788 * @param target The target to ignore when running this transition. 789 * @param exclude Whether to add the target to or remove the target from the 790 * current list of excluded targets. 791 * @return This transition object. 792 */ 793 public Transition excludeTarget(View target, boolean exclude) { 794 mTargetExcludes = excludeView(mTargetExcludes, target, exclude); 795 return this; 796 } 797 798 /** 799 * Whether to add the children of given target to the list of target children 800 * to exclude from this transition. The <code>exclude</code> parameter specifies 801 * whether the target should be added to or removed from the excluded list. 802 * 803 * <p>Excluding targets is a general mechanism for allowing transitions to run on 804 * a view hierarchy while skipping target views that should not be part of 805 * the transition. For example, you may want to avoid animating children 806 * of a specific ListView or Spinner. Views can be excluded either by their 807 * id, or by their instance reference, or by the Class of that view 808 * (eg, {@link Spinner}).</p> 809 * 810 * @see #excludeTarget(View, boolean) 811 * @see #excludeChildren(int, boolean) 812 * @see #excludeChildren(Class, boolean) 813 * 814 * @param target The target to ignore when running this transition. 815 * @param exclude Whether to add the target to or remove the target from the 816 * current list of excluded targets. 817 * @return This transition object. 818 */ 819 public Transition excludeChildren(View target, boolean exclude) { 820 mTargetChildExcludes = excludeView(mTargetChildExcludes, target, exclude); 821 return this; 822 } 823 824 /** 825 * Utility method to manage the boilerplate code that is the same whether we 826 * are excluding targets or their children. 827 */ 828 private ArrayList<View> excludeView(ArrayList<View> list, View target, boolean exclude) { 829 if (target != null) { 830 if (exclude) { 831 list = ArrayListManager.add(list, target); 832 } else { 833 list = ArrayListManager.remove(list, target); 834 } 835 } 836 return list; 837 } 838 839 /** 840 * Whether to add the given type to the list of types to exclude from this 841 * transition. The <code>exclude</code> parameter specifies whether the target 842 * type should be added to or removed from the excluded list. 843 * 844 * <p>Excluding targets is a general mechanism for allowing transitions to run on 845 * a view hierarchy while skipping target views that should not be part of 846 * the transition. For example, you may want to avoid animating children 847 * of a specific ListView or Spinner. Views can be excluded either by their 848 * id, or by their instance reference, or by the Class of that view 849 * (eg, {@link Spinner}).</p> 850 * 851 * @see #excludeChildren(Class, boolean) 852 * @see #excludeTarget(int, boolean) 853 * @see #excludeTarget(View, boolean) 854 * 855 * @param type The type to ignore when running this transition. 856 * @param exclude Whether to add the target type to or remove it from the 857 * current list of excluded target types. 858 * @return This transition object. 859 */ 860 public Transition excludeTarget(Class type, boolean exclude) { 861 mTargetTypeExcludes = excludeType(mTargetTypeExcludes, type, exclude); 862 return this; 863 } 864 865 /** 866 * Whether to add the given type to the list of types whose children should 867 * be excluded from this transition. The <code>exclude</code> parameter 868 * specifies whether the target type should be added to or removed from 869 * the excluded list. 870 * 871 * <p>Excluding targets is a general mechanism for allowing transitions to run on 872 * a view hierarchy while skipping target views that should not be part of 873 * the transition. For example, you may want to avoid animating children 874 * of a specific ListView or Spinner. Views can be excluded either by their 875 * id, or by their instance reference, or by the Class of that view 876 * (eg, {@link Spinner}).</p> 877 * 878 * @see #excludeTarget(Class, boolean) 879 * @see #excludeChildren(int, boolean) 880 * @see #excludeChildren(View, boolean) 881 * 882 * @param type The type to ignore when running this transition. 883 * @param exclude Whether to add the target type to or remove it from the 884 * current list of excluded target types. 885 * @return This transition object. 886 */ 887 public Transition excludeChildren(Class type, boolean exclude) { 888 mTargetTypeChildExcludes = excludeType(mTargetTypeChildExcludes, type, exclude); 889 return this; 890 } 891 892 /** 893 * Utility method to manage the boilerplate code that is the same whether we 894 * are excluding targets or their children. 895 */ 896 private ArrayList<Class> excludeType(ArrayList<Class> list, Class type, boolean exclude) { 897 if (type != null) { 898 if (exclude) { 899 list = ArrayListManager.add(list, type); 900 } else { 901 list = ArrayListManager.remove(list, type); 902 } 903 } 904 return list; 905 } 906 907 /** 908 * Sets the target view instances that this Transition is interested in 909 * animating. By default, there are no targets, and a Transition will 910 * listen for changes on every view in the hierarchy below the sceneRoot 911 * of the Scene being transitioned into. Setting targets constrains 912 * the Transition to only listen for, and act on, these views. 913 * All other views will be ignored. 914 * 915 * <p>The target list is like the {@link #addTarget(int) targetId} 916 * list except this list specifies the actual View instances, not the ids 917 * of the views. This is an important distinction when scene changes involve 918 * view hierarchies which have been inflated separately; different views may 919 * share the same id but not actually be the same instance. If the transition 920 * should treat those views as the same, then {@link #addTarget(int)} should be used 921 * instead of {@link #addTarget(View)}. If, on the other hand, scene changes involve 922 * changes all within the same view hierarchy, among views which do not 923 * necessarily have ids set on them, then the target list of views may be more 924 * convenient.</p> 925 * 926 * @see #addTarget(int) 927 * @param target A View on which the Transition will act, must be non-null. 928 * @return The Transition to which the target is added. 929 * Returning the same object makes it easier to chain calls during 930 * construction, such as 931 * <code>transitionSet.addTransitions(new Fade()).addTarget(someView);</code> 932 */ 933 public Transition addTarget(View target) { 934 mTargets.add(target); 935 return this; 936 } 937 938 /** 939 * Removes the given target from the list of targets that this Transition 940 * is interested in animating. 941 * 942 * @param target The target view, must be non-null. 943 * @return Transition The Transition from which the target is removed. 944 * Returning the same object makes it easier to chain calls during 945 * construction, such as 946 * <code>transitionSet.addTransitions(new Fade()).removeTarget(someView);</code> 947 */ 948 public Transition removeTarget(View target) { 949 if (target != null) { 950 mTargets.remove(target); 951 } 952 return this; 953 } 954 955 /** 956 * Returns the array of target IDs that this transition limits itself to 957 * tracking and animating. If the array is null for both this method and 958 * {@link #getTargets()}, then this transition is 959 * not limited to specific views, and will handle changes to any views 960 * in the hierarchy of a scene change. 961 * 962 * @return the list of target IDs 963 */ 964 public List<Integer> getTargetIds() { 965 return mTargetIds; 966 } 967 968 /** 969 * Returns the array of target views that this transition limits itself to 970 * tracking and animating. If the array is null for both this method and 971 * {@link #getTargetIds()}, then this transition is 972 * not limited to specific views, and will handle changes to any views 973 * in the hierarchy of a scene change. 974 * 975 * @return the list of target views 976 */ 977 public List<View> getTargets() { 978 return mTargets; 979 } 980 981 /** 982 * Recursive method that captures values for the given view and the 983 * hierarchy underneath it. 984 * @param sceneRoot The root of the view hierarchy being captured 985 * @param start true if this capture is happening before the scene change, 986 * false otherwise 987 */ 988 void captureValues(ViewGroup sceneRoot, boolean start) { 989 if (start) { 990 mStartValues.viewValues.clear(); 991 mStartValues.idValues.clear(); 992 mStartValues.itemIdValues.clear(); 993 } else { 994 mEndValues.viewValues.clear(); 995 mEndValues.idValues.clear(); 996 mEndValues.itemIdValues.clear(); 997 } 998 if (mTargetIds.size() > 0 || mTargets.size() > 0) { 999 if (mTargetIds.size() > 0) { 1000 for (int i = 0; i < mTargetIds.size(); ++i) { 1001 int id = mTargetIds.get(i); 1002 View view = sceneRoot.findViewById(id); 1003 if (view != null) { 1004 TransitionValues values = new TransitionValues(); 1005 values.view = view; 1006 if (start) { 1007 captureStartValues(values); 1008 } else { 1009 captureEndValues(values); 1010 } 1011 if (start) { 1012 mStartValues.viewValues.put(view, values); 1013 if (id >= 0) { 1014 mStartValues.idValues.put(id, values); 1015 } 1016 } else { 1017 mEndValues.viewValues.put(view, values); 1018 if (id >= 0) { 1019 mEndValues.idValues.put(id, values); 1020 } 1021 } 1022 } 1023 } 1024 } 1025 if (mTargets.size() > 0) { 1026 for (int i = 0; i < mTargets.size(); ++i) { 1027 View view = mTargets.get(i); 1028 if (view != null) { 1029 TransitionValues values = new TransitionValues(); 1030 values.view = view; 1031 if (start) { 1032 captureStartValues(values); 1033 } else { 1034 captureEndValues(values); 1035 } 1036 if (start) { 1037 mStartValues.viewValues.put(view, values); 1038 } else { 1039 mEndValues.viewValues.put(view, values); 1040 } 1041 } 1042 } 1043 } 1044 } else { 1045 captureHierarchy(sceneRoot, start); 1046 } 1047 } 1048 1049 /** 1050 * Recursive method which captures values for an entire view hierarchy, 1051 * starting at some root view. Transitions without targetIDs will use this 1052 * method to capture values for all possible views. 1053 * 1054 * @param view The view for which to capture values. Children of this View 1055 * will also be captured, recursively down to the leaf nodes. 1056 * @param start true if values are being captured in the start scene, false 1057 * otherwise. 1058 */ 1059 private void captureHierarchy(View view, boolean start) { 1060 if (view == null) { 1061 return; 1062 } 1063 boolean isListViewItem = false; 1064 if (view.getParent() instanceof ListView) { 1065 isListViewItem = true; 1066 } 1067 if (isListViewItem && !((ListView) view.getParent()).getAdapter().hasStableIds()) { 1068 // ignore listview children unless we can track them with stable IDs 1069 return; 1070 } 1071 int id = View.NO_ID; 1072 long itemId = View.NO_ID; 1073 if (!isListViewItem) { 1074 id = view.getId(); 1075 } else { 1076 ListView listview = (ListView) view.getParent(); 1077 int position = listview.getPositionForView(view); 1078 itemId = listview.getItemIdAtPosition(position); 1079 view.setHasTransientState(true); 1080 } 1081 if (mTargetIdExcludes != null && mTargetIdExcludes.contains(id)) { 1082 return; 1083 } 1084 if (mTargetExcludes != null && mTargetExcludes.contains(view)) { 1085 return; 1086 } 1087 if (mTargetTypeExcludes != null && view != null) { 1088 int numTypes = mTargetTypeExcludes.size(); 1089 for (int i = 0; i < numTypes; ++i) { 1090 if (mTargetTypeExcludes.get(i).isInstance(view)) { 1091 return; 1092 } 1093 } 1094 } 1095 TransitionValues values = new TransitionValues(); 1096 values.view = view; 1097 captureStartValues(values); 1098 if (start) { 1099 if (!isListViewItem) { 1100 mStartValues.viewValues.put(view, values); 1101 if (id >= 0) { 1102 mStartValues.idValues.put((int) id, values); 1103 } 1104 } else { 1105 mStartValues.itemIdValues.put(itemId, values); 1106 } 1107 } else { 1108 if (!isListViewItem) { 1109 mEndValues.viewValues.put(view, values); 1110 if (id >= 0) { 1111 mEndValues.idValues.put((int) id, values); 1112 } 1113 } else { 1114 mEndValues.itemIdValues.put(itemId, values); 1115 } 1116 } 1117 if (view instanceof ViewGroup) { 1118 // Don't traverse child hierarchy if there are any child-excludes on this view 1119 if (mTargetIdChildExcludes != null && mTargetIdChildExcludes.contains(id)) { 1120 return; 1121 } 1122 if (mTargetChildExcludes != null && mTargetChildExcludes.contains(view)) { 1123 return; 1124 } 1125 if (mTargetTypeChildExcludes != null && view != null) { 1126 int numTypes = mTargetTypeChildExcludes.size(); 1127 for (int i = 0; i < numTypes; ++i) { 1128 if (mTargetTypeChildExcludes.get(i).isInstance(view)) { 1129 return; 1130 } 1131 } 1132 } 1133 ViewGroup parent = (ViewGroup) view; 1134 for (int i = 0; i < parent.getChildCount(); ++i) { 1135 captureHierarchy(parent.getChildAt(i), start); 1136 } 1137 } 1138 } 1139 1140 /** 1141 * This method can be called by transitions to get the TransitionValues for 1142 * any particular view during the transition-playing process. This might be 1143 * necessary, for example, to query the before/after state of related views 1144 * for a given transition. 1145 */ 1146 public TransitionValues getTransitionValues(View view, boolean start) { 1147 if (mParent != null) { 1148 return mParent.getTransitionValues(view, start); 1149 } 1150 TransitionValuesMaps valuesMaps = start ? mStartValues : mEndValues; 1151 TransitionValues values = valuesMaps.viewValues.get(view); 1152 if (values == null) { 1153 int id = view.getId(); 1154 if (id >= 0) { 1155 values = valuesMaps.idValues.get(id); 1156 } 1157 if (values == null && view.getParent() instanceof ListView) { 1158 ListView listview = (ListView) view.getParent(); 1159 int position = listview.getPositionForView(view); 1160 long itemId = listview.getItemIdAtPosition(position); 1161 values = valuesMaps.itemIdValues.get(itemId); 1162 } 1163 // TODO: Doesn't handle the case where a view was parented to a 1164 // ListView (with an itemId), but no longer is 1165 } 1166 return values; 1167 } 1168 1169 /** 1170 * Pauses this transition, sending out calls to {@link 1171 * TransitionListener#onTransitionPause(Transition)} to all listeners 1172 * and pausing all running animators started by this transition. 1173 * 1174 * @hide 1175 */ 1176 public void pause() { 1177 if (!mEnded) { 1178 ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators(); 1179 int numOldAnims = runningAnimators.size(); 1180 for (int i = numOldAnims - 1; i >= 0; i--) { 1181 Animator anim = runningAnimators.keyAt(i); 1182 anim.pause(); 1183 } 1184 if (mListeners != null && mListeners.size() > 0) { 1185 ArrayList<TransitionListener> tmpListeners = 1186 (ArrayList<TransitionListener>) mListeners.clone(); 1187 int numListeners = tmpListeners.size(); 1188 for (int i = 0; i < numListeners; ++i) { 1189 tmpListeners.get(i).onTransitionPause(this); 1190 } 1191 } 1192 mPaused = true; 1193 } 1194 } 1195 1196 /** 1197 * Resumes this transition, sending out calls to {@link 1198 * TransitionListener#onTransitionPause(Transition)} to all listeners 1199 * and pausing all running animators started by this transition. 1200 * 1201 * @hide 1202 */ 1203 public void resume() { 1204 if (mPaused) { 1205 if (!mEnded) { 1206 ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators(); 1207 int numOldAnims = runningAnimators.size(); 1208 for (int i = numOldAnims - 1; i >= 0; i--) { 1209 Animator anim = runningAnimators.keyAt(i); 1210 anim.resume(); 1211 } 1212 if (mListeners != null && mListeners.size() > 0) { 1213 ArrayList<TransitionListener> tmpListeners = 1214 (ArrayList<TransitionListener>) mListeners.clone(); 1215 int numListeners = tmpListeners.size(); 1216 for (int i = 0; i < numListeners; ++i) { 1217 tmpListeners.get(i).onTransitionResume(this); 1218 } 1219 } 1220 } 1221 mPaused = false; 1222 } 1223 } 1224 1225 /** 1226 * Called by TransitionManager to play the transition. This calls 1227 * createAnimators() to set things up and create all of the animations and then 1228 * runAnimations() to actually start the animations. 1229 */ 1230 void playTransition(ViewGroup sceneRoot) { 1231 ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators(); 1232 int numOldAnims = runningAnimators.size(); 1233 for (int i = numOldAnims - 1; i >= 0; i--) { 1234 Animator anim = runningAnimators.keyAt(i); 1235 if (anim != null) { 1236 AnimationInfo oldInfo = runningAnimators.get(anim); 1237 if (oldInfo != null) { 1238 boolean cancel = false; 1239 TransitionValues oldValues = oldInfo.values; 1240 View oldView = oldInfo.view; 1241 TransitionValues newValues = mEndValues.viewValues != null ? 1242 mEndValues.viewValues.get(oldView) : null; 1243 if (newValues == null) { 1244 newValues = mEndValues.idValues.get(oldView.getId()); 1245 } 1246 if (oldValues != null) { 1247 // if oldValues null, then transition didn't care to stash values, 1248 // and won't get canceled 1249 if (newValues != null) { 1250 for (String key : oldValues.values.keySet()) { 1251 Object oldValue = oldValues.values.get(key); 1252 Object newValue = newValues.values.get(key); 1253 if (oldValue != null && newValue != null && 1254 !oldValue.equals(newValue)) { 1255 cancel = true; 1256 if (DBG) { 1257 Log.d(LOG_TAG, "Transition.playTransition: " + 1258 "oldValue != newValue for " + key + 1259 ": old, new = " + oldValue + ", " + newValue); 1260 } 1261 break; 1262 } 1263 } 1264 } 1265 } 1266 if (cancel) { 1267 if (anim.isRunning() || anim.isStarted()) { 1268 if (DBG) { 1269 Log.d(LOG_TAG, "Canceling anim " + anim); 1270 } 1271 anim.cancel(); 1272 } else { 1273 if (DBG) { 1274 Log.d(LOG_TAG, "removing anim from info list: " + anim); 1275 } 1276 runningAnimators.remove(anim); 1277 } 1278 } 1279 } 1280 } 1281 } 1282 1283 createAnimators(sceneRoot, mStartValues, mEndValues); 1284 runAnimators(); 1285 } 1286 1287 /** 1288 * This is a utility method used by subclasses to handle standard parts of 1289 * setting up and running an Animator: it sets the {@link #getDuration() 1290 * duration} and the {@link #getStartDelay() startDelay}, starts the 1291 * animation, and, when the animator ends, calls {@link #end()}. 1292 * 1293 * @param animator The Animator to be run during this transition. 1294 * 1295 * @hide 1296 */ 1297 protected void animate(Animator animator) { 1298 // TODO: maybe pass auto-end as a boolean parameter? 1299 if (animator == null) { 1300 end(); 1301 } else { 1302 if (getDuration() >= 0) { 1303 animator.setDuration(getDuration()); 1304 } 1305 if (getStartDelay() >= 0) { 1306 animator.setStartDelay(getStartDelay()); 1307 } 1308 if (getInterpolator() != null) { 1309 animator.setInterpolator(getInterpolator()); 1310 } 1311 animator.addListener(new AnimatorListenerAdapter() { 1312 @Override 1313 public void onAnimationEnd(Animator animation) { 1314 end(); 1315 animation.removeListener(this); 1316 } 1317 }); 1318 animator.start(); 1319 } 1320 } 1321 1322 /** 1323 * This method is called automatically by the transition and 1324 * TransitionSet classes prior to a Transition subclass starting; 1325 * subclasses should not need to call it directly. 1326 * 1327 * @hide 1328 */ 1329 protected void start() { 1330 if (mNumInstances == 0) { 1331 if (mListeners != null && mListeners.size() > 0) { 1332 ArrayList<TransitionListener> tmpListeners = 1333 (ArrayList<TransitionListener>) mListeners.clone(); 1334 int numListeners = tmpListeners.size(); 1335 for (int i = 0; i < numListeners; ++i) { 1336 tmpListeners.get(i).onTransitionStart(this); 1337 } 1338 } 1339 mEnded = false; 1340 } 1341 mNumInstances++; 1342 } 1343 1344 /** 1345 * This method is called automatically by the Transition and 1346 * TransitionSet classes when a transition finishes, either because 1347 * a transition did nothing (returned a null Animator from 1348 * {@link Transition#createAnimator(ViewGroup, TransitionValues, 1349 * TransitionValues)}) or because the transition returned a valid 1350 * Animator and end() was called in the onAnimationEnd() 1351 * callback of the AnimatorListener. 1352 * 1353 * @hide 1354 */ 1355 protected void end() { 1356 --mNumInstances; 1357 if (mNumInstances == 0) { 1358 if (mListeners != null && mListeners.size() > 0) { 1359 ArrayList<TransitionListener> tmpListeners = 1360 (ArrayList<TransitionListener>) mListeners.clone(); 1361 int numListeners = tmpListeners.size(); 1362 for (int i = 0; i < numListeners; ++i) { 1363 tmpListeners.get(i).onTransitionEnd(this); 1364 } 1365 } 1366 for (int i = 0; i < mStartValues.itemIdValues.size(); ++i) { 1367 TransitionValues tv = mStartValues.itemIdValues.valueAt(i); 1368 View v = tv.view; 1369 if (v.hasTransientState()) { 1370 v.setHasTransientState(false); 1371 } 1372 } 1373 for (int i = 0; i < mEndValues.itemIdValues.size(); ++i) { 1374 TransitionValues tv = mEndValues.itemIdValues.valueAt(i); 1375 View v = tv.view; 1376 if (v.hasTransientState()) { 1377 v.setHasTransientState(false); 1378 } 1379 } 1380 mEnded = true; 1381 } 1382 } 1383 1384 /** 1385 * This method cancels a transition that is currently running. 1386 * 1387 * @hide 1388 */ 1389 protected void cancel() { 1390 int numAnimators = mCurrentAnimators.size(); 1391 for (int i = numAnimators - 1; i >= 0; i--) { 1392 Animator animator = mCurrentAnimators.get(i); 1393 animator.cancel(); 1394 } 1395 if (mListeners != null && mListeners.size() > 0) { 1396 ArrayList<TransitionListener> tmpListeners = 1397 (ArrayList<TransitionListener>) mListeners.clone(); 1398 int numListeners = tmpListeners.size(); 1399 for (int i = 0; i < numListeners; ++i) { 1400 tmpListeners.get(i).onTransitionCancel(this); 1401 } 1402 } 1403 } 1404 1405 /** 1406 * Adds a listener to the set of listeners that are sent events through the 1407 * life of an animation, such as start, repeat, and end. 1408 * 1409 * @param listener the listener to be added to the current set of listeners 1410 * for this animation. 1411 * @return This transition object. 1412 */ 1413 public Transition addListener(TransitionListener listener) { 1414 if (mListeners == null) { 1415 mListeners = new ArrayList<TransitionListener>(); 1416 } 1417 mListeners.add(listener); 1418 return this; 1419 } 1420 1421 /** 1422 * Removes a listener from the set listening to this animation. 1423 * 1424 * @param listener the listener to be removed from the current set of 1425 * listeners for this transition. 1426 * @return This transition object. 1427 */ 1428 public Transition removeListener(TransitionListener listener) { 1429 if (mListeners == null) { 1430 return this; 1431 } 1432 mListeners.remove(listener); 1433 if (mListeners.size() == 0) { 1434 mListeners = null; 1435 } 1436 return this; 1437 } 1438 1439 Transition setSceneRoot(ViewGroup sceneRoot) { 1440 mSceneRoot = sceneRoot; 1441 return this; 1442 } 1443 1444 @Override 1445 public String toString() { 1446 return toString(""); 1447 } 1448 1449 @Override 1450 public Transition clone() { 1451 Transition clone = null; 1452 try { 1453 clone = (Transition) super.clone(); 1454 clone.mAnimators = new ArrayList<Animator>(); 1455 clone.mStartValues = new TransitionValuesMaps(); 1456 clone.mEndValues = new TransitionValuesMaps(); 1457 } catch (CloneNotSupportedException e) {} 1458 1459 return clone; 1460 } 1461 1462 /** 1463 * Returns the name of this Transition. This name is used internally to distinguish 1464 * between different transitions to determine when interrupting transitions overlap. 1465 * For example, a ChangeBounds running on the same target view as another ChangeBounds 1466 * should determine whether the old transition is animating to different end values 1467 * and should be canceled in favor of the new transition. 1468 * 1469 * <p>By default, a Transition's name is simply the value of {@link Class#getName()}, 1470 * but subclasses are free to override and return something different.</p> 1471 * 1472 * @return The name of this transition. 1473 */ 1474 public String getName() { 1475 return mName; 1476 } 1477 1478 String toString(String indent) { 1479 String result = indent + getClass().getSimpleName() + "@" + 1480 Integer.toHexString(hashCode()) + ": "; 1481 if (mDuration != -1) { 1482 result += "dur(" + mDuration + ") "; 1483 } 1484 if (mStartDelay != -1) { 1485 result += "dly(" + mStartDelay + ") "; 1486 } 1487 if (mInterpolator != null) { 1488 result += "interp(" + mInterpolator + ") "; 1489 } 1490 if (mTargetIds.size() > 0 || mTargets.size() > 0) { 1491 result += "tgts("; 1492 if (mTargetIds.size() > 0) { 1493 for (int i = 0; i < mTargetIds.size(); ++i) { 1494 if (i > 0) { 1495 result += ", "; 1496 } 1497 result += mTargetIds.get(i); 1498 } 1499 } 1500 if (mTargets.size() > 0) { 1501 for (int i = 0; i < mTargets.size(); ++i) { 1502 if (i > 0) { 1503 result += ", "; 1504 } 1505 result += mTargets.get(i); 1506 } 1507 } 1508 result += ")"; 1509 } 1510 return result; 1511 } 1512 1513 /** 1514 * A transition listener receives notifications from a transition. 1515 * Notifications indicate transition lifecycle events. 1516 */ 1517 public static interface TransitionListener { 1518 /** 1519 * Notification about the start of the transition. 1520 * 1521 * @param transition The started transition. 1522 */ 1523 void onTransitionStart(Transition transition); 1524 1525 /** 1526 * Notification about the end of the transition. Canceled transitions 1527 * will always notify listeners of both the cancellation and end 1528 * events. That is, {@link #onTransitionEnd(Transition)} is always called, 1529 * regardless of whether the transition was canceled or played 1530 * through to completion. 1531 * 1532 * @param transition The transition which reached its end. 1533 */ 1534 void onTransitionEnd(Transition transition); 1535 1536 /** 1537 * Notification about the cancellation of the transition. 1538 * Note that cancel may be called by a parent {@link TransitionSet} on 1539 * a child transition which has not yet started. This allows the child 1540 * transition to restore state on target objects which was set at 1541 * {@link #createAnimator(android.view.ViewGroup, TransitionValues, TransitionValues) 1542 * createAnimator()} time. 1543 * 1544 * @param transition The transition which was canceled. 1545 */ 1546 void onTransitionCancel(Transition transition); 1547 1548 /** 1549 * Notification when a transition is paused. 1550 * Note that createAnimator() may be called by a parent {@link TransitionSet} on 1551 * a child transition which has not yet started. This allows the child 1552 * transition to restore state on target objects which was set at 1553 * {@link #createAnimator(android.view.ViewGroup, TransitionValues, TransitionValues) 1554 * createAnimator()} time. 1555 * 1556 * @param transition The transition which was paused. 1557 */ 1558 void onTransitionPause(Transition transition); 1559 1560 /** 1561 * Notification when a transition is resumed. 1562 * Note that resume() may be called by a parent {@link TransitionSet} on 1563 * a child transition which has not yet started. This allows the child 1564 * transition to restore state which may have changed in an earlier call 1565 * to {@link #onTransitionPause(Transition)}. 1566 * 1567 * @param transition The transition which was resumed. 1568 */ 1569 void onTransitionResume(Transition transition); 1570 } 1571 1572 /** 1573 * Utility adapter class to avoid having to override all three methods 1574 * whenever someone just wants to listen for a single event. 1575 * 1576 * @hide 1577 * */ 1578 public static class TransitionListenerAdapter implements TransitionListener { 1579 @Override 1580 public void onTransitionStart(Transition transition) { 1581 } 1582 1583 @Override 1584 public void onTransitionEnd(Transition transition) { 1585 } 1586 1587 @Override 1588 public void onTransitionCancel(Transition transition) { 1589 } 1590 1591 @Override 1592 public void onTransitionPause(Transition transition) { 1593 } 1594 1595 @Override 1596 public void onTransitionResume(Transition transition) { 1597 } 1598 } 1599 1600 /** 1601 * Holds information about each animator used when a new transition starts 1602 * while other transitions are still running to determine whether a running 1603 * animation should be canceled or a new animation noop'd. The structure holds 1604 * information about the state that an animation is going to, to be compared to 1605 * end state of a new animation. 1606 */ 1607 private static class AnimationInfo { 1608 View view; 1609 String name; 1610 TransitionValues values; 1611 1612 AnimationInfo(View view, String name, TransitionValues values) { 1613 this.view = view; 1614 this.name = name; 1615 this.values = values; 1616 } 1617 } 1618 1619 /** 1620 * Utility class for managing typed ArrayLists efficiently. In particular, this 1621 * can be useful for lists that we don't expect to be used often (eg, the exclude 1622 * lists), so we'd like to keep them nulled out by default. This causes the code to 1623 * become tedious, with constant null checks, code to allocate when necessary, 1624 * and code to null out the reference when the list is empty. This class encapsulates 1625 * all of that functionality into simple add()/remove() methods which perform the 1626 * necessary checks, allocation/null-out as appropriate, and return the 1627 * resulting list. 1628 */ 1629 private static class ArrayListManager { 1630 1631 /** 1632 * Add the specified item to the list, returning the resulting list. 1633 * The returned list can either the be same list passed in or, if that 1634 * list was null, the new list that was created. 1635 * 1636 * Note that the list holds unique items; if the item already exists in the 1637 * list, the list is not modified. 1638 */ 1639 static <T> ArrayList<T> add(ArrayList<T> list, T item) { 1640 if (list == null) { 1641 list = new ArrayList<T>(); 1642 } 1643 if (!list.contains(item)) { 1644 list.add(item); 1645 } 1646 return list; 1647 } 1648 1649 /** 1650 * Remove the specified item from the list, returning the resulting list. 1651 * The returned list can either the be same list passed in or, if that 1652 * list becomes empty as a result of the remove(), the new list was created. 1653 */ 1654 static <T> ArrayList<T> remove(ArrayList<T> list, T item) { 1655 if (list != null) { 1656 list.remove(item); 1657 if (list.isEmpty()) { 1658 list = null; 1659 } 1660 } 1661 return list; 1662 } 1663 } 1664 1665} 1666