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