Visibility.java revision 18ab79967ce8bcde4b1507164ac8186e5135622e
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.animation.AnimatorListenerAdapter; 21import android.graphics.Bitmap; 22import android.graphics.Canvas; 23import android.graphics.drawable.BitmapDrawable; 24import android.view.View; 25import android.view.ViewGroup; 26 27/** 28 * This transition tracks changes to the visibility of target views in the 29 * start and end scenes. Visibility is determined not just by the 30 * {@link View#setVisibility(int)} state of views, but also whether 31 * views exist in the current view hierarchy. The class is intended to be a 32 * utility for subclasses such as {@link Fade}, which use this visibility 33 * information to determine the specific animations to run when visibility 34 * changes occur. Subclasses should implement one or both of the methods 35 * {@link #onAppear(ViewGroup, TransitionValues, int, TransitionValues, int)}, 36 * {@link #onDisappear(ViewGroup, TransitionValues, int, TransitionValues, int)} or 37 * {@link #onAppear(ViewGroup, View, TransitionValues, TransitionValues)}, 38 * {@link #onDisappear(ViewGroup, View, TransitionValues, TransitionValues)}. 39 */ 40public abstract class Visibility extends Transition { 41 42 private static final String PROPNAME_VISIBILITY = "android:visibility:visibility"; 43 private static final String PROPNAME_PARENT = "android:visibility:parent"; 44 private static final String PROPNAME_SCREEN_LOCATION = "android:visibility:screenLocation"; 45 46 /** 47 * Mode used in {@link #setMode(int)} to make the transition 48 * operate on targets that are appearing. Maybe be combined with 49 * {@link #OUT} to target Visibility changes both in and out. 50 */ 51 public static final int IN = 0x1; 52 53 /** 54 * Mode used in {@link #setMode(int)} to make the transition 55 * operate on targets that are disappearing. Maybe be combined with 56 * {@link #IN} to target Visibility changes both in and out. 57 */ 58 public static final int OUT = 0x2; 59 60 private static final String[] sTransitionProperties = { 61 PROPNAME_VISIBILITY, 62 PROPNAME_PARENT, 63 PROPNAME_SCREEN_LOCATION, 64 }; 65 66 private static class VisibilityInfo { 67 boolean visibilityChange; 68 boolean fadeIn; 69 int startVisibility; 70 int endVisibility; 71 ViewGroup startParent; 72 ViewGroup endParent; 73 } 74 75 private int mMode = IN | OUT; 76 77 /** 78 * Changes the transition to support appearing and/or disappearing Views, depending 79 * on <code>mode</code>. 80 * 81 * @param mode The behavior supported by this transition, a combination of 82 * {@link #IN} and {@link #OUT}. 83 */ 84 public void setMode(int mode) { 85 if ((mode & ~(IN | OUT)) != 0) { 86 throw new IllegalArgumentException("Only IN and OUT flags are allowed"); 87 } 88 mMode = mode; 89 } 90 91 @Override 92 public String[] getTransitionProperties() { 93 return sTransitionProperties; 94 } 95 96 private void captureValues(TransitionValues transitionValues) { 97 int visibility = transitionValues.view.getVisibility(); 98 transitionValues.values.put(PROPNAME_VISIBILITY, visibility); 99 transitionValues.values.put(PROPNAME_PARENT, transitionValues.view.getParent()); 100 int[] loc = new int[2]; 101 transitionValues.view.getLocationOnScreen(loc); 102 transitionValues.values.put(PROPNAME_SCREEN_LOCATION, loc); 103 } 104 105 @Override 106 public void captureStartValues(TransitionValues transitionValues) { 107 captureValues(transitionValues); 108 } 109 110 @Override 111 public void captureEndValues(TransitionValues transitionValues) { 112 captureValues(transitionValues); 113 } 114 115 /** 116 * Returns whether the view is 'visible' according to the given values 117 * object. This is determined by testing the same properties in the values 118 * object that are used to determine whether the object is appearing or 119 * disappearing in the {@link 120 * Transition#createAnimator(ViewGroup, TransitionValues, TransitionValues)} 121 * method. This method can be called by, for example, subclasses that want 122 * to know whether the object is visible in the same way that Visibility 123 * determines it for the actual animation. 124 * 125 * @param values The TransitionValues object that holds the information by 126 * which visibility is determined. 127 * @return True if the view reference by <code>values</code> is visible, 128 * false otherwise. 129 */ 130 public boolean isVisible(TransitionValues values) { 131 if (values == null) { 132 return false; 133 } 134 int visibility = (Integer) values.values.get(PROPNAME_VISIBILITY); 135 View parent = (View) values.values.get(PROPNAME_PARENT); 136 137 return visibility == View.VISIBLE && parent != null; 138 } 139 140 private VisibilityInfo getVisibilityChangeInfo(TransitionValues startValues, 141 TransitionValues endValues) { 142 final VisibilityInfo visInfo = new VisibilityInfo(); 143 visInfo.visibilityChange = false; 144 visInfo.fadeIn = false; 145 if (startValues != null && startValues.values.containsKey(PROPNAME_VISIBILITY)) { 146 visInfo.startVisibility = (Integer) startValues.values.get(PROPNAME_VISIBILITY); 147 visInfo.startParent = (ViewGroup) startValues.values.get(PROPNAME_PARENT); 148 } else { 149 visInfo.startVisibility = -1; 150 visInfo.startParent = null; 151 } 152 if (endValues != null && endValues.values.containsKey(PROPNAME_VISIBILITY)) { 153 visInfo.endVisibility = (Integer) endValues.values.get(PROPNAME_VISIBILITY); 154 visInfo.endParent = (ViewGroup) endValues.values.get(PROPNAME_PARENT); 155 } else { 156 visInfo.endVisibility = -1; 157 visInfo.endParent = null; 158 } 159 if (startValues != null && endValues != null) { 160 if (visInfo.startVisibility == visInfo.endVisibility && 161 visInfo.startParent == visInfo.endParent) { 162 return visInfo; 163 } else { 164 if (visInfo.startVisibility != visInfo.endVisibility) { 165 if (visInfo.startVisibility == View.VISIBLE) { 166 visInfo.fadeIn = false; 167 visInfo.visibilityChange = true; 168 } else if (visInfo.endVisibility == View.VISIBLE) { 169 visInfo.fadeIn = true; 170 visInfo.visibilityChange = true; 171 } 172 // no visibilityChange if going between INVISIBLE and GONE 173 } else if (visInfo.startParent != visInfo.endParent) { 174 if (visInfo.endParent == null) { 175 visInfo.fadeIn = false; 176 visInfo.visibilityChange = true; 177 } else if (visInfo.startParent == null) { 178 visInfo.fadeIn = true; 179 visInfo.visibilityChange = true; 180 } 181 } 182 } 183 } 184 if (startValues == null) { 185 visInfo.fadeIn = true; 186 visInfo.visibilityChange = true; 187 } else if (endValues == null) { 188 visInfo.fadeIn = false; 189 visInfo.visibilityChange = true; 190 } 191 return visInfo; 192 } 193 194 @Override 195 public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, 196 TransitionValues endValues) { 197 VisibilityInfo visInfo = getVisibilityChangeInfo(startValues, endValues); 198 if (visInfo.visibilityChange 199 && (visInfo.startParent != null || visInfo.endParent != null)) { 200 if (visInfo.fadeIn) { 201 return onAppear(sceneRoot, startValues, visInfo.startVisibility, 202 endValues, visInfo.endVisibility); 203 } else { 204 return onDisappear(sceneRoot, startValues, visInfo.startVisibility, 205 endValues, visInfo.endVisibility 206 ); 207 } 208 } 209 return null; 210 } 211 212 /** 213 * The default implementation of this method calls 214 * {@link #onAppear(ViewGroup, View, TransitionValues, TransitionValues)}. 215 * Subclasses should override this method or 216 * {@link #onAppear(ViewGroup, View, TransitionValues, TransitionValues)}. 217 * if they need to create an Animator when targets appear. 218 * The method should only be called by the Visibility class; it is 219 * not intended to be called from external classes. 220 * 221 * @param sceneRoot The root of the transition hierarchy 222 * @param startValues The target values in the start scene 223 * @param startVisibility The target visibility in the start scene 224 * @param endValues The target values in the end scene 225 * @param endVisibility The target visibility in the end scene 226 * @return An Animator to be started at the appropriate time in the 227 * overall transition for this scene change. A null value means no animation 228 * should be run. 229 */ 230 public Animator onAppear(ViewGroup sceneRoot, 231 TransitionValues startValues, int startVisibility, 232 TransitionValues endValues, int endVisibility) { 233 if ((mMode & IN) != IN || endValues == null) { 234 return null; 235 } 236 return onAppear(sceneRoot, endValues.view, startValues, endValues); 237 } 238 239 /** 240 * The default implementation of this method returns a null Animator. Subclasses should 241 * override this method to make targets appear with the desired transition. The 242 * method should only be called from 243 * {@link #onAppear(ViewGroup, TransitionValues, int, TransitionValues, int)}. 244 * 245 * @param sceneRoot The root of the transition hierarchy 246 * @param view The View to make appear. This will be in the target scene's View hierarchy and 247 * will be VISIBLE. 248 * @param startValues The target values in the start scene 249 * @param endValues The target values in the end scene 250 * @return An Animator to be started at the appropriate time in the 251 * overall transition for this scene change. A null value means no animation 252 * should be run. 253 */ 254 public Animator onAppear(ViewGroup sceneRoot, View view, TransitionValues startValues, 255 TransitionValues endValues) { 256 return null; 257 } 258 259 /** 260 * Subclasses should override this method or 261 * {@link #onDisappear(ViewGroup, View, TransitionValues, TransitionValues)} 262 * if they need to create an Animator when targets disappear. 263 * The method should only be called by the Visibility class; it is 264 * not intended to be called from external classes. 265 * <p> 266 * The default implementation of this method attempts to find a View to use to call 267 * {@link #onDisappear(ViewGroup, View, TransitionValues, TransitionValues)}, 268 * based on the situation of the View in the View hierarchy. For example, 269 * if a View was simply removed from its parent, then the View will be added 270 * into a {@link android.view.ViewGroupOverlay} and passed as the <code>view</code> 271 * parameter in {@link #onDisappear(ViewGroup, View, TransitionValues, TransitionValues)}. 272 * If a visible View is changed to be {@link View#GONE} or {@link View#INVISIBLE}, 273 * then it can be used as the <code>view</code> and the visibility will be changed 274 * to {@link View#VISIBLE} for the duration of the animation. However, if a View 275 * is in a hierarchy which is also altering its visibility, the situation can be 276 * more complicated. In general, if a view that is no longer in the hierarchy in 277 * the end scene still has a parent (so its parent hierarchy was removed, but it 278 * was not removed from its parent), then it will be left alone to avoid side-effects from 279 * improperly removing it from its parent. The only exception to this is if 280 * the previous {@link Scene} was {@link Scene#getSceneForLayout(ViewGroup, int, 281 * android.content.Context) created from a layout resource file}, then it is considered 282 * safe to un-parent the starting scene view in order to make it disappear.</p> 283 * 284 * @param sceneRoot The root of the transition hierarchy 285 * @param startValues The target values in the start scene 286 * @param startVisibility The target visibility in the start scene 287 * @param endValues The target values in the end scene 288 * @param endVisibility The target visibility in the end scene 289 * @return An Animator to be started at the appropriate time in the 290 * overall transition for this scene change. A null value means no animation 291 * should be run. 292 */ 293 public Animator onDisappear(ViewGroup sceneRoot, 294 TransitionValues startValues, int startVisibility, 295 TransitionValues endValues, int endVisibility) { 296 if ((mMode & OUT) != OUT) { 297 return null; 298 } 299 300 View startView = (startValues != null) ? startValues.view : null; 301 View endView = (endValues != null) ? endValues.view : null; 302 View overlayView = null; 303 View viewToKeep = null; 304 if (endView == null || endView.getParent() == null) { 305 if (endView != null) { 306 // endView was removed from its parent - add it to the overlay 307 overlayView = endView; 308 } else if (startView != null) { 309 // endView does not exist. Use startView only under certain 310 // conditions, because placing a view in an overlay necessitates 311 // it being removed from its current parent 312 if (startView.getParent() == null) { 313 // no parent - safe to use 314 overlayView = startView; 315 } else if (startView.getParent() instanceof View) { 316 View startParent = (View) startView.getParent(); 317 if (!isValidTarget(startParent)) { 318 if (startView.isAttachedToWindow()) { 319 overlayView = copyViewImage(startView); 320 } else { 321 overlayView = startView; 322 } 323 } else if (startParent.getParent() == null) { 324 int id = startParent.getId(); 325 if (id != View.NO_ID && sceneRoot.findViewById(id) != null 326 && mCanRemoveViews) { 327 // no parent, but its parent is unparented but the parent 328 // hierarchy has been replaced by a new hierarchy with the same id 329 // and it is safe to un-parent startView 330 overlayView = startView; 331 } 332 } 333 } 334 } 335 } else { 336 // visibility change 337 if (endVisibility == View.INVISIBLE) { 338 viewToKeep = endView; 339 } else { 340 // Becoming GONE 341 if (startView == endView) { 342 viewToKeep = endView; 343 } else { 344 overlayView = startView; 345 } 346 } 347 } 348 final int finalVisibility = endVisibility; 349 final ViewGroup finalSceneRoot = sceneRoot; 350 351 if (overlayView != null) { 352 // TODO: Need to do this for general case of adding to overlay 353 int[] screenLoc = (int[]) startValues.values.get(PROPNAME_SCREEN_LOCATION); 354 int screenX = screenLoc[0]; 355 int screenY = screenLoc[1]; 356 int[] loc = new int[2]; 357 sceneRoot.getLocationOnScreen(loc); 358 overlayView.offsetLeftAndRight((screenX - loc[0]) - overlayView.getLeft()); 359 overlayView.offsetTopAndBottom((screenY - loc[1]) - overlayView.getTop()); 360 sceneRoot.getOverlay().add(overlayView); 361 Animator animator = onDisappear(sceneRoot, overlayView, startValues, endValues); 362 if (animator == null) { 363 sceneRoot.getOverlay().remove(overlayView); 364 } else { 365 final View finalOverlayView = overlayView; 366 animator.addListener(new AnimatorListenerAdapter() { 367 @Override 368 public void onAnimationEnd(Animator animation) { 369 finalSceneRoot.getOverlay().remove(finalOverlayView); 370 } 371 }); 372 } 373 return animator; 374 } 375 376 if (viewToKeep != null) { 377 int originalVisibility = viewToKeep.getVisibility(); 378 viewToKeep.setVisibility(View.VISIBLE); 379 Animator animator = onDisappear(sceneRoot, viewToKeep, startValues, endValues); 380 if (animator == null) { 381 viewToKeep.setVisibility(originalVisibility); 382 } else { 383 final View finalViewToKeep = viewToKeep; 384 animator.addListener(new AnimatorListenerAdapter() { 385 boolean mCanceled = false; 386 387 @Override 388 public void onAnimationPause(Animator animation) { 389 if (!mCanceled) { 390 finalViewToKeep.setVisibility(finalVisibility); 391 } 392 } 393 394 @Override 395 public void onAnimationResume(Animator animation) { 396 if (!mCanceled) { 397 finalViewToKeep.setVisibility(View.VISIBLE); 398 } 399 } 400 401 @Override 402 public void onAnimationCancel(Animator animation) { 403 mCanceled = true; 404 } 405 406 @Override 407 public void onAnimationEnd(Animator animation) { 408 if (!mCanceled) { 409 finalViewToKeep.setVisibility(finalVisibility); 410 } 411 } 412 }); 413 } 414 return animator; 415 } 416 return null; 417 } 418 419 private View copyViewImage(View view) { 420 int width = view.getWidth(); 421 int height = view.getHeight(); 422 if (width <= 0 || height <= 0) { 423 return null; 424 } 425 Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 426 Canvas canvas = new Canvas(bitmap); 427 view.draw(canvas); 428 final BitmapDrawable drawable = new BitmapDrawable(bitmap); 429 430 View overlayView = new View(view.getContext()); 431 overlayView.setBackground(drawable); 432 int widthSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY); 433 int heightSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY); 434 overlayView.measure(widthSpec, heightSpec); 435 overlayView.layout(0, 0, width, height); 436 return overlayView; 437 } 438 439 @Override 440 boolean areValuesChanged(TransitionValues oldValues, TransitionValues newValues) { 441 VisibilityInfo changeInfo = getVisibilityChangeInfo(oldValues, newValues); 442 if (oldValues == null && newValues == null) { 443 return false; 444 } 445 return changeInfo.visibilityChange && (changeInfo.startVisibility == View.VISIBLE || 446 changeInfo.endVisibility == View.VISIBLE); 447 } 448 449 /** 450 * The default implementation of this method returns a null Animator. Subclasses should 451 * override this method to make targets disappear with the desired transition. The 452 * method should only be called from 453 * {@link #onDisappear(ViewGroup, TransitionValues, int, TransitionValues, int)}. 454 * 455 * @param sceneRoot The root of the transition hierarchy 456 * @param view The View to make disappear. This will be in the target scene's View 457 * hierarchy or in an {@link android.view.ViewGroupOverlay} and will be 458 * VISIBLE. 459 * @param startValues The target values in the start scene 460 * @param endValues The target values in the end scene 461 * @return An Animator to be started at the appropriate time in the 462 * overall transition for this scene change. A null value means no animation 463 * should be run. 464 */ 465 public Animator onDisappear(ViewGroup sceneRoot, View view, TransitionValues startValues, 466 TransitionValues endValues) { 467 return null; 468 } 469} 470