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