1/*
2 * Copyright (C) 2016 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.support.transition;
18
19import android.animation.TimeInterpolator;
20import android.support.annotation.RestrictTo;
21import android.util.AndroidRuntimeException;
22import android.view.View;
23import android.view.ViewGroup;
24
25import java.util.ArrayList;
26
27import static android.support.annotation.RestrictTo.Scope.GROUP_ID;
28
29class TransitionSetPort extends TransitionPort {
30
31    /**
32     * A flag used to indicate that the child transitions of this set
33     * should all start at the same time.
34     */
35    public static final int ORDERING_TOGETHER = 0;
36
37    /**
38     * A flag used to indicate that the child transitions of this set should
39     * play in sequence; when one child transition ends, the next child
40     * transition begins. Note that a transition does not end until all
41     * instances of it (which are playing on all applicable targets of the
42     * transition) end.
43     */
44    public static final int ORDERING_SEQUENTIAL = 1;
45
46    ArrayList<TransitionPort> mTransitions = new ArrayList<TransitionPort>();
47
48    int mCurrentListeners;
49
50    boolean mStarted = false;
51
52    private boolean mPlayTogether = true;
53
54    public TransitionSetPort() {
55    }
56
57    public int getOrdering() {
58        return mPlayTogether ? ORDERING_TOGETHER : ORDERING_SEQUENTIAL;
59    }
60
61    public TransitionSetPort setOrdering(int ordering) {
62        switch (ordering) {
63            case ORDERING_SEQUENTIAL:
64                mPlayTogether = false;
65                break;
66            case ORDERING_TOGETHER:
67                mPlayTogether = true;
68                break;
69            default:
70                throw new AndroidRuntimeException("Invalid parameter for TransitionSet " +
71                        "ordering: " + ordering);
72        }
73        return this;
74    }
75
76    public TransitionSetPort addTransition(TransitionPort transition) {
77        if (transition != null) {
78            mTransitions.add(transition);
79            transition.mParent = this;
80            if (mDuration >= 0) {
81                transition.setDuration(mDuration);
82            }
83        }
84        return this;
85    }
86
87    /**
88     * Setting a non-negative duration on a TransitionSet causes all of the child
89     * transitions (current and future) to inherit this duration.
90     *
91     * @param duration The length of the animation, in milliseconds.
92     * @return This transitionSet object.
93     */
94    @Override
95    public TransitionSetPort setDuration(long duration) {
96        super.setDuration(duration);
97        if (mDuration >= 0) {
98            int numTransitions = mTransitions.size();
99            for (int i = 0; i < numTransitions; ++i) {
100                mTransitions.get(i).setDuration(duration);
101            }
102        }
103        return this;
104    }
105
106    @Override
107    public TransitionSetPort setStartDelay(long startDelay) {
108        return (TransitionSetPort) super.setStartDelay(startDelay);
109    }
110
111    @Override
112    public TransitionSetPort setInterpolator(TimeInterpolator interpolator) {
113        return (TransitionSetPort) super.setInterpolator(interpolator);
114    }
115
116    @Override
117    public TransitionSetPort addTarget(View target) {
118        return (TransitionSetPort) super.addTarget(target);
119    }
120
121    @Override
122    public TransitionSetPort addTarget(int targetId) {
123        return (TransitionSetPort) super.addTarget(targetId);
124    }
125
126    @Override
127    public TransitionSetPort addListener(TransitionListener listener) {
128        return (TransitionSetPort) super.addListener(listener);
129    }
130
131    @Override
132    public TransitionSetPort removeTarget(int targetId) {
133        return (TransitionSetPort) super.removeTarget(targetId);
134    }
135
136    @Override
137    public TransitionSetPort removeTarget(View target) {
138        return (TransitionSetPort) super.removeTarget(target);
139    }
140
141    @Override
142    public TransitionSetPort removeListener(TransitionListener listener) {
143        return (TransitionSetPort) super.removeListener(listener);
144    }
145
146    public TransitionSetPort removeTransition(TransitionPort transition) {
147        mTransitions.remove(transition);
148        transition.mParent = null;
149        return this;
150    }
151
152    /**
153     * Sets up listeners for each of the child transitions. This is used to
154     * determine when this transition set is finished (all child transitions
155     * must finish first).
156     */
157    private void setupStartEndListeners() {
158        TransitionSetListener listener = new TransitionSetListener(this);
159        for (TransitionPort childTransition : mTransitions) {
160            childTransition.addListener(listener);
161        }
162        mCurrentListeners = mTransitions.size();
163    }
164
165    /**
166     * @hide
167     */
168    @RestrictTo(GROUP_ID)
169    @Override
170    protected void createAnimators(ViewGroup sceneRoot, TransitionValuesMaps startValues,
171            TransitionValuesMaps endValues) {
172        for (TransitionPort childTransition : mTransitions) {
173            childTransition.createAnimators(sceneRoot, startValues, endValues);
174        }
175    }
176
177    /**
178     * @hide
179     */
180    @RestrictTo(GROUP_ID)
181    @Override
182    protected void runAnimators() {
183        if (mTransitions.isEmpty()) {
184            start();
185            end();
186            return;
187        }
188        setupStartEndListeners();
189        if (!mPlayTogether) {
190            // Setup sequence with listeners
191            // TODO: Need to add listeners in such a way that we can remove them later if canceled
192            for (int i = 1; i < mTransitions.size(); ++i) {
193                TransitionPort previousTransition = mTransitions.get(i - 1);
194                final TransitionPort nextTransition = mTransitions.get(i);
195                previousTransition.addListener(new TransitionListenerAdapter() {
196                    @Override
197                    public void onTransitionEnd(TransitionPort transition) {
198                        nextTransition.runAnimators();
199                        transition.removeListener(this);
200                    }
201                });
202            }
203            TransitionPort firstTransition = mTransitions.get(0);
204            if (firstTransition != null) {
205                firstTransition.runAnimators();
206            }
207        } else {
208            for (TransitionPort childTransition : mTransitions) {
209                childTransition.runAnimators();
210            }
211        }
212    }
213
214    @Override
215    public void captureStartValues(TransitionValues transitionValues) {
216        int targetId = transitionValues.view.getId();
217        if (isValidTarget(transitionValues.view, targetId)) {
218            for (TransitionPort childTransition : mTransitions) {
219                if (childTransition.isValidTarget(transitionValues.view, targetId)) {
220                    childTransition.captureStartValues(transitionValues);
221                }
222            }
223        }
224    }
225
226    @Override
227    public void captureEndValues(TransitionValues transitionValues) {
228        int targetId = transitionValues.view.getId();
229        if (isValidTarget(transitionValues.view, targetId)) {
230            for (TransitionPort childTransition : mTransitions) {
231                if (childTransition.isValidTarget(transitionValues.view, targetId)) {
232                    childTransition.captureEndValues(transitionValues);
233                }
234            }
235        }
236    }
237
238    /** @hide */
239    @RestrictTo(GROUP_ID)
240    @Override
241    public void pause(View sceneRoot) {
242        super.pause(sceneRoot);
243        int numTransitions = mTransitions.size();
244        for (int i = 0; i < numTransitions; ++i) {
245            mTransitions.get(i).pause(sceneRoot);
246        }
247    }
248
249    /** @hide */
250    @RestrictTo(GROUP_ID)
251    @Override
252    public void resume(View sceneRoot) {
253        super.resume(sceneRoot);
254        int numTransitions = mTransitions.size();
255        for (int i = 0; i < numTransitions; ++i) {
256            mTransitions.get(i).resume(sceneRoot);
257        }
258    }
259
260    /** @hide */
261    @RestrictTo(GROUP_ID)
262    @Override
263    protected void cancel() {
264        super.cancel();
265        int numTransitions = mTransitions.size();
266        for (int i = 0; i < numTransitions; ++i) {
267            mTransitions.get(i).cancel();
268        }
269    }
270
271    @Override
272    TransitionSetPort setSceneRoot(ViewGroup sceneRoot) {
273        super.setSceneRoot(sceneRoot);
274        int numTransitions = mTransitions.size();
275        for (int i = 0; i < numTransitions; ++i) {
276            mTransitions.get(i).setSceneRoot(sceneRoot);
277        }
278        return (TransitionSetPort) this;
279    }
280
281    @Override
282    void setCanRemoveViews(boolean canRemoveViews) {
283        super.setCanRemoveViews(canRemoveViews);
284        int numTransitions = mTransitions.size();
285        for (int i = 0; i < numTransitions; ++i) {
286            mTransitions.get(i).setCanRemoveViews(canRemoveViews);
287        }
288    }
289
290    @Override
291    String toString(String indent) {
292        String result = super.toString(indent);
293        for (int i = 0; i < mTransitions.size(); ++i) {
294            result += "\n" + mTransitions.get(i).toString(indent + "  ");
295        }
296        return result;
297    }
298
299    @Override
300    public TransitionSetPort clone() {
301        TransitionSetPort clone = (TransitionSetPort) super.clone();
302        clone.mTransitions = new ArrayList<TransitionPort>();
303        int numTransitions = mTransitions.size();
304        for (int i = 0; i < numTransitions; ++i) {
305            clone.addTransition((TransitionPort) mTransitions.get(i).clone());
306        }
307        return clone;
308    }
309
310    /**
311     * This listener is used to detect when all child transitions are done, at
312     * which point this transition set is also done.
313     */
314    static class TransitionSetListener extends TransitionListenerAdapter {
315
316        TransitionSetPort mTransitionSet;
317
318        TransitionSetListener(TransitionSetPort transitionSet) {
319            mTransitionSet = transitionSet;
320        }
321
322        @Override
323        public void onTransitionStart(TransitionPort transition) {
324            if (!mTransitionSet.mStarted) {
325                mTransitionSet.start();
326                mTransitionSet.mStarted = true;
327            }
328        }
329
330        @Override
331        public void onTransitionEnd(TransitionPort transition) {
332            --mTransitionSet.mCurrentListeners;
333            if (mTransitionSet.mCurrentListeners == 0) {
334                // All child trans
335                mTransitionSet.mStarted = false;
336                mTransitionSet.end();
337            }
338            transition.removeListener(this);
339        }
340    }
341
342}
343