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