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.util.AndroidRuntimeException; 21 import android.view.View; 22 import android.view.ViewGroup; 23 24 import java.util.ArrayList; 25 26 /** 27 * A TransitionSet is a parent of child transitions (including other 28 * TransitionSets). Using TransitionSets enables more complex 29 * choreography of transitions, where some sets play {@link #ORDERING_TOGETHER} and 30 * others play {@link #ORDERING_SEQUENTIAL}. For example, {@link AutoTransition} 31 * uses a TransitionSet to sequentially play a Fade(Fade.OUT), followed by 32 * a {@link ChangeBounds}, followed by a Fade(Fade.OUT) transition. 33 * 34 * <p>A TransitionSet can be described in a resource file by using the 35 * tag <code>transitionSet</code>, along with the standard 36 * attributes of {@link android.R.styleable#TransitionSet} and 37 * {@link android.R.styleable#Transition}. Child transitions of the 38 * TransitionSet object can be loaded by adding those child tags inside the 39 * enclosing <code>transitionSet</code> tag. For example, the following xml 40 * describes a TransitionSet that plays a Fade and then a ChangeBounds 41 * transition on the affected view targets:</p> 42 * <pre> 43 * <transitionSet xmlns:android="http://schemas.android.com/apk/res/android" 44 * android:ordering="sequential"> 45 * <fade/> 46 * <changeBounds/> 47 * </transitionSet> 48 * </pre> 49 */ 50 public class TransitionSet extends Transition { 51 52 ArrayList<Transition> mTransitions = new ArrayList<Transition>(); 53 private boolean mPlayTogether = true; 54 int mCurrentListeners; 55 boolean mStarted = false; 56 57 /** 58 * A flag used to indicate that the child transitions of this set 59 * should all start at the same time. 60 */ 61 public static final int ORDERING_TOGETHER = 0; 62 /** 63 * A flag used to indicate that the child transitions of this set should 64 * play in sequence; when one child transition ends, the next child 65 * transition begins. Note that a transition does not end until all 66 * instances of it (which are playing on all applicable targets of the 67 * transition) end. 68 */ 69 public static final int ORDERING_SEQUENTIAL = 1; 70 71 /** 72 * Constructs an empty transition set. Add child transitions to the 73 * set by calling {@link #addTransition(Transition)} )}. By default, 74 * child transitions will play {@link #ORDERING_TOGETHER together}. 75 */ TransitionSet()76 public TransitionSet() { 77 } 78 79 /** 80 * Sets the play order of this set's child transitions. 81 * 82 * @param ordering {@link #ORDERING_TOGETHER} to play this set's child 83 * transitions together, {@link #ORDERING_SEQUENTIAL} to play the child 84 * transitions in sequence. 85 * @return This transitionSet object. 86 */ setOrdering(int ordering)87 public TransitionSet setOrdering(int ordering) { 88 switch (ordering) { 89 case ORDERING_SEQUENTIAL: 90 mPlayTogether = false; 91 break; 92 case ORDERING_TOGETHER: 93 mPlayTogether = true; 94 break; 95 default: 96 throw new AndroidRuntimeException("Invalid parameter for TransitionSet " + 97 "ordering: " + ordering); 98 } 99 return this; 100 } 101 102 /** 103 * Returns the ordering of this TransitionSet. By default, the value is 104 * {@link #ORDERING_TOGETHER}. 105 * 106 * @return {@link #ORDERING_TOGETHER} if child transitions will play at the same 107 * time, {@link #ORDERING_SEQUENTIAL} if they will play in sequence. 108 * 109 * @see #setOrdering(int) 110 */ getOrdering()111 public int getOrdering() { 112 return mPlayTogether ? ORDERING_TOGETHER : ORDERING_SEQUENTIAL; 113 } 114 115 /** 116 * Adds child transition to this set. The order in which this child transition 117 * is added relative to other child transitions that are added, in addition to 118 * the {@link #getOrdering() ordering} property, determines the 119 * order in which the transitions are started. 120 * 121 * <p>If this transitionSet has a {@link #getDuration() duration} set on it, the 122 * child transition will inherit that duration. Transitions are assumed to have 123 * a maximum of one transitionSet parent.</p> 124 * 125 * @param transition A non-null child transition to be added to this set. 126 * @return This transitionSet object. 127 */ addTransition(Transition transition)128 public TransitionSet addTransition(Transition transition) { 129 if (transition != null) { 130 mTransitions.add(transition); 131 transition.mParent = this; 132 if (mDuration >= 0) { 133 transition.setDuration(mDuration); 134 } 135 } 136 return this; 137 } 138 139 /** 140 * Setting a non-negative duration on a TransitionSet causes all of the child 141 * transitions (current and future) to inherit this duration. 142 * 143 * @param duration The length of the animation, in milliseconds. 144 * @return This transitionSet object. 145 */ 146 @Override setDuration(long duration)147 public TransitionSet setDuration(long duration) { 148 super.setDuration(duration); 149 if (mDuration >= 0) { 150 int numTransitions = mTransitions.size(); 151 for (int i = 0; i < numTransitions; ++i) { 152 mTransitions.get(i).setDuration(duration); 153 } 154 } 155 return this; 156 } 157 158 @Override setStartDelay(long startDelay)159 public TransitionSet setStartDelay(long startDelay) { 160 return (TransitionSet) super.setStartDelay(startDelay); 161 } 162 163 @Override setInterpolator(TimeInterpolator interpolator)164 public TransitionSet setInterpolator(TimeInterpolator interpolator) { 165 return (TransitionSet) super.setInterpolator(interpolator); 166 } 167 168 @Override addTarget(View target)169 public TransitionSet addTarget(View target) { 170 return (TransitionSet) super.addTarget(target); 171 } 172 173 @Override addTarget(int targetId)174 public TransitionSet addTarget(int targetId) { 175 return (TransitionSet) super.addTarget(targetId); 176 } 177 178 @Override addListener(TransitionListener listener)179 public TransitionSet addListener(TransitionListener listener) { 180 return (TransitionSet) super.addListener(listener); 181 } 182 183 @Override removeTarget(int targetId)184 public TransitionSet removeTarget(int targetId) { 185 return (TransitionSet) super.removeTarget(targetId); 186 } 187 188 @Override removeTarget(View target)189 public TransitionSet removeTarget(View target) { 190 return (TransitionSet) super.removeTarget(target); 191 } 192 193 @Override removeListener(TransitionListener listener)194 public TransitionSet removeListener(TransitionListener listener) { 195 return (TransitionSet) super.removeListener(listener); 196 } 197 198 /** 199 * Removes the specified child transition from this set. 200 * 201 * @param transition The transition to be removed. 202 * @return This transitionSet object. 203 */ removeTransition(Transition transition)204 public TransitionSet removeTransition(Transition transition) { 205 mTransitions.remove(transition); 206 transition.mParent = null; 207 return this; 208 } 209 210 /** 211 * Sets up listeners for each of the child transitions. This is used to 212 * determine when this transition set is finished (all child transitions 213 * must finish first). 214 */ setupStartEndListeners()215 private void setupStartEndListeners() { 216 TransitionSetListener listener = new TransitionSetListener(this); 217 for (Transition childTransition : mTransitions) { 218 childTransition.addListener(listener); 219 } 220 mCurrentListeners = mTransitions.size(); 221 } 222 223 /** 224 * This listener is used to detect when all child transitions are done, at 225 * which point this transition set is also done. 226 */ 227 static class TransitionSetListener extends TransitionListenerAdapter { 228 TransitionSet mTransitionSet; TransitionSetListener(TransitionSet transitionSet)229 TransitionSetListener(TransitionSet transitionSet) { 230 mTransitionSet = transitionSet; 231 } 232 @Override onTransitionStart(Transition transition)233 public void onTransitionStart(Transition transition) { 234 if (!mTransitionSet.mStarted) { 235 mTransitionSet.start(); 236 mTransitionSet.mStarted = true; 237 } 238 } 239 240 @Override onTransitionEnd(Transition transition)241 public void onTransitionEnd(Transition transition) { 242 --mTransitionSet.mCurrentListeners; 243 if (mTransitionSet.mCurrentListeners == 0) { 244 // All child trans 245 mTransitionSet.mStarted = false; 246 mTransitionSet.end(); 247 } 248 transition.removeListener(this); 249 } 250 } 251 252 /** 253 * @hide 254 */ 255 @Override createAnimators(ViewGroup sceneRoot, TransitionValuesMaps startValues, TransitionValuesMaps endValues)256 protected void createAnimators(ViewGroup sceneRoot, TransitionValuesMaps startValues, 257 TransitionValuesMaps endValues) { 258 for (Transition childTransition : mTransitions) { 259 childTransition.createAnimators(sceneRoot, startValues, endValues); 260 } 261 } 262 263 /** 264 * @hide 265 */ 266 @Override runAnimators()267 protected void runAnimators() { 268 setupStartEndListeners(); 269 if (!mPlayTogether) { 270 // Setup sequence with listeners 271 // TODO: Need to add listeners in such a way that we can remove them later if canceled 272 for (int i = 1; i < mTransitions.size(); ++i) { 273 Transition previousTransition = mTransitions.get(i - 1); 274 final Transition nextTransition = mTransitions.get(i); 275 previousTransition.addListener(new TransitionListenerAdapter() { 276 @Override 277 public void onTransitionEnd(Transition transition) { 278 nextTransition.runAnimators(); 279 transition.removeListener(this); 280 } 281 }); 282 } 283 Transition firstTransition = mTransitions.get(0); 284 if (firstTransition != null) { 285 firstTransition.runAnimators(); 286 } 287 } else { 288 for (Transition childTransition : mTransitions) { 289 childTransition.runAnimators(); 290 } 291 } 292 } 293 294 @Override captureStartValues(TransitionValues transitionValues)295 public void captureStartValues(TransitionValues transitionValues) { 296 int targetId = transitionValues.view.getId(); 297 if (isValidTarget(transitionValues.view, targetId)) { 298 for (Transition childTransition : mTransitions) { 299 if (childTransition.isValidTarget(transitionValues.view, targetId)) { 300 childTransition.captureStartValues(transitionValues); 301 } 302 } 303 } 304 } 305 306 @Override captureEndValues(TransitionValues transitionValues)307 public void captureEndValues(TransitionValues transitionValues) { 308 int targetId = transitionValues.view.getId(); 309 if (isValidTarget(transitionValues.view, targetId)) { 310 for (Transition childTransition : mTransitions) { 311 if (childTransition.isValidTarget(transitionValues.view, targetId)) { 312 childTransition.captureEndValues(transitionValues); 313 } 314 } 315 } 316 } 317 318 /** @hide */ 319 @Override pause()320 public void pause() { 321 super.pause(); 322 int numTransitions = mTransitions.size(); 323 for (int i = 0; i < numTransitions; ++i) { 324 mTransitions.get(i).pause(); 325 } 326 } 327 328 /** @hide */ 329 @Override resume()330 public void resume() { 331 super.resume(); 332 int numTransitions = mTransitions.size(); 333 for (int i = 0; i < numTransitions; ++i) { 334 mTransitions.get(i).resume(); 335 } 336 } 337 338 /** @hide */ 339 @Override cancel()340 protected void cancel() { 341 super.cancel(); 342 int numTransitions = mTransitions.size(); 343 for (int i = 0; i < numTransitions; ++i) { 344 mTransitions.get(i).cancel(); 345 } 346 } 347 348 @Override setSceneRoot(ViewGroup sceneRoot)349 TransitionSet setSceneRoot(ViewGroup sceneRoot) { 350 super.setSceneRoot(sceneRoot); 351 int numTransitions = mTransitions.size(); 352 for (int i = 0; i < numTransitions; ++i) { 353 mTransitions.get(i).setSceneRoot(sceneRoot); 354 } 355 return (TransitionSet) this; 356 } 357 358 @Override setCanRemoveViews(boolean canRemoveViews)359 void setCanRemoveViews(boolean canRemoveViews) { 360 super.setCanRemoveViews(canRemoveViews); 361 int numTransitions = mTransitions.size(); 362 for (int i = 0; i < numTransitions; ++i) { 363 mTransitions.get(i).setCanRemoveViews(canRemoveViews); 364 } 365 } 366 367 @Override toString(String indent)368 String toString(String indent) { 369 String result = super.toString(indent); 370 for (int i = 0; i < mTransitions.size(); ++i) { 371 result += "\n" + mTransitions.get(i).toString(indent + " "); 372 } 373 return result; 374 } 375 376 @Override clone()377 public TransitionSet clone() { 378 TransitionSet clone = (TransitionSet) super.clone(); 379 clone.mTransitions = new ArrayList<Transition>(); 380 int numTransitions = mTransitions.size(); 381 for (int i = 0; i < numTransitions; ++i) { 382 clone.addTransition((Transition) mTransitions.get(i).clone()); 383 } 384 return clone; 385 } 386 387 } 388