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