Transition.java revision a98fb7ab6a17d27395cf2c8e86060af49b861be6
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 = 355 new SparseArray<TransitionValues>(endValues.idValues.size()); 356 for (int i = 0; i < endValues.idValues.size(); ++i) { 357 int id = endValues.idValues.keyAt(i); 358 endIdCopy.put(id, endValues.idValues.valueAt(i)); 359 } 360 LongSparseArray<TransitionValues> endItemIdCopy = 361 new LongSparseArray<TransitionValues>(endValues.itemIdValues.size()); 362 for (int i = 0; i < endValues.itemIdValues.size(); ++i) { 363 long id = endValues.itemIdValues.keyAt(i); 364 endItemIdCopy.put(id, endValues.itemIdValues.valueAt(i)); 365 } 366 // Walk through the start values, playing everything we find 367 // Remove from the end set as we go 368 ArrayList<TransitionValues> startValuesList = new ArrayList<TransitionValues>(); 369 ArrayList<TransitionValues> endValuesList = new ArrayList<TransitionValues>(); 370 for (View view : startValues.viewValues.keySet()) { 371 TransitionValues start = null; 372 TransitionValues end = null; 373 boolean isInListView = false; 374 if (view.getParent() instanceof ListView) { 375 isInListView = true; 376 } 377 if (!isInListView) { 378 int id = view.getId(); 379 start = startValues.viewValues.get(view) != null ? 380 startValues.viewValues.get(view) : startValues.idValues.get(id); 381 if (endValues.viewValues.get(view) != null) { 382 end = endValues.viewValues.get(view); 383 endCopy.remove(view); 384 } else if (id != View.NO_ID) { 385 end = endValues.idValues.get(id); 386 View removeView = null; 387 for (View viewToRemove : endCopy.keySet()) { 388 if (viewToRemove.getId() == id) { 389 removeView = viewToRemove; 390 } 391 } 392 if (removeView != null) { 393 endCopy.remove(removeView); 394 } 395 } 396 endIdCopy.remove(id); 397 if (isValidTarget(view, id)) { 398 startValuesList.add(start); 399 endValuesList.add(end); 400 } 401 } else { 402 ListView parent = (ListView) view.getParent(); 403 if (parent.getAdapter().hasStableIds()) { 404 int position = parent.getPositionForView(view); 405 long itemId = parent.getItemIdAtPosition(position); 406 start = startValues.itemIdValues.get(itemId); 407 endItemIdCopy.remove(itemId); 408 // TODO: deal with targetIDs for itemIDs for ListView items 409 startValuesList.add(start); 410 endValuesList.add(end); 411 } 412 } 413 } 414 int startItemIdCopySize = startValues.itemIdValues.size(); 415 for (int i = 0; i < startItemIdCopySize; ++i) { 416 long id = startValues.itemIdValues.keyAt(i); 417 if (isValidTarget(null, id)) { 418 TransitionValues start = startValues.itemIdValues.get(id); 419 TransitionValues end = endValues.itemIdValues.get(id); 420 endItemIdCopy.remove(id); 421 startValuesList.add(start); 422 endValuesList.add(end); 423 } 424 } 425 // Now walk through the remains of the end set 426 for (View view : endCopy.keySet()) { 427 int id = view.getId(); 428 if (isValidTarget(view, id)) { 429 TransitionValues start = startValues.viewValues.get(view) != null ? 430 startValues.viewValues.get(view) : startValues.idValues.get(id); 431 TransitionValues end = endCopy.get(view); 432 endIdCopy.remove(id); 433 startValuesList.add(start); 434 endValuesList.add(end); 435 } 436 } 437 int endIdCopySize = endIdCopy.size(); 438 for (int i = 0; i < endIdCopySize; ++i) { 439 int id = endIdCopy.keyAt(i); 440 if (isValidTarget(null, id)) { 441 TransitionValues start = startValues.idValues.get(id); 442 TransitionValues end = endIdCopy.get(id); 443 startValuesList.add(start); 444 endValuesList.add(end); 445 } 446 } 447 int endItemIdCopySize = endItemIdCopy.size(); 448 for (int i = 0; i < endItemIdCopySize; ++i) { 449 long id = endItemIdCopy.keyAt(i); 450 // TODO: Deal with targetIDs and itemIDs 451 TransitionValues start = startValues.itemIdValues.get(id); 452 TransitionValues end = endItemIdCopy.get(id); 453 startValuesList.add(start); 454 endValuesList.add(end); 455 } 456 ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators(); 457 long minStartDelay = Long.MAX_VALUE; 458 int minAnimator = mAnimators.size(); 459 SparseLongArray startDelays = new SparseLongArray(); 460 for (int i = 0; i < startValuesList.size(); ++i) { 461 TransitionValues start = startValuesList.get(i); 462 TransitionValues end = endValuesList.get(i); 463 // Only bother trying to animate with values that differ between start/end 464 if (start != null || end != null) { 465 if (start == null || !start.equals(end)) { 466 if (DBG) { 467 View view = (end != null) ? end.view : start.view; 468 Log.d(LOG_TAG, " differing start/end values for view " + 469 view); 470 if (start == null || end == null) { 471 Log.d(LOG_TAG, " " + ((start == null) ? 472 "start null, end non-null" : "start non-null, end null")); 473 } else { 474 for (String key : start.values.keySet()) { 475 Object startValue = start.values.get(key); 476 Object endValue = end.values.get(key); 477 if (startValue != endValue && !startValue.equals(endValue)) { 478 Log.d(LOG_TAG, " " + key + ": start(" + startValue + 479 "), end(" + endValue +")"); 480 } 481 } 482 } 483 } 484 // TODO: what to do about targetIds and itemIds? 485 Animator animator = createAnimator(sceneRoot, start, end); 486 if (animator != null) { 487 // Save animation info for future cancellation purposes 488 View view = null; 489 TransitionValues infoValues = null; 490 if (end != null) { 491 view = end.view; 492 String[] properties = getTransitionProperties(); 493 if (view != null && properties != null && properties.length > 0) { 494 infoValues = new TransitionValues(); 495 infoValues.view = view; 496 TransitionValues newValues = endValues.viewValues.get(view); 497 if (newValues != null) { 498 for (int j = 0; j < properties.length; ++j) { 499 infoValues.values.put(properties[j], 500 newValues.values.get(properties[j])); 501 } 502 } 503 int numExistingAnims = runningAnimators.size(); 504 for (int j = 0; j < numExistingAnims; ++j) { 505 Animator anim = runningAnimators.keyAt(j); 506 AnimationInfo info = runningAnimators.get(anim); 507 if (info.values != null && info.view == view && 508 ((info.name == null && getName() == null) || 509 info.name.equals(getName()))) { 510 if (info.values.equals(infoValues)) { 511 // Favor the old animator 512 animator = null; 513 break; 514 } 515 } 516 } 517 } 518 } else { 519 view = (start != null) ? start.view : null; 520 } 521 if (animator != null) { 522 if (mPropagation != null) { 523 long delay = mPropagation 524 .getStartDelay(sceneRoot, this, start, end); 525 startDelays.put(mAnimators.size(), delay); 526 minStartDelay = Math.min(delay, minStartDelay); 527 } 528 AnimationInfo info = new AnimationInfo(view, getName(), 529 sceneRoot.getWindowId(), infoValues); 530 runningAnimators.put(animator, info); 531 mAnimators.add(animator); 532 } 533 } 534 } 535 } 536 } 537 if (minStartDelay != 0) { 538 for (int i = 0; i < startDelays.size(); i++) { 539 int index = startDelays.keyAt(i); 540 Animator animator = mAnimators.get(index); 541 long delay = startDelays.valueAt(i) - minStartDelay + animator.getStartDelay(); 542 animator.setStartDelay(delay); 543 } 544 } 545 } 546 547 /** 548 * Internal utility method for checking whether a given view/id 549 * is valid for this transition, where "valid" means that either 550 * the Transition has no target/targetId list (the default, in which 551 * cause the transition should act on all views in the hiearchy), or 552 * the given view is in the target list or the view id is in the 553 * targetId list. If the target parameter is null, then the target list 554 * is not checked (this is in the case of ListView items, where the 555 * views are ignored and only the ids are used). 556 */ 557 boolean isValidTarget(View target, long targetId) { 558 if (mTargetIdExcludes != null && mTargetIdExcludes.contains(targetId)) { 559 return false; 560 } 561 if (mTargetExcludes != null && mTargetExcludes.contains(target)) { 562 return false; 563 } 564 if (mTargetTypeExcludes != null && target != null) { 565 int numTypes = mTargetTypeExcludes.size(); 566 for (int i = 0; i < numTypes; ++i) { 567 Class type = mTargetTypeExcludes.get(i); 568 if (type.isInstance(target)) { 569 return false; 570 } 571 } 572 } 573 if (mTargetIds.size() == 0 && mTargets.size() == 0 && mTargetTypes == null) { 574 return true; 575 } 576 if (mTargetIds.contains((int) targetId) || mTargets.contains(target)) { 577 return true; 578 } 579 if (mTargetTypes != null) { 580 for (int i = 0; i < mTargetTypes.size(); ++i) { 581 if (mTargetTypes.get(i).isInstance(target)) { 582 return true; 583 } 584 } 585 } 586 return false; 587 } 588 589 private static ArrayMap<Animator, AnimationInfo> getRunningAnimators() { 590 ArrayMap<Animator, AnimationInfo> runningAnimators = sRunningAnimators.get(); 591 if (runningAnimators == null) { 592 runningAnimators = new ArrayMap<Animator, AnimationInfo>(); 593 sRunningAnimators.set(runningAnimators); 594 } 595 return runningAnimators; 596 } 597 598 /** 599 * This is called internally once all animations have been set up by the 600 * transition hierarchy. 601 * 602 * @hide 603 */ 604 protected void runAnimators() { 605 if (DBG) { 606 Log.d(LOG_TAG, "runAnimators() on " + this); 607 } 608 start(); 609 ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators(); 610 // Now start every Animator that was previously created for this transition 611 for (Animator anim : mAnimators) { 612 if (DBG) { 613 Log.d(LOG_TAG, " anim: " + anim); 614 } 615 if (runningAnimators.containsKey(anim)) { 616 start(); 617 runAnimator(anim, runningAnimators); 618 } 619 } 620 mAnimators.clear(); 621 end(); 622 } 623 624 private void runAnimator(Animator animator, 625 final ArrayMap<Animator, AnimationInfo> runningAnimators) { 626 if (animator != null) { 627 // TODO: could be a single listener instance for all of them since it uses the param 628 animator.addListener(new AnimatorListenerAdapter() { 629 @Override 630 public void onAnimationStart(Animator animation) { 631 mCurrentAnimators.add(animation); 632 } 633 @Override 634 public void onAnimationEnd(Animator animation) { 635 runningAnimators.remove(animation); 636 mCurrentAnimators.remove(animation); 637 } 638 }); 639 animate(animator); 640 } 641 } 642 643 /** 644 * Captures the values in the start scene for the properties that this 645 * transition monitors. These values are then passed as the startValues 646 * structure in a later call to 647 * {@link #createAnimator(ViewGroup, TransitionValues, TransitionValues)}. 648 * The main concern for an implementation is what the 649 * properties are that the transition cares about and what the values are 650 * for all of those properties. The start and end values will be compared 651 * later during the 652 * {@link #createAnimator(android.view.ViewGroup, TransitionValues, TransitionValues)} 653 * method to determine what, if any, animations, should be run. 654 * 655 * <p>Subclasses must implement this method. The method should only be called by the 656 * transition system; it is not intended to be called from external classes.</p> 657 * 658 * @param transitionValues The holder for any values that the Transition 659 * wishes to store. Values are stored in the <code>values</code> field 660 * of this TransitionValues object and are keyed from 661 * a String value. For example, to store a view's rotation value, 662 * a transition might call 663 * <code>transitionValues.values.put("appname:transitionname:rotation", 664 * view.getRotation())</code>. The target view will already be stored in 665 * the transitionValues structure when this method is called. 666 * 667 * @see #captureEndValues(TransitionValues) 668 * @see #createAnimator(ViewGroup, TransitionValues, TransitionValues) 669 */ 670 public abstract void captureStartValues(TransitionValues transitionValues); 671 672 /** 673 * Captures the values in the end scene for the properties that this 674 * transition monitors. These values are then passed as the endValues 675 * structure in a later call to 676 * {@link #createAnimator(ViewGroup, TransitionValues, TransitionValues)}. 677 * The main concern for an implementation is what the 678 * properties are that the transition cares about and what the values are 679 * for all of those properties. The start and end values will be compared 680 * later during the 681 * {@link #createAnimator(android.view.ViewGroup, TransitionValues, TransitionValues)} 682 * method to determine what, if any, animations, should be run. 683 * 684 * <p>Subclasses must implement this method. The method should only be called by the 685 * transition system; it is not intended to be called from external classes.</p> 686 * 687 * @param transitionValues The holder for any values that the Transition 688 * wishes to store. Values are stored in the <code>values</code> field 689 * of this TransitionValues object and are keyed from 690 * a String value. For example, to store a view's rotation value, 691 * a transition might call 692 * <code>transitionValues.values.put("appname:transitionname:rotation", 693 * view.getRotation())</code>. The target view will already be stored in 694 * the transitionValues structure when this method is called. 695 * 696 * @see #captureStartValues(TransitionValues) 697 * @see #createAnimator(ViewGroup, TransitionValues, TransitionValues) 698 */ 699 public abstract void captureEndValues(TransitionValues transitionValues); 700 701 /** 702 * Adds the id of a target view that this Transition is interested in 703 * animating. By default, there are no targetIds, and a Transition will 704 * listen for changes on every view in the hierarchy below the sceneRoot 705 * of the Scene being transitioned into. Setting targetIds constrains 706 * the Transition to only listen for, and act on, views with these IDs. 707 * Views with different IDs, or no IDs whatsoever, will be ignored. 708 * 709 * <p>Note that using ids to specify targets implies that ids should be unique 710 * within the view hierarchy underneat the scene root.</p> 711 * 712 * @see View#getId() 713 * @param targetId The id of a target view, must be a positive number. 714 * @return The Transition to which the targetId is added. 715 * Returning the same object makes it easier to chain calls during 716 * construction, such as 717 * <code>transitionSet.addTransitions(new Fade()).addTarget(someId);</code> 718 */ 719 public Transition addTarget(int targetId) { 720 if (targetId > 0) { 721 mTargetIds.add(targetId); 722 } 723 return this; 724 } 725 726 /** 727 * Adds the Class of a target view that this Transition is interested in 728 * animating. By default, there are no targetTypes, and a Transition will 729 * listen for changes on every view in the hierarchy below the sceneRoot 730 * of the Scene being transitioned into. Setting targetTypes constrains 731 * the Transition to only listen for, and act on, views with these classes. 732 * Views with different classes will be ignored. 733 * 734 * <p>Note that any View that can be cast to targetType will be included, so 735 * if targetType is <code>View.class</code>, all Views will be included.</p> 736 * 737 * @see #addTarget(int) 738 * @see #addTarget(android.view.View) 739 * @see #excludeTarget(Class, boolean) 740 * @see #excludeChildren(Class, boolean) 741 * 742 * @param targetType The type to include when running this transition. 743 * @return The Transition to which the target class was added. 744 * Returning the same object makes it easier to chain calls during 745 * construction, such as 746 * <code>transitionSet.addTransitions(new Fade()).addTarget(ImageView.class);</code> 747 */ 748 public Transition addTarget(Class targetType) { 749 if (mTargetTypes == null) { 750 mTargetTypes = new ArrayList<Class>(); 751 } 752 mTargetTypes.add(targetType); 753 return this; 754 } 755 756 /** 757 * Removes the given targetId from the list of ids that this Transition 758 * is interested in animating. 759 * 760 * @param targetId The id of a target view, must be a positive number. 761 * @return The Transition from which the targetId is removed. 762 * Returning the same object makes it easier to chain calls during 763 * construction, such as 764 * <code>transitionSet.addTransitions(new Fade()).removeTargetId(someId);</code> 765 */ 766 public Transition removeTarget(int targetId) { 767 if (targetId > 0) { 768 mTargetIds.remove(targetId); 769 } 770 return this; 771 } 772 773 /** 774 * Whether to add the given id to the list of target ids to exclude from this 775 * transition. The <code>exclude</code> parameter specifies whether the target 776 * should be added to or removed from the excluded list. 777 * 778 * <p>Excluding targets is a general mechanism for allowing transitions to run on 779 * a view hierarchy while skipping target views that should not be part of 780 * the transition. For example, you may want to avoid animating children 781 * of a specific ListView or Spinner. Views can be excluded either by their 782 * id, or by their instance reference, or by the Class of that view 783 * (eg, {@link Spinner}).</p> 784 * 785 * @see #excludeChildren(int, boolean) 786 * @see #excludeTarget(View, boolean) 787 * @see #excludeTarget(Class, boolean) 788 * 789 * @param targetId The id of a target to ignore when running this transition. 790 * @param exclude Whether to add the target to or remove the target from the 791 * current list of excluded targets. 792 * @return This transition object. 793 */ 794 public Transition excludeTarget(int targetId, boolean exclude) { 795 mTargetIdExcludes = excludeId(mTargetIdExcludes, targetId, exclude); 796 return this; 797 } 798 799 /** 800 * Whether to add the children of the given id to the list of targets to exclude 801 * from this transition. The <code>exclude</code> parameter specifies whether 802 * the children of the target should be added to or removed from the excluded list. 803 * Excluding children in this way provides a simple mechanism for excluding all 804 * children of specific targets, rather than individually excluding each 805 * child individually. 806 * 807 * <p>Excluding targets is a general mechanism for allowing transitions to run on 808 * a view hierarchy while skipping target views that should not be part of 809 * the transition. For example, you may want to avoid animating children 810 * of a specific ListView or Spinner. Views can be excluded either by their 811 * id, or by their instance reference, or by the Class of that view 812 * (eg, {@link Spinner}).</p> 813 * 814 * @see #excludeTarget(int, boolean) 815 * @see #excludeChildren(View, boolean) 816 * @see #excludeChildren(Class, boolean) 817 * 818 * @param targetId The id of a target whose children should be ignored when running 819 * this transition. 820 * @param exclude Whether to add the target to or remove the target from the 821 * current list of excluded-child targets. 822 * @return This transition object. 823 */ 824 public Transition excludeChildren(int targetId, boolean exclude) { 825 mTargetIdChildExcludes = excludeId(mTargetIdChildExcludes, targetId, exclude); 826 return this; 827 } 828 829 /** 830 * Utility method to manage the boilerplate code that is the same whether we 831 * are excluding targets or their children. 832 */ 833 private ArrayList<Integer> excludeId(ArrayList<Integer> list, int targetId, boolean exclude) { 834 if (targetId > 0) { 835 if (exclude) { 836 list = ArrayListManager.add(list, targetId); 837 } else { 838 list = ArrayListManager.remove(list, targetId); 839 } 840 } 841 return list; 842 } 843 844 /** 845 * Whether to add the given target to the list of targets to exclude from this 846 * transition. The <code>exclude</code> parameter specifies whether the target 847 * should be added to or removed from the excluded list. 848 * 849 * <p>Excluding targets is a general mechanism for allowing transitions to run on 850 * a view hierarchy while skipping target views that should not be part of 851 * the transition. For example, you may want to avoid animating children 852 * of a specific ListView or Spinner. Views can be excluded either by their 853 * id, or by their instance reference, or by the Class of that view 854 * (eg, {@link Spinner}).</p> 855 * 856 * @see #excludeChildren(View, boolean) 857 * @see #excludeTarget(int, boolean) 858 * @see #excludeTarget(Class, boolean) 859 * 860 * @param target The target to ignore when running this transition. 861 * @param exclude Whether to add the target to or remove the target from the 862 * current list of excluded targets. 863 * @return This transition object. 864 */ 865 public Transition excludeTarget(View target, boolean exclude) { 866 mTargetExcludes = excludeView(mTargetExcludes, target, exclude); 867 return this; 868 } 869 870 /** 871 * Whether to add the children of given target to the list of target children 872 * to exclude from this transition. The <code>exclude</code> parameter specifies 873 * whether the target should be added to or removed from the excluded list. 874 * 875 * <p>Excluding targets is a general mechanism for allowing transitions to run on 876 * a view hierarchy while skipping target views that should not be part of 877 * the transition. For example, you may want to avoid animating children 878 * of a specific ListView or Spinner. Views can be excluded either by their 879 * id, or by their instance reference, or by the Class of that view 880 * (eg, {@link Spinner}).</p> 881 * 882 * @see #excludeTarget(View, boolean) 883 * @see #excludeChildren(int, boolean) 884 * @see #excludeChildren(Class, boolean) 885 * 886 * @param target The target to ignore when running this transition. 887 * @param exclude Whether to add the target to or remove the target from the 888 * current list of excluded targets. 889 * @return This transition object. 890 */ 891 public Transition excludeChildren(View target, boolean exclude) { 892 mTargetChildExcludes = excludeView(mTargetChildExcludes, target, exclude); 893 return this; 894 } 895 896 /** 897 * Utility method to manage the boilerplate code that is the same whether we 898 * are excluding targets or their children. 899 */ 900 private ArrayList<View> excludeView(ArrayList<View> list, View target, boolean exclude) { 901 if (target != null) { 902 if (exclude) { 903 list = ArrayListManager.add(list, target); 904 } else { 905 list = ArrayListManager.remove(list, target); 906 } 907 } 908 return list; 909 } 910 911 /** 912 * Whether to add the given type to the list of types to exclude from this 913 * transition. The <code>exclude</code> parameter specifies whether the target 914 * type should be added to or removed from the excluded list. 915 * 916 * <p>Excluding targets is a general mechanism for allowing transitions to run on 917 * a view hierarchy while skipping target views that should not be part of 918 * the transition. For example, you may want to avoid animating children 919 * of a specific ListView or Spinner. Views can be excluded either by their 920 * id, or by their instance reference, or by the Class of that view 921 * (eg, {@link Spinner}).</p> 922 * 923 * @see #excludeChildren(Class, boolean) 924 * @see #excludeTarget(int, boolean) 925 * @see #excludeTarget(View, boolean) 926 * 927 * @param type The type to ignore when running this transition. 928 * @param exclude Whether to add the target type to or remove it from the 929 * current list of excluded target types. 930 * @return This transition object. 931 */ 932 public Transition excludeTarget(Class type, boolean exclude) { 933 mTargetTypeExcludes = excludeType(mTargetTypeExcludes, type, exclude); 934 return this; 935 } 936 937 /** 938 * Whether to add the given type to the list of types whose children should 939 * be excluded from this transition. The <code>exclude</code> parameter 940 * specifies whether the target type should be added to or removed from 941 * the excluded list. 942 * 943 * <p>Excluding targets is a general mechanism for allowing transitions to run on 944 * a view hierarchy while skipping target views that should not be part of 945 * the transition. For example, you may want to avoid animating children 946 * of a specific ListView or Spinner. Views can be excluded either by their 947 * id, or by their instance reference, or by the Class of that view 948 * (eg, {@link Spinner}).</p> 949 * 950 * @see #excludeTarget(Class, boolean) 951 * @see #excludeChildren(int, boolean) 952 * @see #excludeChildren(View, boolean) 953 * 954 * @param type The type to ignore when running this transition. 955 * @param exclude Whether to add the target type to or remove it from the 956 * current list of excluded target types. 957 * @return This transition object. 958 */ 959 public Transition excludeChildren(Class type, boolean exclude) { 960 mTargetTypeChildExcludes = excludeType(mTargetTypeChildExcludes, type, exclude); 961 return this; 962 } 963 964 /** 965 * Utility method to manage the boilerplate code that is the same whether we 966 * are excluding targets or their children. 967 */ 968 private ArrayList<Class> excludeType(ArrayList<Class> list, Class type, boolean exclude) { 969 if (type != null) { 970 if (exclude) { 971 list = ArrayListManager.add(list, type); 972 } else { 973 list = ArrayListManager.remove(list, type); 974 } 975 } 976 return list; 977 } 978 979 /** 980 * Sets the target view instances that this Transition is interested in 981 * animating. By default, there are no targets, and a Transition will 982 * listen for changes on every view in the hierarchy below the sceneRoot 983 * of the Scene being transitioned into. Setting targets constrains 984 * the Transition to only listen for, and act on, these views. 985 * All other views will be ignored. 986 * 987 * <p>The target list is like the {@link #addTarget(int) targetId} 988 * list except this list specifies the actual View instances, not the ids 989 * of the views. This is an important distinction when scene changes involve 990 * view hierarchies which have been inflated separately; different views may 991 * share the same id but not actually be the same instance. If the transition 992 * should treat those views as the same, then {@link #addTarget(int)} should be used 993 * instead of {@link #addTarget(View)}. If, on the other hand, scene changes involve 994 * changes all within the same view hierarchy, among views which do not 995 * necessarily have ids set on them, then the target list of views may be more 996 * convenient.</p> 997 * 998 * @see #addTarget(int) 999 * @param target A View on which the Transition will act, must be non-null. 1000 * @return The Transition to which the target is added. 1001 * Returning the same object makes it easier to chain calls during 1002 * construction, such as 1003 * <code>transitionSet.addTransitions(new Fade()).addTarget(someView);</code> 1004 */ 1005 public Transition addTarget(View target) { 1006 mTargets.add(target); 1007 return this; 1008 } 1009 1010 /** 1011 * Removes the given target from the list of targets that this Transition 1012 * is interested in animating. 1013 * 1014 * @param target The target view, must be non-null. 1015 * @return Transition The Transition from which the target is removed. 1016 * Returning the same object makes it easier to chain calls during 1017 * construction, such as 1018 * <code>transitionSet.addTransitions(new Fade()).removeTarget(someView);</code> 1019 */ 1020 public Transition removeTarget(View target) { 1021 if (target != null) { 1022 mTargets.remove(target); 1023 } 1024 return this; 1025 } 1026 1027 /** 1028 * Returns the array of target IDs that this transition limits itself to 1029 * tracking and animating. If the array is null for both this method and 1030 * {@link #getTargets()}, then this transition is 1031 * not limited to specific views, and will handle changes to any views 1032 * in the hierarchy of a scene change. 1033 * 1034 * @return the list of target IDs 1035 */ 1036 public List<Integer> getTargetIds() { 1037 return mTargetIds; 1038 } 1039 1040 /** 1041 * Returns the array of target views that this transition limits itself to 1042 * tracking and animating. If the array is null for both this method and 1043 * {@link #getTargetIds()}, then this transition is 1044 * not limited to specific views, and will handle changes to any views 1045 * in the hierarchy of a scene change. 1046 * 1047 * @return the list of target views 1048 */ 1049 public List<View> getTargets() { 1050 return mTargets; 1051 } 1052 1053 /** 1054 * Recursive method that captures values for the given view and the 1055 * hierarchy underneath it. 1056 * @param sceneRoot The root of the view hierarchy being captured 1057 * @param start true if this capture is happening before the scene change, 1058 * false otherwise 1059 */ 1060 void captureValues(ViewGroup sceneRoot, boolean start) { 1061 clearValues(start); 1062 if (mTargetIds.size() > 0 || mTargets.size() > 0) { 1063 if (mTargetIds.size() > 0) { 1064 for (int i = 0; i < mTargetIds.size(); ++i) { 1065 int id = mTargetIds.get(i); 1066 View view = sceneRoot.findViewById(id); 1067 if (view != null) { 1068 TransitionValues values = new TransitionValues(); 1069 values.view = view; 1070 if (start) { 1071 captureStartValues(values); 1072 } else { 1073 captureEndValues(values); 1074 } 1075 capturePropagationValues(values); 1076 if (start) { 1077 mStartValues.viewValues.put(view, values); 1078 if (id >= 0) { 1079 mStartValues.idValues.put(id, values); 1080 } 1081 } else { 1082 mEndValues.viewValues.put(view, values); 1083 if (id >= 0) { 1084 mEndValues.idValues.put(id, values); 1085 } 1086 } 1087 } 1088 } 1089 } 1090 if (mTargets.size() > 0) { 1091 for (int i = 0; i < mTargets.size(); ++i) { 1092 View view = mTargets.get(i); 1093 if (view != null) { 1094 TransitionValues values = new TransitionValues(); 1095 values.view = view; 1096 if (start) { 1097 captureStartValues(values); 1098 } else { 1099 captureEndValues(values); 1100 } 1101 capturePropagationValues(values); 1102 if (start) { 1103 mStartValues.viewValues.put(view, values); 1104 } else { 1105 mEndValues.viewValues.put(view, values); 1106 } 1107 } 1108 } 1109 } 1110 } else { 1111 captureHierarchy(sceneRoot, start); 1112 } 1113 } 1114 1115 /** 1116 * Clear valuesMaps for specified start/end state 1117 * 1118 * @param start true if the start values should be cleared, false otherwise 1119 */ 1120 void clearValues(boolean start) { 1121 if (start) { 1122 mStartValues.viewValues.clear(); 1123 mStartValues.idValues.clear(); 1124 mStartValues.itemIdValues.clear(); 1125 } else { 1126 mEndValues.viewValues.clear(); 1127 mEndValues.idValues.clear(); 1128 mEndValues.itemIdValues.clear(); 1129 } 1130 } 1131 1132 /** 1133 * Recursive method which captures values for an entire view hierarchy, 1134 * starting at some root view. Transitions without targetIDs will use this 1135 * method to capture values for all possible views. 1136 * 1137 * @param view The view for which to capture values. Children of this View 1138 * will also be captured, recursively down to the leaf nodes. 1139 * @param start true if values are being captured in the start scene, false 1140 * otherwise. 1141 */ 1142 private void captureHierarchy(View view, boolean start) { 1143 if (view == null) { 1144 return; 1145 } 1146 boolean isListViewItem = false; 1147 if (view.getParent() instanceof ListView) { 1148 isListViewItem = true; 1149 } 1150 if (isListViewItem && !((ListView) view.getParent()).getAdapter().hasStableIds()) { 1151 // ignore listview children unless we can track them with stable IDs 1152 return; 1153 } 1154 int id = View.NO_ID; 1155 long itemId = View.NO_ID; 1156 if (!isListViewItem) { 1157 id = view.getId(); 1158 } else { 1159 ListView listview = (ListView) view.getParent(); 1160 int position = listview.getPositionForView(view); 1161 itemId = listview.getItemIdAtPosition(position); 1162 view.setHasTransientState(true); 1163 } 1164 if (mTargetIdExcludes != null && mTargetIdExcludes.contains(id)) { 1165 return; 1166 } 1167 if (mTargetExcludes != null && mTargetExcludes.contains(view)) { 1168 return; 1169 } 1170 if (mTargetTypeExcludes != null && view != null) { 1171 int numTypes = mTargetTypeExcludes.size(); 1172 for (int i = 0; i < numTypes; ++i) { 1173 if (mTargetTypeExcludes.get(i).isInstance(view)) { 1174 return; 1175 } 1176 } 1177 } 1178 if (view.getParent() instanceof ViewGroup) { 1179 TransitionValues values = new TransitionValues(); 1180 values.view = view; 1181 if (start) { 1182 captureStartValues(values); 1183 } else { 1184 captureEndValues(values); 1185 } 1186 capturePropagationValues(values); 1187 if (start) { 1188 if (!isListViewItem) { 1189 mStartValues.viewValues.put(view, values); 1190 if (id >= 0) { 1191 mStartValues.idValues.put((int) id, values); 1192 } 1193 } else { 1194 mStartValues.itemIdValues.put(itemId, values); 1195 } 1196 } else { 1197 if (!isListViewItem) { 1198 mEndValues.viewValues.put(view, values); 1199 if (id >= 0) { 1200 mEndValues.idValues.put((int) id, values); 1201 } 1202 } else { 1203 mEndValues.itemIdValues.put(itemId, values); 1204 } 1205 } 1206 } 1207 if (view instanceof ViewGroup) { 1208 // Don't traverse child hierarchy if there are any child-excludes on this view 1209 if (mTargetIdChildExcludes != null && mTargetIdChildExcludes.contains(id)) { 1210 return; 1211 } 1212 if (mTargetChildExcludes != null && mTargetChildExcludes.contains(view)) { 1213 return; 1214 } 1215 if (mTargetTypeChildExcludes != null && view != null) { 1216 int numTypes = mTargetTypeChildExcludes.size(); 1217 for (int i = 0; i < numTypes; ++i) { 1218 if (mTargetTypeChildExcludes.get(i).isInstance(view)) { 1219 return; 1220 } 1221 } 1222 } 1223 ViewGroup parent = (ViewGroup) view; 1224 for (int i = 0; i < parent.getChildCount(); ++i) { 1225 captureHierarchy(parent.getChildAt(i), start); 1226 } 1227 } 1228 } 1229 1230 /** 1231 * This method can be called by transitions to get the TransitionValues for 1232 * any particular view during the transition-playing process. This might be 1233 * necessary, for example, to query the before/after state of related views 1234 * for a given transition. 1235 */ 1236 public TransitionValues getTransitionValues(View view, boolean start) { 1237 if (mParent != null) { 1238 return mParent.getTransitionValues(view, start); 1239 } 1240 TransitionValuesMaps valuesMaps = start ? mStartValues : mEndValues; 1241 TransitionValues values = valuesMaps.viewValues.get(view); 1242 if (values == null) { 1243 int id = view.getId(); 1244 if (id >= 0) { 1245 values = valuesMaps.idValues.get(id); 1246 } 1247 if (values == null && view.getParent() instanceof ListView) { 1248 ListView listview = (ListView) view.getParent(); 1249 int position = listview.getPositionForView(view); 1250 long itemId = listview.getItemIdAtPosition(position); 1251 values = valuesMaps.itemIdValues.get(itemId); 1252 } 1253 // TODO: Doesn't handle the case where a view was parented to a 1254 // ListView (with an itemId), but no longer is 1255 } 1256 return values; 1257 } 1258 1259 /** 1260 * Pauses this transition, sending out calls to {@link 1261 * TransitionListener#onTransitionPause(Transition)} to all listeners 1262 * and pausing all running animators started by this transition. 1263 * 1264 * @hide 1265 */ 1266 public void pause(View sceneRoot) { 1267 if (!mEnded) { 1268 ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators(); 1269 int numOldAnims = runningAnimators.size(); 1270 WindowId windowId = sceneRoot.getWindowId(); 1271 for (int i = numOldAnims - 1; i >= 0; i--) { 1272 AnimationInfo info = runningAnimators.valueAt(i); 1273 if (info.view != null && windowId.equals(info.windowId)) { 1274 Animator anim = runningAnimators.keyAt(i); 1275 anim.pause(); 1276 } 1277 } 1278 if (mListeners != null && mListeners.size() > 0) { 1279 ArrayList<TransitionListener> tmpListeners = 1280 (ArrayList<TransitionListener>) mListeners.clone(); 1281 int numListeners = tmpListeners.size(); 1282 for (int i = 0; i < numListeners; ++i) { 1283 tmpListeners.get(i).onTransitionPause(this); 1284 } 1285 } 1286 mPaused = true; 1287 } 1288 } 1289 1290 /** 1291 * Resumes this transition, sending out calls to {@link 1292 * TransitionListener#onTransitionPause(Transition)} to all listeners 1293 * and pausing all running animators started by this transition. 1294 * 1295 * @hide 1296 */ 1297 public void resume(View sceneRoot) { 1298 if (mPaused) { 1299 if (!mEnded) { 1300 ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators(); 1301 int numOldAnims = runningAnimators.size(); 1302 WindowId windowId = sceneRoot.getWindowId(); 1303 for (int i = numOldAnims - 1; i >= 0; i--) { 1304 AnimationInfo info = runningAnimators.valueAt(i); 1305 if (info.view != null && windowId.equals(info.windowId)) { 1306 Animator anim = runningAnimators.keyAt(i); 1307 anim.resume(); 1308 } 1309 } 1310 if (mListeners != null && mListeners.size() > 0) { 1311 ArrayList<TransitionListener> tmpListeners = 1312 (ArrayList<TransitionListener>) mListeners.clone(); 1313 int numListeners = tmpListeners.size(); 1314 for (int i = 0; i < numListeners; ++i) { 1315 tmpListeners.get(i).onTransitionResume(this); 1316 } 1317 } 1318 } 1319 mPaused = false; 1320 } 1321 } 1322 1323 /** 1324 * Called by TransitionManager to play the transition. This calls 1325 * createAnimators() to set things up and create all of the animations and then 1326 * runAnimations() to actually start the animations. 1327 */ 1328 void playTransition(ViewGroup sceneRoot) { 1329 ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators(); 1330 int numOldAnims = runningAnimators.size(); 1331 for (int i = numOldAnims - 1; i >= 0; i--) { 1332 Animator anim = runningAnimators.keyAt(i); 1333 if (anim != null) { 1334 AnimationInfo oldInfo = runningAnimators.get(anim); 1335 if (oldInfo != null && oldInfo.view != null && 1336 oldInfo.view.getContext() == sceneRoot.getContext()) { 1337 boolean cancel = false; 1338 TransitionValues oldValues = oldInfo.values; 1339 View oldView = oldInfo.view; 1340 TransitionValues newValues = mEndValues.viewValues != null ? 1341 mEndValues.viewValues.get(oldView) : null; 1342 if (newValues == null) { 1343 newValues = mEndValues.idValues.get(oldView.getId()); 1344 } 1345 if (oldValues != null) { 1346 // if oldValues null, then transition didn't care to stash values, 1347 // and won't get canceled 1348 if (newValues != null) { 1349 for (String key : oldValues.values.keySet()) { 1350 Object oldValue = oldValues.values.get(key); 1351 Object newValue = newValues.values.get(key); 1352 if (oldValue != null && newValue != null && 1353 !oldValue.equals(newValue)) { 1354 cancel = true; 1355 if (DBG) { 1356 Log.d(LOG_TAG, "Transition.playTransition: " + 1357 "oldValue != newValue for " + key + 1358 ": old, new = " + oldValue + ", " + newValue); 1359 } 1360 break; 1361 } 1362 } 1363 } 1364 } 1365 if (cancel) { 1366 if (anim.isRunning() || anim.isStarted()) { 1367 if (DBG) { 1368 Log.d(LOG_TAG, "Canceling anim " + anim); 1369 } 1370 anim.cancel(); 1371 } else { 1372 if (DBG) { 1373 Log.d(LOG_TAG, "removing anim from info list: " + anim); 1374 } 1375 runningAnimators.remove(anim); 1376 } 1377 } 1378 } 1379 } 1380 } 1381 1382 createAnimators(sceneRoot, mStartValues, mEndValues); 1383 runAnimators(); 1384 } 1385 1386 /** 1387 * This is a utility method used by subclasses to handle standard parts of 1388 * setting up and running an Animator: it sets the {@link #getDuration() 1389 * duration} and the {@link #getStartDelay() startDelay}, starts the 1390 * animation, and, when the animator ends, calls {@link #end()}. 1391 * 1392 * @param animator The Animator to be run during this transition. 1393 * 1394 * @hide 1395 */ 1396 protected void animate(Animator animator) { 1397 // TODO: maybe pass auto-end as a boolean parameter? 1398 if (animator == null) { 1399 end(); 1400 } else { 1401 if (getDuration() >= 0) { 1402 animator.setDuration(getDuration()); 1403 } 1404 if (getStartDelay() >= 0) { 1405 animator.setStartDelay(getStartDelay() + animator.getStartDelay()); 1406 } 1407 if (getInterpolator() != null) { 1408 animator.setInterpolator(getInterpolator()); 1409 } 1410 animator.addListener(new AnimatorListenerAdapter() { 1411 @Override 1412 public void onAnimationEnd(Animator animation) { 1413 end(); 1414 animation.removeListener(this); 1415 } 1416 }); 1417 animator.start(); 1418 } 1419 } 1420 1421 /** 1422 * This method is called automatically by the transition and 1423 * TransitionSet classes prior to a Transition subclass starting; 1424 * subclasses should not need to call it directly. 1425 * 1426 * @hide 1427 */ 1428 protected void start() { 1429 if (mNumInstances == 0) { 1430 if (mListeners != null && mListeners.size() > 0) { 1431 ArrayList<TransitionListener> tmpListeners = 1432 (ArrayList<TransitionListener>) mListeners.clone(); 1433 int numListeners = tmpListeners.size(); 1434 for (int i = 0; i < numListeners; ++i) { 1435 tmpListeners.get(i).onTransitionStart(this); 1436 } 1437 } 1438 mEnded = false; 1439 } 1440 mNumInstances++; 1441 } 1442 1443 /** 1444 * This method is called automatically by the Transition and 1445 * TransitionSet classes when a transition finishes, either because 1446 * a transition did nothing (returned a null Animator from 1447 * {@link Transition#createAnimator(ViewGroup, TransitionValues, 1448 * TransitionValues)}) or because the transition returned a valid 1449 * Animator and end() was called in the onAnimationEnd() 1450 * callback of the AnimatorListener. 1451 * 1452 * @hide 1453 */ 1454 protected void end() { 1455 --mNumInstances; 1456 if (mNumInstances == 0) { 1457 if (mListeners != null && mListeners.size() > 0) { 1458 ArrayList<TransitionListener> tmpListeners = 1459 (ArrayList<TransitionListener>) mListeners.clone(); 1460 int numListeners = tmpListeners.size(); 1461 for (int i = 0; i < numListeners; ++i) { 1462 tmpListeners.get(i).onTransitionEnd(this); 1463 } 1464 } 1465 for (int i = 0; i < mStartValues.itemIdValues.size(); ++i) { 1466 TransitionValues tv = mStartValues.itemIdValues.valueAt(i); 1467 View v = tv.view; 1468 if (v.hasTransientState()) { 1469 v.setHasTransientState(false); 1470 } 1471 } 1472 for (int i = 0; i < mEndValues.itemIdValues.size(); ++i) { 1473 TransitionValues tv = mEndValues.itemIdValues.valueAt(i); 1474 View v = tv.view; 1475 if (v.hasTransientState()) { 1476 v.setHasTransientState(false); 1477 } 1478 } 1479 mEnded = true; 1480 } 1481 } 1482 1483 /** 1484 * This method cancels a transition that is currently running. 1485 * 1486 * @hide 1487 */ 1488 protected void cancel() { 1489 int numAnimators = mCurrentAnimators.size(); 1490 for (int i = numAnimators - 1; i >= 0; i--) { 1491 Animator animator = mCurrentAnimators.get(i); 1492 animator.cancel(); 1493 } 1494 if (mListeners != null && mListeners.size() > 0) { 1495 ArrayList<TransitionListener> tmpListeners = 1496 (ArrayList<TransitionListener>) mListeners.clone(); 1497 int numListeners = tmpListeners.size(); 1498 for (int i = 0; i < numListeners; ++i) { 1499 tmpListeners.get(i).onTransitionCancel(this); 1500 } 1501 } 1502 } 1503 1504 /** 1505 * Adds a listener to the set of listeners that are sent events through the 1506 * life of an animation, such as start, repeat, and end. 1507 * 1508 * @param listener the listener to be added to the current set of listeners 1509 * for this animation. 1510 * @return This transition object. 1511 */ 1512 public Transition addListener(TransitionListener listener) { 1513 if (mListeners == null) { 1514 mListeners = new ArrayList<TransitionListener>(); 1515 } 1516 mListeners.add(listener); 1517 return this; 1518 } 1519 1520 /** 1521 * Removes a listener from the set listening to this animation. 1522 * 1523 * @param listener the listener to be removed from the current set of 1524 * listeners for this transition. 1525 * @return This transition object. 1526 */ 1527 public Transition removeListener(TransitionListener listener) { 1528 if (mListeners == null) { 1529 return this; 1530 } 1531 mListeners.remove(listener); 1532 if (mListeners.size() == 0) { 1533 mListeners = null; 1534 } 1535 return this; 1536 } 1537 1538 /** 1539 * Sets the callback to use to find the epicenter of a Transition. A null value indicates 1540 * that there is no epicenter in the Transition and getEpicenter() will return null. 1541 * Transitions like {@link android.transition.Explode} use a point or Rect to orient 1542 * the direction of travel. This is called the epicenter of the Transition and is 1543 * typically centered on a touched View. The 1544 * {@link android.transition.Transition.EpicenterCallback} allows a Transition to 1545 * dynamically retrieve the epicenter during a Transition. 1546 * @param epicenterCallback The callback to use to find the epicenter of the Transition. 1547 */ 1548 public void setEpicenterCallback(EpicenterCallback epicenterCallback) { 1549 mEpicenterCallback = epicenterCallback; 1550 } 1551 1552 /** 1553 * Returns the callback used to find the epicenter of the Transition. 1554 * Transitions like {@link android.transition.Explode} use a point or Rect to orient 1555 * the direction of travel. This is called the epicenter of the Transition and is 1556 * typically centered on a touched View. The 1557 * {@link android.transition.Transition.EpicenterCallback} allows a Transition to 1558 * dynamically retrieve the epicenter during a Transition. 1559 * @return the callback used to find the epicenter of the Transition. 1560 */ 1561 public EpicenterCallback getEpicenterCallback() { 1562 return mEpicenterCallback; 1563 } 1564 1565 /** 1566 * Returns the epicenter as specified by the 1567 * {@link android.transition.Transition.EpicenterCallback} or null if no callback exists. 1568 * @return the epicenter as specified by the 1569 * {@link android.transition.Transition.EpicenterCallback} or null if no callback exists. 1570 * @see #setEpicenterCallback(android.transition.Transition.EpicenterCallback) 1571 */ 1572 public Rect getEpicenter() { 1573 if (mEpicenterCallback == null) { 1574 return null; 1575 } 1576 return mEpicenterCallback.getEpicenter(this); 1577 } 1578 1579 /** 1580 * Sets the method for determining Animator start delays. 1581 * When a Transition affects several Views like {@link android.transition.Explode} or 1582 * {@link android.transition.Slide}, there may be a desire to have a "wave-front" effect 1583 * such that the Animator start delay depends on position of the View. The 1584 * TransitionPropagation specifies how the start delays are calculated. 1585 * @param transitionPropagation The class used to determine the start delay of 1586 * Animators created by this Transition. A null value 1587 * indicates that no delay should be used. 1588 */ 1589 public void setPropagation(TransitionPropagation transitionPropagation) { 1590 mPropagation = transitionPropagation; 1591 } 1592 1593 /** 1594 * Returns the {@link android.transition.TransitionPropagation} used to calculate Animator start 1595 * delays. 1596 * When a Transition affects several Views like {@link android.transition.Explode} or 1597 * {@link android.transition.Slide}, there may be a desire to have a "wave-front" effect 1598 * such that the Animator start delay depends on position of the View. The 1599 * TransitionPropagation specifies how the start delays are calculated. 1600 * @return the {@link android.transition.TransitionPropagation} used to calculate Animator start 1601 * delays. This is null by default. 1602 */ 1603 public TransitionPropagation getPropagation() { 1604 return mPropagation; 1605 } 1606 1607 /** 1608 * Captures TransitionPropagation values for the given view and the 1609 * hierarchy underneath it. 1610 */ 1611 void capturePropagationValues(TransitionValues transitionValues) { 1612 if (mPropagation != null && !transitionValues.values.isEmpty()) { 1613 String[] propertyNames = mPropagation.getPropagationProperties(); 1614 if (propertyNames == null) { 1615 return; 1616 } 1617 boolean containsAll = true; 1618 for (int i = 0; i < propertyNames.length; i++) { 1619 if (!transitionValues.values.containsKey(propertyNames[i])) { 1620 containsAll = false; 1621 break; 1622 } 1623 } 1624 if (!containsAll) { 1625 mPropagation.captureValues(transitionValues); 1626 } 1627 } 1628 } 1629 1630 Transition setSceneRoot(ViewGroup sceneRoot) { 1631 mSceneRoot = sceneRoot; 1632 return this; 1633 } 1634 1635 void setCanRemoveViews(boolean canRemoveViews) { 1636 mCanRemoveViews = canRemoveViews; 1637 } 1638 1639 public boolean canRemoveViews() { 1640 return mCanRemoveViews; 1641 } 1642 1643 @Override 1644 public String toString() { 1645 return toString(""); 1646 } 1647 1648 @Override 1649 public Transition clone() { 1650 Transition clone = null; 1651 try { 1652 clone = (Transition) super.clone(); 1653 clone.mAnimators = new ArrayList<Animator>(); 1654 clone.mStartValues = new TransitionValuesMaps(); 1655 clone.mEndValues = new TransitionValuesMaps(); 1656 } catch (CloneNotSupportedException e) {} 1657 1658 return clone; 1659 } 1660 1661 /** 1662 * Returns the name of this Transition. This name is used internally to distinguish 1663 * between different transitions to determine when interrupting transitions overlap. 1664 * For example, a ChangeBounds running on the same target view as another ChangeBounds 1665 * should determine whether the old transition is animating to different end values 1666 * and should be canceled in favor of the new transition. 1667 * 1668 * <p>By default, a Transition's name is simply the value of {@link Class#getName()}, 1669 * but subclasses are free to override and return something different.</p> 1670 * 1671 * @return The name of this transition. 1672 */ 1673 public String getName() { 1674 return mName; 1675 } 1676 1677 String toString(String indent) { 1678 String result = indent + getClass().getSimpleName() + "@" + 1679 Integer.toHexString(hashCode()) + ": "; 1680 if (mDuration != -1) { 1681 result += "dur(" + mDuration + ") "; 1682 } 1683 if (mStartDelay != -1) { 1684 result += "dly(" + mStartDelay + ") "; 1685 } 1686 if (mInterpolator != null) { 1687 result += "interp(" + mInterpolator + ") "; 1688 } 1689 if (mTargetIds.size() > 0 || mTargets.size() > 0) { 1690 result += "tgts("; 1691 if (mTargetIds.size() > 0) { 1692 for (int i = 0; i < mTargetIds.size(); ++i) { 1693 if (i > 0) { 1694 result += ", "; 1695 } 1696 result += mTargetIds.get(i); 1697 } 1698 } 1699 if (mTargets.size() > 0) { 1700 for (int i = 0; i < mTargets.size(); ++i) { 1701 if (i > 0) { 1702 result += ", "; 1703 } 1704 result += mTargets.get(i); 1705 } 1706 } 1707 result += ")"; 1708 } 1709 return result; 1710 } 1711 1712 /** 1713 * A transition listener receives notifications from a transition. 1714 * Notifications indicate transition lifecycle events. 1715 */ 1716 public static interface TransitionListener { 1717 /** 1718 * Notification about the start of the transition. 1719 * 1720 * @param transition The started transition. 1721 */ 1722 void onTransitionStart(Transition transition); 1723 1724 /** 1725 * Notification about the end of the transition. Canceled transitions 1726 * will always notify listeners of both the cancellation and end 1727 * events. That is, {@link #onTransitionEnd(Transition)} is always called, 1728 * regardless of whether the transition was canceled or played 1729 * through to completion. 1730 * 1731 * @param transition The transition which reached its end. 1732 */ 1733 void onTransitionEnd(Transition transition); 1734 1735 /** 1736 * Notification about the cancellation of the transition. 1737 * Note that cancel may be called by a parent {@link TransitionSet} on 1738 * a child transition which has not yet started. This allows the child 1739 * transition to restore state on target objects which was set at 1740 * {@link #createAnimator(android.view.ViewGroup, TransitionValues, TransitionValues) 1741 * createAnimator()} time. 1742 * 1743 * @param transition The transition which was canceled. 1744 */ 1745 void onTransitionCancel(Transition transition); 1746 1747 /** 1748 * Notification when a transition is paused. 1749 * Note that createAnimator() may be called by a parent {@link TransitionSet} on 1750 * a child transition which has not yet started. This allows the child 1751 * transition to restore state on target objects which was set at 1752 * {@link #createAnimator(android.view.ViewGroup, TransitionValues, TransitionValues) 1753 * createAnimator()} time. 1754 * 1755 * @param transition The transition which was paused. 1756 */ 1757 void onTransitionPause(Transition transition); 1758 1759 /** 1760 * Notification when a transition is resumed. 1761 * Note that resume() may be called by a parent {@link TransitionSet} on 1762 * a child transition which has not yet started. This allows the child 1763 * transition to restore state which may have changed in an earlier call 1764 * to {@link #onTransitionPause(Transition)}. 1765 * 1766 * @param transition The transition which was resumed. 1767 */ 1768 void onTransitionResume(Transition transition); 1769 } 1770 1771 /** 1772 * Utility adapter class to avoid having to override all three methods 1773 * whenever someone just wants to listen for a single event. 1774 * 1775 * @hide 1776 * */ 1777 public static class TransitionListenerAdapter implements TransitionListener { 1778 @Override 1779 public void onTransitionStart(Transition transition) { 1780 } 1781 1782 @Override 1783 public void onTransitionEnd(Transition transition) { 1784 } 1785 1786 @Override 1787 public void onTransitionCancel(Transition transition) { 1788 } 1789 1790 @Override 1791 public void onTransitionPause(Transition transition) { 1792 } 1793 1794 @Override 1795 public void onTransitionResume(Transition transition) { 1796 } 1797 } 1798 1799 /** 1800 * Holds information about each animator used when a new transition starts 1801 * while other transitions are still running to determine whether a running 1802 * animation should be canceled or a new animation noop'd. The structure holds 1803 * information about the state that an animation is going to, to be compared to 1804 * end state of a new animation. 1805 * @hide 1806 */ 1807 public static class AnimationInfo { 1808 public View view; 1809 String name; 1810 TransitionValues values; 1811 WindowId windowId; 1812 1813 AnimationInfo(View view, String name, WindowId windowId, TransitionValues values) { 1814 this.view = view; 1815 this.name = name; 1816 this.values = values; 1817 this.windowId = windowId; 1818 } 1819 } 1820 1821 /** 1822 * Utility class for managing typed ArrayLists efficiently. In particular, this 1823 * can be useful for lists that we don't expect to be used often (eg, the exclude 1824 * lists), so we'd like to keep them nulled out by default. This causes the code to 1825 * become tedious, with constant null checks, code to allocate when necessary, 1826 * and code to null out the reference when the list is empty. This class encapsulates 1827 * all of that functionality into simple add()/remove() methods which perform the 1828 * necessary checks, allocation/null-out as appropriate, and return the 1829 * resulting list. 1830 */ 1831 private static class ArrayListManager { 1832 1833 /** 1834 * Add the specified item to the list, returning the resulting list. 1835 * The returned list can either the be same list passed in or, if that 1836 * list was null, the new list that was created. 1837 * 1838 * Note that the list holds unique items; if the item already exists in the 1839 * list, the list is not modified. 1840 */ 1841 static <T> ArrayList<T> add(ArrayList<T> list, T item) { 1842 if (list == null) { 1843 list = new ArrayList<T>(); 1844 } 1845 if (!list.contains(item)) { 1846 list.add(item); 1847 } 1848 return list; 1849 } 1850 1851 /** 1852 * Remove the specified item from the list, returning the resulting list. 1853 * The returned list can either the be same list passed in or, if that 1854 * list becomes empty as a result of the remove(), the new list was created. 1855 */ 1856 static <T> ArrayList<T> remove(ArrayList<T> list, T item) { 1857 if (list != null) { 1858 list.remove(item); 1859 if (list.isEmpty()) { 1860 list = null; 1861 } 1862 } 1863 return list; 1864 } 1865 } 1866 1867 /** 1868 * Class to get the epicenter of Transition. Use 1869 * {@link #setEpicenterCallback(android.transition.Transition.EpicenterCallback)} to 1870 * set the callback used to calculate the epicenter of the Transition. Override 1871 * {@link #getEpicenter()} to return the rectangular region in screen coordinates of 1872 * the epicenter of the transition. 1873 * @see #setEpicenterCallback(android.transition.Transition.EpicenterCallback) 1874 */ 1875 public static abstract class EpicenterCallback { 1876 1877 /** 1878 * Implementers must override to return the epicenter of the Transition in screen 1879 * coordinates. Transitions like {@link android.transition.Explode} depend upon 1880 * an epicenter for the Transition. In Explode, Views move toward or away from the 1881 * center of the epicenter Rect along the vector between the epicenter and the center 1882 * of the View appearing and disappearing. Some Transitions, such as 1883 * {@link android.transition.Fade} pay no attention to the epicenter. 1884 * 1885 * @param transition The transition for which the epicenter applies. 1886 * @return The Rect region of the epicenter of <code>transition</code> or null if 1887 * there is no epicenter. 1888 */ 1889 public abstract Rect getEpicenter(Transition transition); 1890 } 1891} 1892