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