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