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
17package android.transition;
18
19import com.android.internal.R;
20
21import android.animation.TimeInterpolator;
22import android.content.Context;
23import android.content.res.TypedArray;
24import android.util.AndroidRuntimeException;
25import android.util.AttributeSet;
26import android.view.View;
27import android.view.ViewGroup;
28
29import 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 */
55public 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     */
81    public TransitionSet() {
82    }
83
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     */
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     */
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     */
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     */
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     */
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
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
198    public TransitionSet setStartDelay(long startDelay) {
199        return (TransitionSet) super.setStartDelay(startDelay);
200    }
201
202    @Override
203    public TransitionSet setInterpolator(TimeInterpolator interpolator) {
204        return (TransitionSet) super.setInterpolator(interpolator);
205    }
206
207    @Override
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
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
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
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
240    public TransitionSet addListener(TransitionListener listener) {
241        return (TransitionSet) super.addListener(listener);
242    }
243
244    @Override
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
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
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
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
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
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
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
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
309    public TransitionSet removeListener(TransitionListener listener) {
310        return (TransitionSet) super.removeListener(listener);
311    }
312
313    @Override
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
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     */
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     */
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;
361        TransitionSetListener(TransitionSet transitionSet) {
362            mTransitionSet = transitionSet;
363        }
364        @Override
365        public void onTransitionStart(Transition transition) {
366            if (!mTransitionSet.mStarted) {
367                mTransitionSet.start();
368                mTransitionSet.mStarted = true;
369            }
370        }
371
372        @Override
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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