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