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