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 17 package android.transition; 18 19 import android.animation.TimeInterpolator; 20 import android.content.Context; 21 import android.content.res.TypedArray; 22 import android.util.AndroidRuntimeException; 23 import android.util.AttributeSet; 24 import android.view.View; 25 import android.view.ViewGroup; 26 27 import com.android.internal.R; 28 29 import java.util.ArrayList; 30 31 /** 32 * A TransitionSet is a parent of child transitions (including other 33 * TransitionSets). Using TransitionSets enables more complex 34 * choreography of transitions, where some sets play {@link #ORDERING_TOGETHER} and 35 * others play {@link #ORDERING_SEQUENTIAL}. For example, {@link AutoTransition} 36 * uses a TransitionSet to sequentially play a Fade(Fade.OUT), followed by 37 * a {@link ChangeBounds}, followed by a Fade(Fade.OUT) transition. 38 * 39 * <p>A TransitionSet can be described in a resource file by using the 40 * tag <code>transitionSet</code>, along with the standard 41 * attributes of {@link android.R.styleable#TransitionSet} and 42 * {@link android.R.styleable#Transition}. Child transitions of the 43 * TransitionSet object can be loaded by adding those child tags inside the 44 * enclosing <code>transitionSet</code> tag. For example, the following xml 45 * describes a TransitionSet that plays a Fade and then a ChangeBounds 46 * transition on the affected view targets:</p> 47 * <pre> 48 * <transitionSet xmlns:android="http://schemas.android.com/apk/res/android" 49 * android:transitionOrdering="sequential"> 50 * <fade/> 51 * <changeBounds/> 52 * </transitionSet> 53 * </pre> 54 */ 55 public class TransitionSet extends Transition { 56 /** 57 * Flag indicating the the interpolator changed. 58 */ 59 private static final int FLAG_CHANGE_INTERPOLATOR = 0x01; 60 /** 61 * Flag indicating the the propagation changed. 62 */ 63 private static final int FLAG_CHANGE_PROPAGATION = 0x02; 64 /** 65 * Flag indicating the the path motion changed. 66 */ 67 private static final int FLAG_CHANGE_PATH_MOTION = 0x04; 68 /** 69 * Flag indicating the the epicentera callback changed. 70 */ 71 static final int FLAG_CHANGE_EPICENTER = 0x08; 72 73 ArrayList<Transition> mTransitions = new ArrayList<Transition>(); 74 private boolean mPlayTogether = true; 75 int mCurrentListeners; 76 boolean mStarted = false; 77 // Flags to know whether or not the interpolator, path motion, epicenter, propagation 78 // have changed 79 private int mChangeFlags = 0; 80 81 /** 82 * A flag used to indicate that the child transitions of this set 83 * should all start at the same time. 84 */ 85 public static final int ORDERING_TOGETHER = 0; 86 /** 87 * A flag used to indicate that the child transitions of this set should 88 * play in sequence; when one child transition ends, the next child 89 * transition begins. Note that a transition does not end until all 90 * instances of it (which are playing on all applicable targets of the 91 * transition) end. 92 */ 93 public static final int ORDERING_SEQUENTIAL = 1; 94 95 /** 96 * Constructs an empty transition set. Add child transitions to the 97 * set by calling {@link #addTransition(Transition)} )}. By default, 98 * child transitions will play {@link #ORDERING_TOGETHER together}. 99 */ TransitionSet()100 public TransitionSet() { 101 } 102 TransitionSet(Context context, AttributeSet attrs)103 public TransitionSet(Context context, AttributeSet attrs) { 104 super(context, attrs); 105 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TransitionSet); 106 int ordering = a.getInt(R.styleable.TransitionSet_transitionOrdering, 107 TransitionSet.ORDERING_TOGETHER); 108 setOrdering(ordering); 109 a.recycle(); 110 } 111 112 /** 113 * Sets the play order of this set's child transitions. 114 * 115 * @param ordering {@link #ORDERING_TOGETHER} to play this set's child 116 * transitions together, {@link #ORDERING_SEQUENTIAL} to play the child 117 * transitions in sequence. 118 * @return This transitionSet object. 119 */ setOrdering(int ordering)120 public TransitionSet setOrdering(int ordering) { 121 switch (ordering) { 122 case ORDERING_SEQUENTIAL: 123 mPlayTogether = false; 124 break; 125 case ORDERING_TOGETHER: 126 mPlayTogether = true; 127 break; 128 default: 129 throw new AndroidRuntimeException("Invalid parameter for TransitionSet " + 130 "ordering: " + ordering); 131 } 132 return this; 133 } 134 135 /** 136 * Returns the ordering of this TransitionSet. By default, the value is 137 * {@link #ORDERING_TOGETHER}. 138 * 139 * @return {@link #ORDERING_TOGETHER} if child transitions will play at the same 140 * time, {@link #ORDERING_SEQUENTIAL} if they will play in sequence. 141 * 142 * @see #setOrdering(int) 143 */ getOrdering()144 public int getOrdering() { 145 return mPlayTogether ? ORDERING_TOGETHER : ORDERING_SEQUENTIAL; 146 } 147 148 /** 149 * Adds child transition to this set. The order in which this child transition 150 * is added relative to other child transitions that are added, in addition to 151 * the {@link #getOrdering() ordering} property, determines the 152 * order in which the transitions are started. 153 * 154 * <p>If this transitionSet has a {@link #getDuration() duration}, 155 * {@link #getInterpolator() interpolator}, {@link #getPropagation() propagation delay}, 156 * {@link #getPathMotion() path motion}, or 157 * {@link #setEpicenterCallback(EpicenterCallback) epicenter callback} 158 * set on it, the child transition will inherit the values that are set. 159 * Transitions are assumed to have a maximum of one transitionSet parent.</p> 160 * 161 * @param transition A non-null child transition to be added to this set. 162 * @return This transitionSet object. 163 */ addTransition(Transition transition)164 public TransitionSet addTransition(Transition transition) { 165 if (transition != null) { 166 addTransitionInternal(transition); 167 if (mDuration >= 0) { 168 transition.setDuration(mDuration); 169 } 170 if ((mChangeFlags & FLAG_CHANGE_INTERPOLATOR) != 0) { 171 transition.setInterpolator(getInterpolator()); 172 } 173 if ((mChangeFlags & FLAG_CHANGE_PROPAGATION) != 0) { 174 transition.setPropagation(getPropagation()); 175 } 176 if ((mChangeFlags & FLAG_CHANGE_PATH_MOTION) != 0) { 177 transition.setPathMotion(getPathMotion()); 178 } 179 if ((mChangeFlags & FLAG_CHANGE_EPICENTER) != 0) { 180 transition.setEpicenterCallback(getEpicenterCallback()); 181 } 182 } 183 return this; 184 } 185 addTransitionInternal(Transition transition)186 private void addTransitionInternal(Transition transition) { 187 mTransitions.add(transition); 188 transition.mParent = this; 189 } 190 191 /** 192 * Returns the number of child transitions in the TransitionSet. 193 * 194 * @return The number of child transitions in the TransitionSet. 195 * @see #addTransition(Transition) 196 * @see #getTransitionAt(int) 197 */ getTransitionCount()198 public int getTransitionCount() { 199 return mTransitions.size(); 200 } 201 202 /** 203 * Returns the child Transition at the specified position in the TransitionSet. 204 * 205 * @param index The position of the Transition to retrieve. 206 * @see #addTransition(Transition) 207 * @see #getTransitionCount() 208 */ getTransitionAt(int index)209 public Transition getTransitionAt(int index) { 210 if (index < 0 || index >= mTransitions.size()) { 211 return null; 212 } 213 return mTransitions.get(index); 214 } 215 216 /** 217 * Setting a non-negative duration on a TransitionSet causes all of the child 218 * transitions (current and future) to inherit this duration. 219 * 220 * @param duration The length of the animation, in milliseconds. 221 * @return This transitionSet object. 222 */ 223 @Override setDuration(long duration)224 public TransitionSet setDuration(long duration) { 225 super.setDuration(duration); 226 if (mDuration >= 0 && mTransitions != null) { 227 int numTransitions = mTransitions.size(); 228 for (int i = 0; i < numTransitions; ++i) { 229 mTransitions.get(i).setDuration(duration); 230 } 231 } 232 return this; 233 } 234 235 @Override setStartDelay(long startDelay)236 public TransitionSet setStartDelay(long startDelay) { 237 return (TransitionSet) super.setStartDelay(startDelay); 238 } 239 240 @Override setInterpolator(TimeInterpolator interpolator)241 public TransitionSet setInterpolator(TimeInterpolator interpolator) { 242 mChangeFlags |= FLAG_CHANGE_INTERPOLATOR; 243 if (mTransitions != null) { 244 int numTransitions = mTransitions.size(); 245 for (int i = 0; i < numTransitions; ++i) { 246 mTransitions.get(i).setInterpolator(interpolator); 247 } 248 } 249 return (TransitionSet) super.setInterpolator(interpolator); 250 } 251 252 @Override addTarget(View target)253 public TransitionSet addTarget(View target) { 254 for (int i = 0; i < mTransitions.size(); i++) { 255 mTransitions.get(i).addTarget(target); 256 } 257 return (TransitionSet) super.addTarget(target); 258 } 259 260 @Override addTarget(int targetId)261 public TransitionSet addTarget(int targetId) { 262 for (int i = 0; i < mTransitions.size(); i++) { 263 mTransitions.get(i).addTarget(targetId); 264 } 265 return (TransitionSet) super.addTarget(targetId); 266 } 267 268 @Override addTarget(String targetName)269 public TransitionSet addTarget(String targetName) { 270 for (int i = 0; i < mTransitions.size(); i++) { 271 mTransitions.get(i).addTarget(targetName); 272 } 273 return (TransitionSet) super.addTarget(targetName); 274 } 275 276 @Override addTarget(Class targetType)277 public TransitionSet addTarget(Class targetType) { 278 for (int i = 0; i < mTransitions.size(); i++) { 279 mTransitions.get(i).addTarget(targetType); 280 } 281 return (TransitionSet) super.addTarget(targetType); 282 } 283 284 @Override addListener(TransitionListener listener)285 public TransitionSet addListener(TransitionListener listener) { 286 return (TransitionSet) super.addListener(listener); 287 } 288 289 @Override removeTarget(int targetId)290 public TransitionSet removeTarget(int targetId) { 291 for (int i = 0; i < mTransitions.size(); i++) { 292 mTransitions.get(i).removeTarget(targetId); 293 } 294 return (TransitionSet) super.removeTarget(targetId); 295 } 296 297 @Override removeTarget(View target)298 public TransitionSet removeTarget(View target) { 299 for (int i = 0; i < mTransitions.size(); i++) { 300 mTransitions.get(i).removeTarget(target); 301 } 302 return (TransitionSet) super.removeTarget(target); 303 } 304 305 @Override removeTarget(Class target)306 public TransitionSet removeTarget(Class target) { 307 for (int i = 0; i < mTransitions.size(); i++) { 308 mTransitions.get(i).removeTarget(target); 309 } 310 return (TransitionSet) super.removeTarget(target); 311 } 312 313 @Override removeTarget(String target)314 public TransitionSet removeTarget(String target) { 315 for (int i = 0; i < mTransitions.size(); i++) { 316 mTransitions.get(i).removeTarget(target); 317 } 318 return (TransitionSet) super.removeTarget(target); 319 } 320 321 @Override excludeTarget(View target, boolean exclude)322 public Transition excludeTarget(View target, boolean exclude) { 323 for (int i = 0; i < mTransitions.size(); i++) { 324 mTransitions.get(i).excludeTarget(target, exclude); 325 } 326 return super.excludeTarget(target, exclude); 327 } 328 329 @Override excludeTarget(String targetName, boolean exclude)330 public Transition excludeTarget(String targetName, boolean exclude) { 331 for (int i = 0; i < mTransitions.size(); i++) { 332 mTransitions.get(i).excludeTarget(targetName, exclude); 333 } 334 return super.excludeTarget(targetName, exclude); 335 } 336 337 @Override excludeTarget(int targetId, boolean exclude)338 public Transition excludeTarget(int targetId, boolean exclude) { 339 for (int i = 0; i < mTransitions.size(); i++) { 340 mTransitions.get(i).excludeTarget(targetId, exclude); 341 } 342 return super.excludeTarget(targetId, exclude); 343 } 344 345 @Override excludeTarget(Class type, boolean exclude)346 public Transition excludeTarget(Class type, boolean exclude) { 347 for (int i = 0; i < mTransitions.size(); i++) { 348 mTransitions.get(i).excludeTarget(type, exclude); 349 } 350 return super.excludeTarget(type, exclude); 351 } 352 353 @Override removeListener(TransitionListener listener)354 public TransitionSet removeListener(TransitionListener listener) { 355 return (TransitionSet) super.removeListener(listener); 356 } 357 358 @Override setPathMotion(PathMotion pathMotion)359 public void setPathMotion(PathMotion pathMotion) { 360 super.setPathMotion(pathMotion); 361 mChangeFlags |= FLAG_CHANGE_PATH_MOTION; 362 if (mTransitions != null) { 363 for (int i = 0; i < mTransitions.size(); i++) { 364 mTransitions.get(i).setPathMotion(pathMotion); 365 } 366 } 367 } 368 369 /** 370 * Removes the specified child transition from this set. 371 * 372 * @param transition The transition to be removed. 373 * @return This transitionSet object. 374 */ removeTransition(Transition transition)375 public TransitionSet removeTransition(Transition transition) { 376 mTransitions.remove(transition); 377 transition.mParent = null; 378 return this; 379 } 380 381 /** 382 * Sets up listeners for each of the child transitions. This is used to 383 * determine when this transition set is finished (all child transitions 384 * must finish first). 385 */ setupStartEndListeners()386 private void setupStartEndListeners() { 387 TransitionSetListener listener = new TransitionSetListener(this); 388 for (Transition childTransition : mTransitions) { 389 childTransition.addListener(listener); 390 } 391 mCurrentListeners = mTransitions.size(); 392 } 393 394 /** 395 * This listener is used to detect when all child transitions are done, at 396 * which point this transition set is also done. 397 */ 398 static class TransitionSetListener extends TransitionListenerAdapter { 399 TransitionSet mTransitionSet; TransitionSetListener(TransitionSet transitionSet)400 TransitionSetListener(TransitionSet transitionSet) { 401 mTransitionSet = transitionSet; 402 } 403 @Override onTransitionStart(Transition transition)404 public void onTransitionStart(Transition transition) { 405 if (!mTransitionSet.mStarted) { 406 mTransitionSet.start(); 407 mTransitionSet.mStarted = true; 408 } 409 } 410 411 @Override onTransitionEnd(Transition transition)412 public void onTransitionEnd(Transition transition) { 413 --mTransitionSet.mCurrentListeners; 414 if (mTransitionSet.mCurrentListeners == 0) { 415 // All child trans 416 mTransitionSet.mStarted = false; 417 mTransitionSet.end(); 418 } 419 transition.removeListener(this); 420 } 421 } 422 423 /** 424 * @hide 425 */ 426 @Override createAnimators(ViewGroup sceneRoot, TransitionValuesMaps startValues, TransitionValuesMaps endValues, ArrayList<TransitionValues> startValuesList, ArrayList<TransitionValues> endValuesList)427 protected void createAnimators(ViewGroup sceneRoot, TransitionValuesMaps startValues, 428 TransitionValuesMaps endValues, ArrayList<TransitionValues> startValuesList, 429 ArrayList<TransitionValues> endValuesList) { 430 long startDelay = getStartDelay(); 431 int numTransitions = mTransitions.size(); 432 for (int i = 0; i < numTransitions; i++) { 433 Transition childTransition = mTransitions.get(i); 434 // We only set the start delay on the first transition if we are playing 435 // the transitions sequentially. 436 if (startDelay > 0 && (mPlayTogether || i == 0)) { 437 long childStartDelay = childTransition.getStartDelay(); 438 if (childStartDelay > 0) { 439 childTransition.setStartDelay(startDelay + childStartDelay); 440 } else { 441 childTransition.setStartDelay(startDelay); 442 } 443 } 444 childTransition.createAnimators(sceneRoot, startValues, endValues, startValuesList, 445 endValuesList); 446 } 447 } 448 449 /** 450 * @hide 451 */ 452 @Override runAnimators()453 protected void runAnimators() { 454 if (mTransitions.isEmpty()) { 455 start(); 456 end(); 457 return; 458 } 459 setupStartEndListeners(); 460 int numTransitions = mTransitions.size(); 461 if (!mPlayTogether) { 462 // Setup sequence with listeners 463 // TODO: Need to add listeners in such a way that we can remove them later if canceled 464 for (int i = 1; i < numTransitions; ++i) { 465 Transition previousTransition = mTransitions.get(i - 1); 466 final Transition nextTransition = mTransitions.get(i); 467 previousTransition.addListener(new TransitionListenerAdapter() { 468 @Override 469 public void onTransitionEnd(Transition transition) { 470 nextTransition.runAnimators(); 471 transition.removeListener(this); 472 } 473 }); 474 } 475 Transition firstTransition = mTransitions.get(0); 476 if (firstTransition != null) { 477 firstTransition.runAnimators(); 478 } 479 } else { 480 for (int i = 0; i < numTransitions; ++i) { 481 mTransitions.get(i).runAnimators(); 482 } 483 } 484 } 485 486 @Override captureStartValues(TransitionValues transitionValues)487 public void captureStartValues(TransitionValues transitionValues) { 488 if (isValidTarget(transitionValues.view)) { 489 for (Transition childTransition : mTransitions) { 490 if (childTransition.isValidTarget(transitionValues.view)) { 491 childTransition.captureStartValues(transitionValues); 492 transitionValues.targetedTransitions.add(childTransition); 493 } 494 } 495 } 496 } 497 498 @Override captureEndValues(TransitionValues transitionValues)499 public void captureEndValues(TransitionValues transitionValues) { 500 if (isValidTarget(transitionValues.view)) { 501 for (Transition childTransition : mTransitions) { 502 if (childTransition.isValidTarget(transitionValues.view)) { 503 childTransition.captureEndValues(transitionValues); 504 transitionValues.targetedTransitions.add(childTransition); 505 } 506 } 507 } 508 } 509 510 @Override capturePropagationValues(TransitionValues transitionValues)511 void capturePropagationValues(TransitionValues transitionValues) { 512 super.capturePropagationValues(transitionValues); 513 int numTransitions = mTransitions.size(); 514 for (int i = 0; i < numTransitions; ++i) { 515 mTransitions.get(i).capturePropagationValues(transitionValues); 516 } 517 } 518 519 /** @hide */ 520 @Override pause(View sceneRoot)521 public void pause(View sceneRoot) { 522 super.pause(sceneRoot); 523 int numTransitions = mTransitions.size(); 524 for (int i = 0; i < numTransitions; ++i) { 525 mTransitions.get(i).pause(sceneRoot); 526 } 527 } 528 529 /** @hide */ 530 @Override resume(View sceneRoot)531 public void resume(View sceneRoot) { 532 super.resume(sceneRoot); 533 int numTransitions = mTransitions.size(); 534 for (int i = 0; i < numTransitions; ++i) { 535 mTransitions.get(i).resume(sceneRoot); 536 } 537 } 538 539 /** @hide */ 540 @Override cancel()541 protected void cancel() { 542 super.cancel(); 543 int numTransitions = mTransitions.size(); 544 for (int i = 0; i < numTransitions; ++i) { 545 mTransitions.get(i).cancel(); 546 } 547 } 548 549 /** @hide */ 550 @Override forceToEnd(ViewGroup sceneRoot)551 void forceToEnd(ViewGroup sceneRoot) { 552 super.forceToEnd(sceneRoot); 553 int numTransitions = mTransitions.size(); 554 for (int i = 0; i < numTransitions; ++i) { 555 mTransitions.get(i).forceToEnd(sceneRoot); 556 } 557 } 558 559 @Override setSceneRoot(ViewGroup sceneRoot)560 TransitionSet setSceneRoot(ViewGroup sceneRoot) { 561 super.setSceneRoot(sceneRoot); 562 int numTransitions = mTransitions.size(); 563 for (int i = 0; i < numTransitions; ++i) { 564 mTransitions.get(i).setSceneRoot(sceneRoot); 565 } 566 return (TransitionSet) this; 567 } 568 569 @Override setCanRemoveViews(boolean canRemoveViews)570 void setCanRemoveViews(boolean canRemoveViews) { 571 super.setCanRemoveViews(canRemoveViews); 572 int numTransitions = mTransitions.size(); 573 for (int i = 0; i < numTransitions; ++i) { 574 mTransitions.get(i).setCanRemoveViews(canRemoveViews); 575 } 576 } 577 578 @Override setPropagation(TransitionPropagation propagation)579 public void setPropagation(TransitionPropagation propagation) { 580 super.setPropagation(propagation); 581 mChangeFlags |= FLAG_CHANGE_PROPAGATION; 582 int numTransitions = mTransitions.size(); 583 for (int i = 0; i < numTransitions; ++i) { 584 mTransitions.get(i).setPropagation(propagation); 585 } 586 } 587 588 @Override setEpicenterCallback(EpicenterCallback epicenterCallback)589 public void setEpicenterCallback(EpicenterCallback epicenterCallback) { 590 super.setEpicenterCallback(epicenterCallback); 591 mChangeFlags |= FLAG_CHANGE_EPICENTER; 592 int numTransitions = mTransitions.size(); 593 for (int i = 0; i < numTransitions; ++i) { 594 mTransitions.get(i).setEpicenterCallback(epicenterCallback); 595 } 596 } 597 598 @Override toString(String indent)599 String toString(String indent) { 600 String result = super.toString(indent); 601 for (int i = 0; i < mTransitions.size(); ++i) { 602 result += "\n" + mTransitions.get(i).toString(indent + " "); 603 } 604 return result; 605 } 606 607 @Override clone()608 public TransitionSet clone() { 609 TransitionSet clone = (TransitionSet) super.clone(); 610 clone.mTransitions = new ArrayList<Transition>(); 611 int numTransitions = mTransitions.size(); 612 for (int i = 0; i < numTransitions; ++i) { 613 clone.addTransitionInternal((Transition) mTransitions.get(i).clone()); 614 } 615 return clone; 616 } 617 } 618