Visibility.java revision d82c8ac4db7091d2e976af4c89a1734465d20cd2
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.Animator;
20import android.view.View;
21import android.view.ViewGroup;
22
23/**
24 * This transition tracks changes to the visibility of target views in the
25 * start and end scenes. Visibility is determined not just by the
26 * {@link View#setVisibility(int)} state of views, but also whether
27 * views exist in the current view hierarchy. The class is intended to be a
28 * utility for subclasses such as {@link Fade}, which use this visibility
29 * information to determine the specific animations to run when visibility
30 * changes occur. Subclasses should implement one or both of the methods
31 * {@link #onAppear(ViewGroup, TransitionValues, int, TransitionValues, int)},
32 * {@link #onDisappear(ViewGroup, TransitionValues, int, TransitionValues, int)},
33 *
34 * <p>Note that a view's visibility change is determined by both whether the view
35 * itself is changing and whether its parent hierarchy's visibility is changing.
36 * That is, a view that appears in the end scene will only trigger a call to
37 * {@link #onAppear(android.view.ViewGroup, TransitionValues, int, TransitionValues, int)
38 * appear()} if its parent hierarchy was stable between the start and end scenes.
39 * This is done to avoid causing a visibility transition on every node in a hierarchy
40 * when only the top-most node is the one that should be transitioned in/out.
41 * Stability is determined by either the parent hierarchy views being the same
42 * between scenes or, if scenes are inflated from layout resource files and thus
43 * have result in different view instances, if the views represented by
44 * the ids of those parents are stable. This means that visibility determination
45 * is more effective with inflated view hierarchies if ids are used.
46 * The exception to this is when the visibility subclass transition is
47 * targeted at specific views, in which case the visibility of parent views
48 * is ignored.</p>
49 */
50public abstract class Visibility extends Transition {
51
52    private static final String PROPNAME_VISIBILITY = "android:visibility:visibility";
53    private static final String PROPNAME_PARENT = "android:visibility:parent";
54    private static final String[] sTransitionProperties = {
55            PROPNAME_VISIBILITY,
56            PROPNAME_PARENT,
57    };
58
59    private static class VisibilityInfo {
60        boolean visibilityChange;
61        boolean fadeIn;
62        int startVisibility;
63        int endVisibility;
64        ViewGroup startParent;
65        ViewGroup endParent;
66    }
67
68    // Temporary structure, used in calculating state in setup() and play()
69    private VisibilityInfo mTmpVisibilityInfo = new VisibilityInfo();
70
71    @Override
72    public String[] getTransitionProperties() {
73        return sTransitionProperties;
74    }
75
76    private void captureValues(TransitionValues transitionValues) {
77        int visibility = transitionValues.view.getVisibility();
78        transitionValues.values.put(PROPNAME_VISIBILITY, visibility);
79        transitionValues.values.put(PROPNAME_PARENT, transitionValues.view.getParent());
80    }
81
82    @Override
83    public void captureStartValues(TransitionValues transitionValues) {
84        captureValues(transitionValues);
85    }
86
87    @Override
88    public void captureEndValues(TransitionValues transitionValues) {
89        captureValues(transitionValues);
90    }
91
92    /**
93     * Returns whether the view is 'visible' according to the given values
94     * object. This is determined by testing the same properties in the values
95     * object that are used to determine whether the object is appearing or
96     * disappearing in the {@link
97     * Transition#createAnimator(ViewGroup, TransitionValues, TransitionValues)}
98     * method. This method can be called by, for example, subclasses that want
99     * to know whether the object is visible in the same way that Visibility
100     * determines it for the actual animation.
101     *
102     * @param values The TransitionValues object that holds the information by
103     * which visibility is determined.
104     * @return True if the view reference by <code>values</code> is visible,
105     * false otherwise.
106     */
107    public boolean isVisible(TransitionValues values) {
108        if (values == null) {
109            return false;
110        }
111        int visibility = (Integer) values.values.get(PROPNAME_VISIBILITY);
112        View parent = (View) values.values.get(PROPNAME_PARENT);
113
114        return visibility == View.VISIBLE && parent != null;
115    }
116
117    /**
118     * Tests whether the hierarchy, up to the scene root, changes visibility between
119     * start and end scenes. This is done to ensure that a view that changes visibility
120     * is only animated if that view's parent was stable between scenes; we should not
121     * fade an entire hierarchy, but rather just the top-most node in the hierarchy that
122     * changed visibility. Note that both the start and end parents are passed in
123     * because the instances may differ for the same view due to layout inflation
124     * between scenes.
125     *
126     * @param sceneRoot The root of the scene hierarchy
127     * @param startView The container view in the start scene
128     * @param endView The container view in the end scene
129     * @return true if the parent hierarchy experienced a visibility change, false
130     * otherwise
131     */
132    private boolean isHierarchyVisibilityChanging(ViewGroup sceneRoot, ViewGroup startView,
133            ViewGroup endView) {
134
135        if (startView == sceneRoot || endView == sceneRoot) {
136            return false;
137        }
138        TransitionValues startValues = startView != null ?
139                getTransitionValues(startView, true) : getTransitionValues(endView, true);
140        TransitionValues endValues = endView != null ?
141                getTransitionValues(endView, false) : getTransitionValues(startView, false);
142
143        if (startValues == null || endValues == null) {
144            return true;
145        }
146        Integer visibility = (Integer) startValues.values.get(PROPNAME_VISIBILITY);
147        int startVisibility = (visibility != null) ? visibility : -1;
148        ViewGroup startParent = (ViewGroup) startValues.values.get(PROPNAME_PARENT);
149        visibility = (Integer) endValues.values.get(PROPNAME_VISIBILITY);
150        int endVisibility = (visibility != null) ? visibility : -1;
151        ViewGroup endParent = (ViewGroup) endValues.values.get(PROPNAME_PARENT);
152        if (startVisibility != endVisibility || startParent != endParent) {
153            return true;
154        }
155
156        if (startParent != null || endParent != null) {
157            return isHierarchyVisibilityChanging(sceneRoot, startParent, endParent);
158        }
159        return false;
160    }
161
162    private VisibilityInfo getVisibilityChangeInfo(TransitionValues startValues,
163            TransitionValues endValues) {
164        final VisibilityInfo visInfo = mTmpVisibilityInfo;
165        visInfo.visibilityChange = false;
166        visInfo.fadeIn = false;
167        if (startValues != null) {
168            visInfo.startVisibility = (Integer) startValues.values.get(PROPNAME_VISIBILITY);
169            visInfo.startParent = (ViewGroup) startValues.values.get(PROPNAME_PARENT);
170        } else {
171            visInfo.startVisibility = -1;
172            visInfo.startParent = null;
173        }
174        if (endValues != null) {
175            visInfo.endVisibility = (Integer) endValues.values.get(PROPNAME_VISIBILITY);
176            visInfo.endParent = (ViewGroup) endValues.values.get(PROPNAME_PARENT);
177        } else {
178            visInfo.endVisibility = -1;
179            visInfo.endParent = null;
180        }
181        if (startValues != null && endValues != null) {
182            if (visInfo.startVisibility == visInfo.endVisibility &&
183                    visInfo.startParent == visInfo.endParent) {
184                return visInfo;
185            } else {
186                if (visInfo.startVisibility != visInfo.endVisibility) {
187                    if (visInfo.startVisibility == View.VISIBLE) {
188                        visInfo.fadeIn = false;
189                        visInfo.visibilityChange = true;
190                    } else if (visInfo.endVisibility == View.VISIBLE) {
191                        visInfo.fadeIn = true;
192                        visInfo.visibilityChange = true;
193                    }
194                    // no visibilityChange if going between INVISIBLE and GONE
195                } else if (visInfo.startParent != visInfo.endParent) {
196                    if (visInfo.endParent == null) {
197                        visInfo.fadeIn = false;
198                        visInfo.visibilityChange = true;
199                    } else if (visInfo.startParent == null) {
200                        visInfo.fadeIn = true;
201                        visInfo.visibilityChange = true;
202                    }
203                }
204            }
205        }
206        if (startValues == null) {
207            visInfo.fadeIn = true;
208            visInfo.visibilityChange = true;
209        } else if (endValues == null) {
210            visInfo.fadeIn = false;
211            visInfo.visibilityChange = true;
212        }
213        return visInfo;
214    }
215
216    @Override
217    public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
218            TransitionValues endValues) {
219        VisibilityInfo visInfo = getVisibilityChangeInfo(startValues, endValues);
220        if (visInfo.visibilityChange) {
221            // Only transition views that are either targets of this transition
222            // or whose parent hierarchies remain stable between scenes
223            boolean isTarget = false;
224            if (mTargets.size() > 0 || mTargetIds.size() > 0) {
225                View startView = startValues != null ? startValues.view : null;
226                View endView = endValues != null ? endValues.view : null;
227                int startId = startView != null ? startView.getId() : -1;
228                int endId = endView != null ? endView.getId() : -1;
229                isTarget = isValidTarget(startView, startId) || isValidTarget(endView, endId);
230            }
231            if (isTarget || ((visInfo.startParent != null || visInfo.endParent != null) &&
232                    !isHierarchyVisibilityChanging(sceneRoot,
233                            visInfo.startParent, visInfo.endParent))) {
234                if (visInfo.fadeIn) {
235                    return onAppear(sceneRoot, startValues, visInfo.startVisibility,
236                            endValues, visInfo.endVisibility);
237                } else {
238                    return onDisappear(sceneRoot, startValues, visInfo.startVisibility,
239                            endValues, visInfo.endVisibility
240                    );
241                }
242            }
243        }
244        return null;
245    }
246
247    /**
248     * The default implementation of this method does nothing. Subclasses
249     * should override if they need to create an Animator when targets appear.
250     * The method should only be called by the Visibility class; it is
251     * not intended to be called from external classes.
252     *
253     * @param sceneRoot The root of the transition hierarchy
254     * @param startValues The target values in the start scene
255     * @param startVisibility The target visibility in the start scene
256     * @param endValues The target values in the end scene
257     * @param endVisibility The target visibility in the end scene
258     * @return An Animator to be started at the appropriate time in the
259     * overall transition for this scene change. A null value means no animation
260     * should be run.
261     */
262    public Animator onAppear(ViewGroup sceneRoot,
263            TransitionValues startValues, int startVisibility,
264            TransitionValues endValues, int endVisibility) {
265        return null;
266    }
267
268    /**
269     * The default implementation of this method does nothing. Subclasses
270     * should override if they need to create an Animator when targets disappear.
271     * The method should only be called by the Visibility class; it is
272     * not intended to be called from external classes.
273     *
274     *
275     * @param sceneRoot The root of the transition hierarchy
276     * @param startValues The target values in the start scene
277     * @param startVisibility The target visibility in the start scene
278     * @param endValues The target values in the end scene
279     * @param endVisibility The target visibility in the end scene
280     * @return An Animator to be started at the appropriate time in the
281     * overall transition for this scene change. A null value means no animation
282     * should be run.
283     */
284    public Animator onDisappear(ViewGroup sceneRoot,
285            TransitionValues startValues, int startVisibility,
286            TransitionValues endValues, int endVisibility) {
287        return null;
288    }
289}
290