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