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            final ArrayList<View> sharedElementTargets) {
101        if (enterTransitionObject != null || sharedElementTransitionObject != null) {
102            final Transition enterTransition = (Transition) enterTransitionObject;
103            if (enterTransition != null) {
104                enterTransition.addTarget(nonExistentView);
105            }
106            if (sharedElementTransitionObject != null) {
107                Transition sharedElementTransition = (Transition) sharedElementTransitionObject;
108                addTargets(sharedElementTransition, sharedElementTargets);
109            }
110
111            if (inFragment != null) {
112                container.getViewTreeObserver().addOnPreDrawListener(
113                        new ViewTreeObserver.OnPreDrawListener() {
114                            public boolean onPreDraw() {
115                                container.getViewTreeObserver().removeOnPreDrawListener(this);
116                                View fragmentView = inFragment.getView();
117                                if (fragmentView != null) {
118                                    if (!nameOverrides.isEmpty()) {
119                                        findNamedViews(renamedViews, fragmentView);
120                                        renamedViews.keySet().retainAll(nameOverrides.values());
121                                        for (Map.Entry<String, String> entry : nameOverrides.entrySet()) {
122                                            String to = entry.getValue();
123                                            View view = renamedViews.get(to);
124                                            if (view != null) {
125                                                String from = entry.getKey();
126                                                view.setTransitionName(from);
127                                            }
128                                        }
129                                    }
130                                    if (enterTransition != null) {
131                                        captureTransitioningViews(enteringViews, fragmentView);
132                                        enteringViews.removeAll(renamedViews.values());
133                                        addTargets(enterTransition, enteringViews);
134                                    }
135                                }
136                                return true;
137                            }
138                        });
139            }
140            setSharedElementEpicenter(enterTransition, epicenterView);
141        }
142    }
143
144    public static Object mergeTransitions(Object enterTransitionObject,
145            Object exitTransitionObject, Object sharedElementTransitionObject,
146            boolean allowOverlap) {
147        boolean overlap = true;
148        Transition enterTransition = (Transition) enterTransitionObject;
149        Transition exitTransition = (Transition) exitTransitionObject;
150        Transition sharedElementTransition = (Transition) sharedElementTransitionObject;
151
152        if (enterTransition != null && exitTransition != null) {
153            overlap = allowOverlap;
154        }
155
156        // Wrap the transitions. Explicit targets like in enter and exit will cause the
157        // views to be targeted regardless of excluded views. If that happens, then the
158        // excluded fragments views (hidden fragments) will still be in the transition.
159
160        Transition transition;
161        if (overlap) {
162            // Regular transition -- do it all together
163            TransitionSet transitionSet = new TransitionSet();
164            if (enterTransition != null) {
165                transitionSet.addTransition(enterTransition);
166            }
167            if (exitTransition != null) {
168                transitionSet.addTransition(exitTransition);
169            }
170            if (sharedElementTransition != null) {
171                transitionSet.addTransition(sharedElementTransition);
172            }
173            transition = transitionSet;
174        } else {
175            // First do exit, then enter, but allow shared element transition to happen
176            // during both.
177            Transition staggered = null;
178            if (exitTransition != null && enterTransition != null) {
179                staggered = new TransitionSet()
180                        .addTransition(exitTransition)
181                        .addTransition(enterTransition)
182                        .setOrdering(TransitionSet.ORDERING_SEQUENTIAL);
183            } else if (exitTransition != null) {
184                staggered = exitTransition;
185            } else if (enterTransition != null) {
186                staggered = enterTransition;
187            }
188            if (sharedElementTransition != null) {
189                TransitionSet together = new TransitionSet();
190                if (staggered != null) {
191                    together.addTransition(staggered);
192                }
193                together.addTransition(sharedElementTransition);
194                transition = together;
195            } else {
196                transition = staggered;
197            }
198        }
199        return transition;
200    }
201
202
203
204    private static void setSharedElementEpicenter(Transition transition,
205            final EpicenterView epicenterView) {
206        if (transition != null) {
207            transition.setEpicenterCallback(new Transition.EpicenterCallback() {
208                private Rect mEpicenter;
209
210                @Override
211                public Rect onGetEpicenter(Transition transition) {
212                    if (mEpicenter == null && epicenterView.epicenter != null) {
213                        mEpicenter = getBoundsOnScreen(epicenterView.epicenter);
214                    }
215                    return mEpicenter;
216                }
217            });
218        }
219    }
220
221    private static Rect getBoundsOnScreen(View view) {
222        Rect epicenter = new Rect();
223        int[] loc = new int[2];
224        view.getLocationOnScreen(loc);
225        // not as good as View.getBoundsOnScreen, but that's not public
226        epicenter.set(loc[0], loc[1], loc[0] + view.getWidth(), loc[1] + view.getHeight());
227        return epicenter;
228    }
229
230    private static void captureTransitioningViews(ArrayList<View> transitioningViews, View view) {
231        if (view.getVisibility() == View.VISIBLE) {
232            if (view instanceof ViewGroup) {
233                ViewGroup viewGroup = (ViewGroup) view;
234                if (viewGroup.isTransitionGroup()) {
235                    transitioningViews.add(viewGroup);
236                } else {
237                    int count = viewGroup.getChildCount();
238                    for (int i = 0; i < count; i++) {
239                        View child = viewGroup.getChildAt(i);
240                        captureTransitioningViews(transitioningViews, child);
241                    }
242                }
243            } else {
244                transitioningViews.add(view);
245            }
246        }
247    }
248
249    public static void findNamedViews(Map<String, View> namedViews, View view) {
250        if (view.getVisibility() == View.VISIBLE) {
251            String transitionName = view.getTransitionName();
252            if (transitionName != null) {
253                namedViews.put(transitionName, view);
254            }
255            if (view instanceof ViewGroup) {
256                ViewGroup viewGroup = (ViewGroup) view;
257                int count = viewGroup.getChildCount();
258                for (int i = 0; i < count; i++) {
259                    View child = viewGroup.getChildAt(i);
260                    findNamedViews(namedViews, child);
261                }
262            }
263        }
264    }
265
266    public static void cleanupTransitions(final View sceneRoot, final View nonExistentView,
267            Object enterTransitionObject, final ArrayList<View> enteringViews,
268            Object exitTransitionObject, final ArrayList<View> exitingViews,
269            Object sharedElementTransitionObject, final ArrayList<View> sharedElementTargets,
270            Object overallTransitionObject, final ArrayList<View> hiddenViews,
271            final Map<String, View> renamedViews) {
272        final Transition enterTransition = (Transition) enterTransitionObject;
273        final Transition exitTransition = (Transition) exitTransitionObject;
274        final Transition sharedElementTransition = (Transition) sharedElementTransitionObject;
275        final Transition overallTransition = (Transition) overallTransitionObject;
276        if (overallTransition != null) {
277            sceneRoot.getViewTreeObserver().addOnPreDrawListener(
278                    new ViewTreeObserver.OnPreDrawListener() {
279                public boolean onPreDraw() {
280                    sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
281                    if (enterTransition != null) {
282                        enterTransition.removeTarget(nonExistentView);
283                        removeTargets(enterTransition, enteringViews);
284                    }
285                    if (exitTransition != null) {
286                        removeTargets(exitTransition, exitingViews);
287                    }
288                    if (sharedElementTransition != null) {
289                        removeTargets(sharedElementTransition, sharedElementTargets);
290                    }
291                    for (Map.Entry<String, View> entry : renamedViews.entrySet()) {
292                        View view = entry.getValue();
293                        String name = entry.getKey();
294                        view.setTransitionName(name);
295                    }
296                    int numViews = hiddenViews.size();
297                    for (int i = 0; i < numViews; i++) {
298                        overallTransition.excludeTarget(hiddenViews.get(i), false);
299                    }
300                    overallTransition.excludeTarget(nonExistentView, false);
301                    return true;
302                }
303            });
304        }
305    }
306
307    public static void removeTargets(Object transitionObject, ArrayList<View> views) {
308        Transition transition = (Transition) transitionObject;
309        int numViews = views.size();
310        for (int i = 0; i < numViews; i++) {
311            transition.removeTarget(views.get(i));
312        }
313    }
314
315    public static void addTargets(Object transitionObject, ArrayList<View> views) {
316        Transition transition = (Transition) transitionObject;
317        int numViews = views.size();
318        for (int i = 0; i < numViews; i++) {
319            transition.addTarget(views.get(i));
320        }
321    }
322
323    public interface ViewRetriever {
324        View getView();
325    }
326
327    public static class EpicenterView {
328        public View epicenter;
329    }
330}
331