FragmentTransitionCompat21.java revision 5a8f14de0bb4304f9a3aebdcb35ee0dd607f27f7
1/*
2 * Copyright (C) 2014 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.v4.app;
18
19import android.content.Context;
20import android.content.res.TypedArray;
21import android.graphics.Rect;
22import android.transition.Transition;
23import android.transition.TransitionInflater;
24import android.transition.TransitionManager;
25import android.transition.TransitionSet;
26import android.util.ArrayMap;
27import android.view.View;
28import android.view.ViewGroup;
29import android.view.ViewTreeObserver;
30
31import java.util.ArrayList;
32import java.util.Map;
33
34class FragmentTransitionCompat21 {
35    public static String getTransitionName(View view) {
36        return view.getTransitionName();
37    }
38
39    public static Object cloneTransition(Object transition) {
40        if (transition != null) {
41            transition = ((Transition)transition).clone();
42        }
43        return transition;
44    }
45
46    public static Object captureExitingViews(Object exitTransition, View root,
47            ArrayList<View> viewList, Map<String, View> namedViews) {
48        if (exitTransition != null) {
49            captureTransitioningViews(viewList, root);
50            if (namedViews != null) {
51                viewList.removeAll(namedViews.values());
52            }
53            if (viewList.isEmpty()) {
54                exitTransition = null;
55            } else {
56                addTargets((Transition) exitTransition, viewList);
57            }
58        }
59        return exitTransition;
60    }
61
62    public static void excludeTarget(Object transitionObject, View view, boolean exclude) {
63        Transition transition = (Transition) transitionObject;
64        transition.excludeTarget(view, exclude);
65    }
66
67    public static void beginDelayedTransition(ViewGroup sceneRoot, Object transitionObject) {
68        Transition transition = (Transition) transitionObject;
69        TransitionManager.beginDelayedTransition(sceneRoot, transition);
70    }
71
72    public static void setEpicenter(Object transitionObject, View view) {
73        Transition transition = (Transition) transitionObject;
74        final Rect epicenter = getBoundsOnScreen(view);
75
76        transition.setEpicenterCallback(new Transition.EpicenterCallback() {
77            @Override
78            public Rect onGetEpicenter(Transition transition) {
79                return epicenter;
80            }
81        });
82    }
83
84    /**
85     * Prepares the enter transition by adding a non-existent view to the transition's target list
86     * and setting it epicenter callback. By adding a non-existent view to the target list,
87     * we can prevent any view from being targeted at the beginning of the transition.
88     * We will add to the views before the end state of the transition is captured so that the
89     * views will appear. At the start of the transition, we clear the list of targets so that
90     * we can restore the state of the transition and use it again.
91     *
92     * <p>The shared element transition maps its shared elements immediately prior to
93     *  capturing the final state of the Transition.</p>
94     */
95    public static void addTransitionTargets(Object enterTransitionObject,
96            Object sharedElementTransitionObject, final View container,
97            final ViewRetriever inFragment, final View nonExistentView,
98            EpicenterView epicenterView, final Map<String, String> nameOverrides,
99            final ArrayList<View> enteringViews, final Map<String, View> renamedViews) {
100        if (enterTransitionObject != null || sharedElementTransitionObject != null) {
101            final Transition enterTransition = (Transition) enterTransitionObject;
102            if (enterTransition != null) {
103                enterTransition.addTarget(nonExistentView);
104            }
105            if (inFragment != null) {
106                container.getViewTreeObserver().addOnPreDrawListener(
107                        new ViewTreeObserver.OnPreDrawListener() {
108                            public boolean onPreDraw() {
109                                container.getViewTreeObserver().removeOnPreDrawListener(this);
110                                View fragmentView = inFragment.getView();
111                                if (fragmentView != null) {
112                                    if (!nameOverrides.isEmpty()) {
113                                        findNamedViews(renamedViews, fragmentView);
114                                        renamedViews.keySet().retainAll(nameOverrides.values());
115                                        for (Map.Entry<String, String> entry : nameOverrides.entrySet()) {
116                                            String to = entry.getValue();
117                                            View view = renamedViews.get(to);
118                                            if (view != null) {
119                                                String from = entry.getKey();
120                                                view.setTransitionName(from);
121                                            }
122                                        }
123                                    }
124                                    if (enterTransition != null) {
125                                        captureTransitioningViews(enteringViews, fragmentView);
126                                        enteringViews.removeAll(renamedViews.values());
127                                        addTargets(enterTransition, enteringViews);
128                                    }
129                                }
130                                return true;
131                            }
132                        });
133            }
134            setSharedElementEpicenter(enterTransition, epicenterView);
135        }
136    }
137
138    public static Object mergeTransitions(Object enterTransitionObject,
139            Object exitTransitionObject, Object sharedElementTransitionObject,
140            boolean allowOverlap) {
141        boolean overlap = true;
142        Transition enterTransition = (Transition) enterTransitionObject;
143        Transition exitTransition = (Transition) exitTransitionObject;
144        Transition sharedElementTransition = (Transition) sharedElementTransitionObject;
145
146        if (enterTransition != null && exitTransition != null) {
147            overlap = allowOverlap;
148        }
149
150        // Wrap the transitions. Explicit targets like in enter and exit will cause the
151        // views to be targeted regardless of excluded views. If that happens, then the
152        // excluded fragments views (hidden fragments) will still be in the transition.
153
154        Transition transition;
155        if (overlap) {
156            // Regular transition -- do it all together
157            transition = mergeTransitions(enterTransition, exitTransition,
158                    sharedElementTransition);
159            if (!(transition instanceof TransitionSet)) {
160                transition = new TransitionSet().addTransition(transition);
161            }
162        } else {
163            // First do exit, then enter, but allow shared element transition to happen
164            // during both.
165            Transition staggered = null;
166            if (exitTransition != null && enterTransition != null) {
167                staggered = new TransitionSet()
168                        .addTransition(exitTransition)
169                        .addTransition(enterTransition)
170                        .setOrdering(TransitionSet.ORDERING_SEQUENTIAL);
171            } else if (exitTransition != null) {
172                staggered = exitTransition;
173            } else if (enterTransition != null) {
174                staggered = enterTransition;
175            }
176            if (sharedElementTransition != null) {
177                TransitionSet together = new TransitionSet();
178                if (staggered != null) {
179                    together.addTransition(staggered);
180                }
181                together.addTransition(sharedElementTransition);
182                transition = together;
183            } else {
184                transition = staggered;
185            }
186        }
187        return transition;
188    }
189
190
191
192    private static void setSharedElementEpicenter(Transition transition,
193            final EpicenterView epicenterView) {
194        if (transition != null) {
195            transition.setEpicenterCallback(new Transition.EpicenterCallback() {
196                private Rect mEpicenter;
197
198                @Override
199                public Rect onGetEpicenter(Transition transition) {
200                    if (mEpicenter == null && epicenterView.epicenter != null) {
201                        mEpicenter = getBoundsOnScreen(epicenterView.epicenter);
202                    }
203                    return mEpicenter;
204                }
205            });
206        }
207    }
208
209    private static Rect getBoundsOnScreen(View view) {
210        Rect epicenter = new Rect();
211        int[] loc = new int[2];
212        view.getLocationOnScreen(loc);
213        // not as good as View.getBoundsOnScreen, but that's not public
214        epicenter.set(loc[0], loc[1], loc[0] + view.getWidth(), loc[1] + view.getHeight());
215        return epicenter;
216    }
217
218    private static void captureTransitioningViews(ArrayList<View> transitioningViews, View view) {
219        if (view.getVisibility() == View.VISIBLE) {
220            if (view instanceof ViewGroup) {
221                ViewGroup viewGroup = (ViewGroup) view;
222                if (viewGroup.isTransitionGroup()) {
223                    transitioningViews.add(viewGroup);
224                } else {
225                    int count = viewGroup.getChildCount();
226                    for (int i = 0; i < count; i++) {
227                        View child = viewGroup.getChildAt(i);
228                        captureTransitioningViews(transitioningViews, child);
229                    }
230                }
231            } else {
232                transitioningViews.add(view);
233            }
234        }
235    }
236
237    public static void findNamedViews(Map<String, View> namedViews, View view) {
238        if (view.getVisibility() == View.VISIBLE) {
239            String transitionName = view.getTransitionName();
240            if (transitionName != null) {
241                namedViews.put(transitionName, view);
242            }
243            if (view instanceof ViewGroup) {
244                ViewGroup viewGroup = (ViewGroup) view;
245                int count = viewGroup.getChildCount();
246                for (int i = 0; i < count; i++) {
247                    View child = viewGroup.getChildAt(i);
248                    findNamedViews(namedViews, child);
249                }
250            }
251        }
252    }
253
254    public static void cleanupTransitions(final View sceneRoot, final View nonExistentView,
255            Object enterTransitionObject, final ArrayList<View> enteringViews,
256            Object exitTransitionObject, final ArrayList<View> exitingViews,
257            Object overallTransitionObject, final ArrayList<View> hiddenViews,
258            final Map<String, View> renamedViews) {
259        final Transition enterTransition = (Transition) enterTransitionObject;
260        final Transition exitTransition = (Transition) exitTransitionObject;
261        final Transition overallTransition = (Transition) overallTransitionObject;
262        if (overallTransition != null) {
263            sceneRoot.getViewTreeObserver().addOnPreDrawListener(
264                    new ViewTreeObserver.OnPreDrawListener() {
265                public boolean onPreDraw() {
266                    sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
267                    if (enterTransition != null) {
268                        enterTransition.removeTarget(nonExistentView);
269                        removeTargets(enterTransition, enteringViews);
270                    }
271                    if (exitTransition != null) {
272                        removeTargets(exitTransition, exitingViews);
273                    }
274                    for (Map.Entry<String, View> entry : renamedViews.entrySet()) {
275                        View view = entry.getValue();
276                        String name = entry.getKey();
277                        view.setTransitionName(name);
278                    }
279                    int numViews = hiddenViews.size();
280                    for (int i = 0; i < numViews; i++) {
281                        overallTransition.excludeTarget(hiddenViews.get(i), false);
282                    }
283                    overallTransition.excludeTarget(nonExistentView, false);
284                    return true;
285                }
286            });
287        }
288    }
289
290    private static void removeTargets(Transition transition, ArrayList<View> views) {
291        int numViews = views.size();
292        for (int i = 0; i < numViews; i++) {
293            transition.removeTarget(views.get(i));
294        }
295    }
296
297    private static void addTargets(Transition transition, ArrayList<View> views) {
298        int numViews = views.size();
299        for (int i = 0; i < numViews; i++) {
300            transition.addTarget(views.get(i));
301        }
302    }
303
304    public interface ViewRetriever {
305        View getView();
306    }
307
308    public static class EpicenterView {
309        public View epicenter;
310    }
311
312    public static Transition mergeTransitions(Transition... transitions) {
313        int count = 0;
314        int nonNullIndex = -1;
315        for (int i = 0; i < transitions.length; i++) {
316            if (transitions[i] != null) {
317                count++;
318                nonNullIndex = i;
319            }
320        }
321
322        if (count == 0) {
323            return null;
324        }
325
326        if (count == 1) {
327            return transitions[nonNullIndex];
328        }
329
330        TransitionSet transitionSet = new TransitionSet();
331        for (int i = 0; i < transitions.length; i++) {
332            if (transitions[i] != null) {
333                transitionSet.addTransition(transitions[i]);
334            }
335        }
336        return transitionSet;
337    }
338}
339