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  *     &lt;transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
49  *             android:ordering="sequential"&gt;
50  *         &lt;fade/&gt;
51  *         &lt;changeBounds/&gt;
52  *     &lt;/transitionSet&gt;
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