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 */
34public abstract class Visibility extends Transition {
35
36    private static final String PROPNAME_VISIBILITY = "android:visibility:visibility";
37    private static final String PROPNAME_PARENT = "android:visibility:parent";
38    private static final String[] sTransitionProperties = {
39            PROPNAME_VISIBILITY,
40            PROPNAME_PARENT,
41    };
42
43    private static class VisibilityInfo {
44        boolean visibilityChange;
45        boolean fadeIn;
46        int startVisibility;
47        int endVisibility;
48        ViewGroup startParent;
49        ViewGroup endParent;
50    }
51
52    @Override
53    public String[] getTransitionProperties() {
54        return sTransitionProperties;
55    }
56
57    private void captureValues(TransitionValues transitionValues) {
58        int visibility = transitionValues.view.getVisibility();
59        transitionValues.values.put(PROPNAME_VISIBILITY, visibility);
60        transitionValues.values.put(PROPNAME_PARENT, transitionValues.view.getParent());
61    }
62
63    @Override
64    public void captureStartValues(TransitionValues transitionValues) {
65        captureValues(transitionValues);
66    }
67
68    @Override
69    public void captureEndValues(TransitionValues transitionValues) {
70        captureValues(transitionValues);
71    }
72
73    /**
74     * Returns whether the view is 'visible' according to the given values
75     * object. This is determined by testing the same properties in the values
76     * object that are used to determine whether the object is appearing or
77     * disappearing in the {@link
78     * Transition#createAnimator(ViewGroup, TransitionValues, TransitionValues)}
79     * method. This method can be called by, for example, subclasses that want
80     * to know whether the object is visible in the same way that Visibility
81     * determines it for the actual animation.
82     *
83     * @param values The TransitionValues object that holds the information by
84     * which visibility is determined.
85     * @return True if the view reference by <code>values</code> is visible,
86     * false otherwise.
87     */
88    public boolean isVisible(TransitionValues values) {
89        if (values == null) {
90            return false;
91        }
92        int visibility = (Integer) values.values.get(PROPNAME_VISIBILITY);
93        View parent = (View) values.values.get(PROPNAME_PARENT);
94
95        return visibility == View.VISIBLE && parent != null;
96    }
97
98    private VisibilityInfo getVisibilityChangeInfo(TransitionValues startValues,
99            TransitionValues endValues) {
100        final VisibilityInfo visInfo = new VisibilityInfo();
101        visInfo.visibilityChange = false;
102        visInfo.fadeIn = false;
103        if (startValues != null) {
104            visInfo.startVisibility = (Integer) startValues.values.get(PROPNAME_VISIBILITY);
105            visInfo.startParent = (ViewGroup) startValues.values.get(PROPNAME_PARENT);
106        } else {
107            visInfo.startVisibility = -1;
108            visInfo.startParent = null;
109        }
110        if (endValues != null) {
111            visInfo.endVisibility = (Integer) endValues.values.get(PROPNAME_VISIBILITY);
112            visInfo.endParent = (ViewGroup) endValues.values.get(PROPNAME_PARENT);
113        } else {
114            visInfo.endVisibility = -1;
115            visInfo.endParent = null;
116        }
117        if (startValues != null && endValues != null) {
118            if (visInfo.startVisibility == visInfo.endVisibility &&
119                    visInfo.startParent == visInfo.endParent) {
120                return visInfo;
121            } else {
122                if (visInfo.startVisibility != visInfo.endVisibility) {
123                    if (visInfo.startVisibility == View.VISIBLE) {
124                        visInfo.fadeIn = false;
125                        visInfo.visibilityChange = true;
126                    } else if (visInfo.endVisibility == View.VISIBLE) {
127                        visInfo.fadeIn = true;
128                        visInfo.visibilityChange = true;
129                    }
130                    // no visibilityChange if going between INVISIBLE and GONE
131                } else if (visInfo.startParent != visInfo.endParent) {
132                    if (visInfo.endParent == null) {
133                        visInfo.fadeIn = false;
134                        visInfo.visibilityChange = true;
135                    } else if (visInfo.startParent == null) {
136                        visInfo.fadeIn = true;
137                        visInfo.visibilityChange = true;
138                    }
139                }
140            }
141        }
142        if (startValues == null) {
143            visInfo.fadeIn = true;
144            visInfo.visibilityChange = true;
145        } else if (endValues == null) {
146            visInfo.fadeIn = false;
147            visInfo.visibilityChange = true;
148        }
149        return visInfo;
150    }
151
152    @Override
153    public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
154            TransitionValues endValues) {
155        VisibilityInfo visInfo = getVisibilityChangeInfo(startValues, endValues);
156        if (visInfo.visibilityChange) {
157            // Only transition views that are either targets of this transition
158            // or whose parent hierarchies remain stable between scenes
159            boolean isTarget = false;
160            if (mTargets.size() > 0 || mTargetIds.size() > 0) {
161                View startView = startValues != null ? startValues.view : null;
162                View endView = endValues != null ? endValues.view : null;
163                int startId = startView != null ? startView.getId() : -1;
164                int endId = endView != null ? endView.getId() : -1;
165                isTarget = isValidTarget(startView, startId) || isValidTarget(endView, endId);
166            }
167            if (isTarget || ((visInfo.startParent != null || visInfo.endParent != null))) {
168                if (visInfo.fadeIn) {
169                    return onAppear(sceneRoot, startValues, visInfo.startVisibility,
170                            endValues, visInfo.endVisibility);
171                } else {
172                    return onDisappear(sceneRoot, startValues, visInfo.startVisibility,
173                            endValues, visInfo.endVisibility
174                    );
175                }
176            }
177        }
178        return null;
179    }
180
181    /**
182     * The default implementation of this method does nothing. Subclasses
183     * should override if they need to create an Animator when targets appear.
184     * The method should only be called by the Visibility class; it is
185     * not intended to be called from external classes.
186     *
187     * @param sceneRoot The root of the transition hierarchy
188     * @param startValues The target values in the start scene
189     * @param startVisibility The target visibility in the start scene
190     * @param endValues The target values in the end scene
191     * @param endVisibility The target visibility in the end scene
192     * @return An Animator to be started at the appropriate time in the
193     * overall transition for this scene change. A null value means no animation
194     * should be run.
195     */
196    public Animator onAppear(ViewGroup sceneRoot,
197            TransitionValues startValues, int startVisibility,
198            TransitionValues endValues, int endVisibility) {
199        return null;
200    }
201
202    /**
203     * The default implementation of this method does nothing. Subclasses
204     * should override if they need to create an Animator when targets disappear.
205     * The method should only be called by the Visibility class; it is
206     * not intended to be called from external classes.
207     *
208     *
209     * @param sceneRoot The root of the transition hierarchy
210     * @param startValues The target values in the start scene
211     * @param startVisibility The target visibility in the start scene
212     * @param endValues The target values in the end scene
213     * @param endVisibility The target visibility in the end scene
214     * @return An Animator to be started at the appropriate time in the
215     * overall transition for this scene change. A null value means no animation
216     * should be run.
217     */
218    public Animator onDisappear(ViewGroup sceneRoot,
219            TransitionValues startValues, int startVisibility,
220            TransitionValues endValues, int endVisibility) {
221        return null;
222    }
223}
224