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