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.util.AndroidRuntimeException;
21import android.view.View;
22import android.view.ViewGroup;
23
24import java.util.ArrayList;
25
26/**
27 * A TransitionSet is a parent of child transitions (including other
28 * TransitionSets). Using TransitionSets enables more complex
29 * choreography of transitions, where some sets play {@link #ORDERING_TOGETHER} and
30 * others play {@link #ORDERING_SEQUENTIAL}. For example, {@link AutoTransition}
31 * uses a TransitionSet to sequentially play a Fade(Fade.OUT), followed by
32 * a {@link ChangeBounds}, followed by a Fade(Fade.OUT) transition.
33 *
34 * <p>A TransitionSet can be described in a resource file by using the
35 * tag <code>transitionSet</code>, along with the standard
36 * attributes of {@link android.R.styleable#TransitionSet} and
37 * {@link android.R.styleable#Transition}. Child transitions of the
38 * TransitionSet object can be loaded by adding those child tags inside the
39 * enclosing <code>transitionSet</code> tag. For example, the following xml
40 * describes a TransitionSet that plays a Fade and then a ChangeBounds
41 * transition on the affected view targets:</p>
42 * <pre>
43 *     &lt;transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
44 *             android:ordering="sequential"&gt;
45 *         &lt;fade/&gt;
46 *         &lt;changeBounds/&gt;
47 *     &lt;/transitionSet&gt;
48 * </pre>
49 */
50public class TransitionSet extends Transition {
51
52    ArrayList<Transition> mTransitions = new ArrayList<Transition>();
53    private boolean mPlayTogether = true;
54    int mCurrentListeners;
55    boolean mStarted = false;
56
57    /**
58     * A flag used to indicate that the child transitions of this set
59     * should all start at the same time.
60     */
61    public static final int ORDERING_TOGETHER = 0;
62    /**
63     * A flag used to indicate that the child transitions of this set should
64     * play in sequence; when one child transition ends, the next child
65     * transition begins. Note that a transition does not end until all
66     * instances of it (which are playing on all applicable targets of the
67     * transition) end.
68     */
69    public static final int ORDERING_SEQUENTIAL = 1;
70
71    /**
72     * Constructs an empty transition set. Add child transitions to the
73     * set by calling {@link #addTransition(Transition)} )}. By default,
74     * child transitions will play {@link #ORDERING_TOGETHER together}.
75     */
76    public TransitionSet() {
77    }
78
79    /**
80     * Sets the play order of this set's child transitions.
81     *
82     * @param ordering {@link #ORDERING_TOGETHER} to play this set's child
83     * transitions together, {@link #ORDERING_SEQUENTIAL} to play the child
84     * transitions in sequence.
85     * @return This transitionSet object.
86     */
87    public TransitionSet setOrdering(int ordering) {
88        switch (ordering) {
89            case ORDERING_SEQUENTIAL:
90                mPlayTogether = false;
91                break;
92            case ORDERING_TOGETHER:
93                mPlayTogether = true;
94                break;
95            default:
96                throw new AndroidRuntimeException("Invalid parameter for TransitionSet " +
97                        "ordering: " + ordering);
98        }
99        return this;
100    }
101
102    /**
103     * Returns the ordering of this TransitionSet. By default, the value is
104     * {@link #ORDERING_TOGETHER}.
105     *
106     * @return {@link #ORDERING_TOGETHER} if child transitions will play at the same
107     * time, {@link #ORDERING_SEQUENTIAL} if they will play in sequence.
108     *
109     * @see #setOrdering(int)
110     */
111    public int getOrdering() {
112        return mPlayTogether ? ORDERING_TOGETHER : ORDERING_SEQUENTIAL;
113    }
114
115    /**
116     * Adds child transition to this set. The order in which this child transition
117     * is added relative to other child transitions that are added, in addition to
118     * the {@link #getOrdering() ordering} property, determines the
119     * order in which the transitions are started.
120     *
121     * <p>If this transitionSet has a {@link #getDuration() duration} set on it, the
122     * child transition will inherit that duration. Transitions are assumed to have
123     * a maximum of one transitionSet parent.</p>
124     *
125     * @param transition A non-null child transition to be added to this set.
126     * @return This transitionSet object.
127     */
128    public TransitionSet addTransition(Transition transition) {
129        if (transition != null) {
130            mTransitions.add(transition);
131            transition.mParent = this;
132            if (mDuration >= 0) {
133                transition.setDuration(mDuration);
134            }
135        }
136        return this;
137    }
138
139    /**
140     * Setting a non-negative duration on a TransitionSet causes all of the child
141     * transitions (current and future) to inherit this duration.
142     *
143     * @param duration The length of the animation, in milliseconds.
144     * @return This transitionSet object.
145     */
146    @Override
147    public TransitionSet setDuration(long duration) {
148        super.setDuration(duration);
149        if (mDuration >= 0) {
150            int numTransitions = mTransitions.size();
151            for (int i = 0; i < numTransitions; ++i) {
152                mTransitions.get(i).setDuration(duration);
153            }
154        }
155        return this;
156    }
157
158    @Override
159    public TransitionSet setStartDelay(long startDelay) {
160        return (TransitionSet) super.setStartDelay(startDelay);
161    }
162
163    @Override
164    public TransitionSet setInterpolator(TimeInterpolator interpolator) {
165        return (TransitionSet) super.setInterpolator(interpolator);
166    }
167
168    @Override
169    public TransitionSet addTarget(View target) {
170        return (TransitionSet) super.addTarget(target);
171    }
172
173    @Override
174    public TransitionSet addTarget(int targetId) {
175        return (TransitionSet) super.addTarget(targetId);
176    }
177
178    @Override
179    public TransitionSet addListener(TransitionListener listener) {
180        return (TransitionSet) super.addListener(listener);
181    }
182
183    @Override
184    public TransitionSet removeTarget(int targetId) {
185        return (TransitionSet) super.removeTarget(targetId);
186    }
187
188    @Override
189    public TransitionSet removeTarget(View target) {
190        return (TransitionSet) super.removeTarget(target);
191    }
192
193    @Override
194    public TransitionSet removeListener(TransitionListener listener) {
195        return (TransitionSet) super.removeListener(listener);
196    }
197
198    /**
199     * Removes the specified child transition from this set.
200     *
201     * @param transition The transition to be removed.
202     * @return This transitionSet object.
203     */
204    public TransitionSet removeTransition(Transition transition) {
205        mTransitions.remove(transition);
206        transition.mParent = null;
207        return this;
208    }
209
210    /**
211     * Sets up listeners for each of the child transitions. This is used to
212     * determine when this transition set is finished (all child transitions
213     * must finish first).
214     */
215    private void setupStartEndListeners() {
216        TransitionSetListener listener = new TransitionSetListener(this);
217        for (Transition childTransition : mTransitions) {
218            childTransition.addListener(listener);
219        }
220        mCurrentListeners = mTransitions.size();
221    }
222
223    /**
224     * This listener is used to detect when all child transitions are done, at
225     * which point this transition set is also done.
226     */
227    static class TransitionSetListener extends TransitionListenerAdapter {
228        TransitionSet mTransitionSet;
229        TransitionSetListener(TransitionSet transitionSet) {
230            mTransitionSet = transitionSet;
231        }
232        @Override
233        public void onTransitionStart(Transition transition) {
234            if (!mTransitionSet.mStarted) {
235                mTransitionSet.start();
236                mTransitionSet.mStarted = true;
237            }
238        }
239
240        @Override
241        public void onTransitionEnd(Transition transition) {
242            --mTransitionSet.mCurrentListeners;
243            if (mTransitionSet.mCurrentListeners == 0) {
244                // All child trans
245                mTransitionSet.mStarted = false;
246                mTransitionSet.end();
247            }
248            transition.removeListener(this);
249        }
250    }
251
252    /**
253     * @hide
254     */
255    @Override
256    protected void createAnimators(ViewGroup sceneRoot, TransitionValuesMaps startValues,
257            TransitionValuesMaps endValues) {
258        for (Transition childTransition : mTransitions) {
259            childTransition.createAnimators(sceneRoot, startValues, endValues);
260        }
261    }
262
263    /**
264     * @hide
265     */
266    @Override
267    protected void runAnimators() {
268        setupStartEndListeners();
269        if (!mPlayTogether) {
270            // Setup sequence with listeners
271            // TODO: Need to add listeners in such a way that we can remove them later if canceled
272            for (int i = 1; i < mTransitions.size(); ++i) {
273                Transition previousTransition = mTransitions.get(i - 1);
274                final Transition nextTransition = mTransitions.get(i);
275                previousTransition.addListener(new TransitionListenerAdapter() {
276                    @Override
277                    public void onTransitionEnd(Transition transition) {
278                        nextTransition.runAnimators();
279                        transition.removeListener(this);
280                    }
281                });
282            }
283            Transition firstTransition = mTransitions.get(0);
284            if (firstTransition != null) {
285                firstTransition.runAnimators();
286            }
287        } else {
288            for (Transition childTransition : mTransitions) {
289                childTransition.runAnimators();
290            }
291        }
292    }
293
294    @Override
295    public void captureStartValues(TransitionValues transitionValues) {
296        int targetId = transitionValues.view.getId();
297        if (isValidTarget(transitionValues.view, targetId)) {
298            for (Transition childTransition : mTransitions) {
299                if (childTransition.isValidTarget(transitionValues.view, targetId)) {
300                    childTransition.captureStartValues(transitionValues);
301                }
302            }
303        }
304    }
305
306    @Override
307    public void captureEndValues(TransitionValues transitionValues) {
308        int targetId = transitionValues.view.getId();
309        if (isValidTarget(transitionValues.view, targetId)) {
310            for (Transition childTransition : mTransitions) {
311                if (childTransition.isValidTarget(transitionValues.view, targetId)) {
312                    childTransition.captureEndValues(transitionValues);
313                }
314            }
315        }
316    }
317
318    /** @hide */
319    @Override
320    public void pause() {
321        super.pause();
322        int numTransitions = mTransitions.size();
323        for (int i = 0; i < numTransitions; ++i) {
324            mTransitions.get(i).pause();
325        }
326    }
327
328    /** @hide */
329    @Override
330    public void resume() {
331        super.resume();
332        int numTransitions = mTransitions.size();
333        for (int i = 0; i < numTransitions; ++i) {
334            mTransitions.get(i).resume();
335        }
336    }
337
338    /** @hide */
339    @Override
340    protected void cancel() {
341        super.cancel();
342        int numTransitions = mTransitions.size();
343        for (int i = 0; i < numTransitions; ++i) {
344            mTransitions.get(i).cancel();
345        }
346    }
347
348    @Override
349    TransitionSet setSceneRoot(ViewGroup sceneRoot) {
350        super.setSceneRoot(sceneRoot);
351        int numTransitions = mTransitions.size();
352        for (int i = 0; i < numTransitions; ++i) {
353            mTransitions.get(i).setSceneRoot(sceneRoot);
354        }
355        return (TransitionSet) this;
356    }
357
358    @Override
359    void setCanRemoveViews(boolean canRemoveViews) {
360        super.setCanRemoveViews(canRemoveViews);
361        int numTransitions = mTransitions.size();
362        for (int i = 0; i < numTransitions; ++i) {
363            mTransitions.get(i).setCanRemoveViews(canRemoveViews);
364        }
365    }
366
367    @Override
368    String toString(String indent) {
369        String result = super.toString(indent);
370        for (int i = 0; i < mTransitions.size(); ++i) {
371            result += "\n" + mTransitions.get(i).toString(indent + "  ");
372        }
373        return result;
374    }
375
376    @Override
377    public TransitionSet clone() {
378        TransitionSet clone = (TransitionSet) super.clone();
379        clone.mTransitions = new ArrayList<Transition>();
380        int numTransitions = mTransitions.size();
381        for (int i = 0; i < numTransitions; ++i) {
382            clone.addTransition((Transition) mTransitions.get(i).clone());
383        }
384        return clone;
385    }
386
387}
388