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 android.animation.TimeInterpolator;
20import android.content.Context;
21import android.content.res.TypedArray;
22import android.util.AndroidRuntimeException;
23import android.util.AttributeSet;
24import android.view.View;
25import android.view.ViewGroup;
26
27import com.android.internal.R;
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:transitionOrdering="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    /**
322     * Removes the specified child transition from this set.
323     *
324     * @param transition The transition to be removed.
325     * @return This transitionSet object.
326     */
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     */
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;
352        TransitionSetListener(TransitionSet transitionSet) {
353            mTransitionSet = transitionSet;
354        }
355        @Override
356        public void onTransitionStart(Transition transition) {
357            if (!mTransitionSet.mStarted) {
358                mTransitionSet.start();
359                mTransitionSet.mStarted = true;
360            }
361        }
362
363        @Override
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
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
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
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
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
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
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
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
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    /** @hide */
502    @Override
503    void forceToEnd(ViewGroup sceneRoot) {
504        super.forceToEnd(sceneRoot);
505        int numTransitions = mTransitions.size();
506        for (int i = 0; i < numTransitions; ++i) {
507            mTransitions.get(i).forceToEnd(sceneRoot);
508        }
509    }
510
511    @Override
512    TransitionSet setSceneRoot(ViewGroup sceneRoot) {
513        super.setSceneRoot(sceneRoot);
514        int numTransitions = mTransitions.size();
515        for (int i = 0; i < numTransitions; ++i) {
516            mTransitions.get(i).setSceneRoot(sceneRoot);
517        }
518        return (TransitionSet) this;
519    }
520
521    @Override
522    void setCanRemoveViews(boolean canRemoveViews) {
523        super.setCanRemoveViews(canRemoveViews);
524        int numTransitions = mTransitions.size();
525        for (int i = 0; i < numTransitions; ++i) {
526            mTransitions.get(i).setCanRemoveViews(canRemoveViews);
527        }
528    }
529
530    @Override
531    public void setPropagation(TransitionPropagation propagation) {
532        super.setPropagation(propagation);
533        int numTransitions = mTransitions.size();
534        for (int i = 0; i < numTransitions; ++i) {
535            mTransitions.get(i).setPropagation(propagation);
536        }
537    }
538
539    @Override
540    public void setEpicenterCallback(EpicenterCallback epicenterCallback) {
541        super.setEpicenterCallback(epicenterCallback);
542        int numTransitions = mTransitions.size();
543        for (int i = 0; i < numTransitions; ++i) {
544            mTransitions.get(i).setEpicenterCallback(epicenterCallback);
545        }
546    }
547
548    @Override
549    String toString(String indent) {
550        String result = super.toString(indent);
551        for (int i = 0; i < mTransitions.size(); ++i) {
552            result += "\n" + mTransitions.get(i).toString(indent + "  ");
553        }
554        return result;
555    }
556
557    @Override
558    public TransitionSet clone() {
559        TransitionSet clone = (TransitionSet) super.clone();
560        clone.mTransitions = new ArrayList<Transition>();
561        int numTransitions = mTransitions.size();
562        for (int i = 0; i < numTransitions; ++i) {
563            clone.addTransition((Transition) mTransitions.get(i).clone());
564        }
565        return clone;
566    }
567}
568