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