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 com.android.internal.R; 20 21 import android.animation.TimeInterpolator; 22 import android.content.Context; 23 import android.content.res.TypedArray; 24 import android.util.AndroidRuntimeException; 25 import android.util.AttributeSet; 26 import android.view.View; 27 import android.view.ViewGroup; 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:ordering="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 */ TransitionSet()81 public TransitionSet() { 82 } 83 TransitionSet(Context context, AttributeSet attrs)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 */ setOrdering(int ordering)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 */ getOrdering()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 */ addTransition(Transition transition)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 */ getTransitionCount()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 */ getTransitionAt(int index)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 setDuration(long duration)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 setStartDelay(long startDelay)198 public TransitionSet setStartDelay(long startDelay) { 199 return (TransitionSet) super.setStartDelay(startDelay); 200 } 201 202 @Override setInterpolator(TimeInterpolator interpolator)203 public TransitionSet setInterpolator(TimeInterpolator interpolator) { 204 return (TransitionSet) super.setInterpolator(interpolator); 205 } 206 207 @Override addTarget(View target)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 addTarget(int targetId)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 addTarget(String targetName)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 addTarget(Class targetType)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 addListener(TransitionListener listener)240 public TransitionSet addListener(TransitionListener listener) { 241 return (TransitionSet) super.addListener(listener); 242 } 243 244 @Override removeTarget(int targetId)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 removeTarget(View target)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 removeTarget(Class target)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 removeTarget(String target)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 excludeTarget(View target, boolean exclude)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 excludeTarget(String targetName, boolean exclude)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 excludeTarget(int targetId, boolean exclude)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 excludeTarget(Class type, boolean exclude)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 removeListener(TransitionListener listener)309 public TransitionSet removeListener(TransitionListener listener) { 310 return (TransitionSet) super.removeListener(listener); 311 } 312 313 @Override setPathMotion(PathMotion pathMotion)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 /** @hide */ 322 @Override forceVisibility(int visibility, boolean isStartValue)323 public void forceVisibility(int visibility, boolean isStartValue) { 324 int numTransitions = mTransitions.size(); 325 for (int i = 0; i < numTransitions; i++) { 326 mTransitions.get(i).forceVisibility(visibility, isStartValue); 327 } 328 } 329 330 /** 331 * Removes the specified child transition from this set. 332 * 333 * @param transition The transition to be removed. 334 * @return This transitionSet object. 335 */ removeTransition(Transition transition)336 public TransitionSet removeTransition(Transition transition) { 337 mTransitions.remove(transition); 338 transition.mParent = null; 339 return this; 340 } 341 342 /** 343 * Sets up listeners for each of the child transitions. This is used to 344 * determine when this transition set is finished (all child transitions 345 * must finish first). 346 */ setupStartEndListeners()347 private void setupStartEndListeners() { 348 TransitionSetListener listener = new TransitionSetListener(this); 349 for (Transition childTransition : mTransitions) { 350 childTransition.addListener(listener); 351 } 352 mCurrentListeners = mTransitions.size(); 353 } 354 355 /** 356 * This listener is used to detect when all child transitions are done, at 357 * which point this transition set is also done. 358 */ 359 static class TransitionSetListener extends TransitionListenerAdapter { 360 TransitionSet mTransitionSet; TransitionSetListener(TransitionSet transitionSet)361 TransitionSetListener(TransitionSet transitionSet) { 362 mTransitionSet = transitionSet; 363 } 364 @Override onTransitionStart(Transition transition)365 public void onTransitionStart(Transition transition) { 366 if (!mTransitionSet.mStarted) { 367 mTransitionSet.start(); 368 mTransitionSet.mStarted = true; 369 } 370 } 371 372 @Override onTransitionEnd(Transition transition)373 public void onTransitionEnd(Transition transition) { 374 --mTransitionSet.mCurrentListeners; 375 if (mTransitionSet.mCurrentListeners == 0) { 376 // All child trans 377 mTransitionSet.mStarted = false; 378 mTransitionSet.end(); 379 } 380 transition.removeListener(this); 381 } 382 } 383 384 /** 385 * @hide 386 */ 387 @Override createAnimators(ViewGroup sceneRoot, TransitionValuesMaps startValues, TransitionValuesMaps endValues, ArrayList<TransitionValues> startValuesList, ArrayList<TransitionValues> endValuesList)388 protected void createAnimators(ViewGroup sceneRoot, TransitionValuesMaps startValues, 389 TransitionValuesMaps endValues, ArrayList<TransitionValues> startValuesList, 390 ArrayList<TransitionValues> endValuesList) { 391 long startDelay = getStartDelay(); 392 int numTransitions = mTransitions.size(); 393 for (int i = 0; i < numTransitions; i++) { 394 Transition childTransition = mTransitions.get(i); 395 // We only set the start delay on the first transition if we are playing 396 // the transitions sequentially. 397 if (startDelay > 0 && (mPlayTogether || i == 0)) { 398 long childStartDelay = childTransition.getStartDelay(); 399 if (childStartDelay > 0) { 400 childTransition.setStartDelay(startDelay + childStartDelay); 401 } else { 402 childTransition.setStartDelay(startDelay); 403 } 404 } 405 childTransition.createAnimators(sceneRoot, startValues, endValues, startValuesList, 406 endValuesList); 407 } 408 } 409 410 /** 411 * @hide 412 */ 413 @Override runAnimators()414 protected void runAnimators() { 415 if (mTransitions.isEmpty()) { 416 start(); 417 end(); 418 return; 419 } 420 setupStartEndListeners(); 421 int numTransitions = mTransitions.size(); 422 if (!mPlayTogether) { 423 // Setup sequence with listeners 424 // TODO: Need to add listeners in such a way that we can remove them later if canceled 425 for (int i = 1; i < numTransitions; ++i) { 426 Transition previousTransition = mTransitions.get(i - 1); 427 final Transition nextTransition = mTransitions.get(i); 428 previousTransition.addListener(new TransitionListenerAdapter() { 429 @Override 430 public void onTransitionEnd(Transition transition) { 431 nextTransition.runAnimators(); 432 transition.removeListener(this); 433 } 434 }); 435 } 436 Transition firstTransition = mTransitions.get(0); 437 if (firstTransition != null) { 438 firstTransition.runAnimators(); 439 } 440 } else { 441 for (int i = 0; i < numTransitions; ++i) { 442 mTransitions.get(i).runAnimators(); 443 } 444 } 445 } 446 447 @Override captureStartValues(TransitionValues transitionValues)448 public void captureStartValues(TransitionValues transitionValues) { 449 if (isValidTarget(transitionValues.view)) { 450 for (Transition childTransition : mTransitions) { 451 if (childTransition.isValidTarget(transitionValues.view)) { 452 childTransition.captureStartValues(transitionValues); 453 transitionValues.targetedTransitions.add(childTransition); 454 } 455 } 456 } 457 } 458 459 @Override captureEndValues(TransitionValues transitionValues)460 public void captureEndValues(TransitionValues transitionValues) { 461 if (isValidTarget(transitionValues.view)) { 462 for (Transition childTransition : mTransitions) { 463 if (childTransition.isValidTarget(transitionValues.view)) { 464 childTransition.captureEndValues(transitionValues); 465 transitionValues.targetedTransitions.add(childTransition); 466 } 467 } 468 } 469 } 470 471 @Override capturePropagationValues(TransitionValues transitionValues)472 void capturePropagationValues(TransitionValues transitionValues) { 473 super.capturePropagationValues(transitionValues); 474 int numTransitions = mTransitions.size(); 475 for (int i = 0; i < numTransitions; ++i) { 476 mTransitions.get(i).capturePropagationValues(transitionValues); 477 } 478 } 479 480 /** @hide */ 481 @Override pause(View sceneRoot)482 public void pause(View sceneRoot) { 483 super.pause(sceneRoot); 484 int numTransitions = mTransitions.size(); 485 for (int i = 0; i < numTransitions; ++i) { 486 mTransitions.get(i).pause(sceneRoot); 487 } 488 } 489 490 /** @hide */ 491 @Override resume(View sceneRoot)492 public void resume(View sceneRoot) { 493 super.resume(sceneRoot); 494 int numTransitions = mTransitions.size(); 495 for (int i = 0; i < numTransitions; ++i) { 496 mTransitions.get(i).resume(sceneRoot); 497 } 498 } 499 500 /** @hide */ 501 @Override cancel()502 protected void cancel() { 503 super.cancel(); 504 int numTransitions = mTransitions.size(); 505 for (int i = 0; i < numTransitions; ++i) { 506 mTransitions.get(i).cancel(); 507 } 508 } 509 510 @Override setSceneRoot(ViewGroup sceneRoot)511 TransitionSet setSceneRoot(ViewGroup sceneRoot) { 512 super.setSceneRoot(sceneRoot); 513 int numTransitions = mTransitions.size(); 514 for (int i = 0; i < numTransitions; ++i) { 515 mTransitions.get(i).setSceneRoot(sceneRoot); 516 } 517 return (TransitionSet) this; 518 } 519 520 @Override setCanRemoveViews(boolean canRemoveViews)521 void setCanRemoveViews(boolean canRemoveViews) { 522 super.setCanRemoveViews(canRemoveViews); 523 int numTransitions = mTransitions.size(); 524 for (int i = 0; i < numTransitions; ++i) { 525 mTransitions.get(i).setCanRemoveViews(canRemoveViews); 526 } 527 } 528 529 @Override setPropagation(TransitionPropagation propagation)530 public void setPropagation(TransitionPropagation propagation) { 531 super.setPropagation(propagation); 532 int numTransitions = mTransitions.size(); 533 for (int i = 0; i < numTransitions; ++i) { 534 mTransitions.get(i).setPropagation(propagation); 535 } 536 } 537 538 @Override setEpicenterCallback(EpicenterCallback epicenterCallback)539 public void setEpicenterCallback(EpicenterCallback epicenterCallback) { 540 super.setEpicenterCallback(epicenterCallback); 541 int numTransitions = mTransitions.size(); 542 for (int i = 0; i < numTransitions; ++i) { 543 mTransitions.get(i).setEpicenterCallback(epicenterCallback); 544 } 545 } 546 547 @Override toString(String indent)548 String toString(String indent) { 549 String result = super.toString(indent); 550 for (int i = 0; i < mTransitions.size(); ++i) { 551 result += "\n" + mTransitions.get(i).toString(indent + " "); 552 } 553 return result; 554 } 555 556 @Override clone()557 public TransitionSet clone() { 558 TransitionSet clone = (TransitionSet) super.clone(); 559 clone.mTransitions = new ArrayList<Transition>(); 560 int numTransitions = mTransitions.size(); 561 for (int i = 0; i < numTransitions; ++i) { 562 clone.addTransition((Transition) mTransitions.get(i).clone()); 563 } 564 return clone; 565 } 566 } 567