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:transitionOrdering="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     /**
322      * Removes the specified child transition from this set.
323      *
324      * @param transition The transition to be removed.
325      * @return This transitionSet object.
326      */
removeTransition(Transition transition)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      */
setupStartEndListeners()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;
TransitionSetListener(TransitionSet transitionSet)352         TransitionSetListener(TransitionSet transitionSet) {
353             mTransitionSet = transitionSet;
354         }
355         @Override
onTransitionStart(Transition transition)356         public void onTransitionStart(Transition transition) {
357             if (!mTransitionSet.mStarted) {
358                 mTransitionSet.start();
359                 mTransitionSet.mStarted = true;
360             }
361         }
362 
363         @Override
onTransitionEnd(Transition transition)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
createAnimators(ViewGroup sceneRoot, TransitionValuesMaps startValues, TransitionValuesMaps endValues, ArrayList<TransitionValues> startValuesList, ArrayList<TransitionValues> endValuesList)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
runAnimators()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
captureStartValues(TransitionValues transitionValues)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
captureEndValues(TransitionValues transitionValues)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
capturePropagationValues(TransitionValues transitionValues)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
pause(View sceneRoot)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
resume(View sceneRoot)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
cancel()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     @Override
setSceneRoot(ViewGroup sceneRoot)502     TransitionSet setSceneRoot(ViewGroup sceneRoot) {
503         super.setSceneRoot(sceneRoot);
504         int numTransitions = mTransitions.size();
505         for (int i = 0; i < numTransitions; ++i) {
506             mTransitions.get(i).setSceneRoot(sceneRoot);
507         }
508         return (TransitionSet) this;
509     }
510 
511     @Override
setCanRemoveViews(boolean canRemoveViews)512     void setCanRemoveViews(boolean canRemoveViews) {
513         super.setCanRemoveViews(canRemoveViews);
514         int numTransitions = mTransitions.size();
515         for (int i = 0; i < numTransitions; ++i) {
516             mTransitions.get(i).setCanRemoveViews(canRemoveViews);
517         }
518     }
519 
520     @Override
setPropagation(TransitionPropagation propagation)521     public void setPropagation(TransitionPropagation propagation) {
522         super.setPropagation(propagation);
523         int numTransitions = mTransitions.size();
524         for (int i = 0; i < numTransitions; ++i) {
525             mTransitions.get(i).setPropagation(propagation);
526         }
527     }
528 
529     @Override
setEpicenterCallback(EpicenterCallback epicenterCallback)530     public void setEpicenterCallback(EpicenterCallback epicenterCallback) {
531         super.setEpicenterCallback(epicenterCallback);
532         int numTransitions = mTransitions.size();
533         for (int i = 0; i < numTransitions; ++i) {
534             mTransitions.get(i).setEpicenterCallback(epicenterCallback);
535         }
536     }
537 
538     @Override
toString(String indent)539     String toString(String indent) {
540         String result = super.toString(indent);
541         for (int i = 0; i < mTransitions.size(); ++i) {
542             result += "\n" + mTransitions.get(i).toString(indent + "  ");
543         }
544         return result;
545     }
546 
547     @Override
clone()548     public TransitionSet clone() {
549         TransitionSet clone = (TransitionSet) super.clone();
550         clone.mTransitions = new ArrayList<Transition>();
551         int numTransitions = mTransitions.size();
552         for (int i = 0; i < numTransitions; ++i) {
553             clone.addTransition((Transition) mTransitions.get(i).clone());
554         }
555         return clone;
556     }
557 }
558