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