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 17 package android.transition; 18 19 import android.animation.Animator; 20 import android.animation.AnimatorListenerAdapter; 21 import android.animation.ObjectAnimator; 22 import android.util.Log; 23 import android.view.View; 24 import android.view.ViewGroup; 25 26 /** 27 * This transition tracks changes to the visibility of target views in the 28 * start and end scenes and fades views in or out when they become visible 29 * or non-visible. Visibility is determined by both the 30 * {@link View#setVisibility(int)} state of the view as well as whether it 31 * is parented in the current view hierarchy. 32 * 33 * <p>The ability of this transition to fade out a particular view, and the 34 * way that that fading operation takes place, is based on 35 * the situation of the view in the view hierarchy. For example, if a view was 36 * simply removed from its parent, then the view will be added into a {@link 37 * android.view.ViewGroupOverlay} while fading. If a visible view is 38 * changed to be {@link View#GONE} or {@link View#INVISIBLE}, then the 39 * visibility will be changed to {@link View#VISIBLE} for the duration of 40 * the animation. However, if a view is in a hierarchy which is also altering 41 * its visibility, the situation can be more complicated. In general, if a 42 * view that is no longer in the hierarchy in the end scene still has a 43 * parent (so its parent hierarchy was removed, but it was not removed from 44 * its parent), then it will be left alone to avoid side-effects from 45 * improperly removing it from its parent. The only exception to this is if 46 * the previous {@link Scene} was 47 * {@link Scene#getSceneForLayout(android.view.ViewGroup, int, android.content.Context) 48 * created from a layout resource file}, then it is considered safe to un-parent 49 * the starting scene view in order to fade it out.</p> 50 * 51 * <p>A Fade transition can be described in a resource file by using the 52 * tag <code>fade</code>, along with the standard 53 * attributes of {@link android.R.styleable#Fade} and 54 * {@link android.R.styleable#Transition}.</p> 55 56 */ 57 public class Fade extends Visibility { 58 59 private static boolean DBG = Transition.DBG && false; 60 61 private static final String LOG_TAG = "Fade"; 62 private static final String PROPNAME_SCREEN_X = "android:fade:screenX"; 63 private static final String PROPNAME_SCREEN_Y = "android:fade:screenY"; 64 65 /** 66 * Fading mode used in {@link #Fade(int)} to make the transition 67 * operate on targets that are appearing. Maybe be combined with 68 * {@link #OUT} to fade both in and out. 69 */ 70 public static final int IN = 0x1; 71 /** 72 * Fading mode used in {@link #Fade(int)} to make the transition 73 * operate on targets that are disappearing. Maybe be combined with 74 * {@link #IN} to fade both in and out. 75 */ 76 public static final int OUT = 0x2; 77 78 private int mFadingMode; 79 80 /** 81 * Constructs a Fade transition that will fade targets in and out. 82 */ 83 public Fade() { 84 this(IN | OUT); 85 } 86 87 /** 88 * Constructs a Fade transition that will fade targets in 89 * and/or out, according to the value of fadingMode. 90 * 91 * @param fadingMode The behavior of this transition, a combination of 92 * {@link #IN} and {@link #OUT}. 93 */ 94 public Fade(int fadingMode) { 95 mFadingMode = fadingMode; 96 } 97 98 /** 99 * Utility method to handle creating and running the Animator. 100 */ 101 private Animator createAnimation(View view, float startAlpha, float endAlpha, 102 AnimatorListenerAdapter listener) { 103 if (startAlpha == endAlpha) { 104 // run listener if we're noop'ing the animation, to get the end-state results now 105 if (listener != null) { 106 listener.onAnimationEnd(null); 107 } 108 return null; 109 } 110 final ObjectAnimator anim = ObjectAnimator.ofFloat(view, "transitionAlpha", startAlpha, 111 endAlpha); 112 if (DBG) { 113 Log.d(LOG_TAG, "Created animator " + anim); 114 } 115 if (listener != null) { 116 anim.addListener(listener); 117 anim.addPauseListener(listener); 118 } 119 return anim; 120 } 121 122 private void captureValues(TransitionValues transitionValues) { 123 int[] loc = new int[2]; 124 transitionValues.view.getLocationOnScreen(loc); 125 transitionValues.values.put(PROPNAME_SCREEN_X, loc[0]); 126 transitionValues.values.put(PROPNAME_SCREEN_Y, loc[1]); 127 } 128 129 @Override 130 public void captureStartValues(TransitionValues transitionValues) { 131 super.captureStartValues(transitionValues); 132 captureValues(transitionValues); 133 } 134 135 @Override 136 public Animator onAppear(ViewGroup sceneRoot, 137 TransitionValues startValues, int startVisibility, 138 TransitionValues endValues, int endVisibility) { 139 if ((mFadingMode & IN) != IN || endValues == null) { 140 return null; 141 } 142 final View endView = endValues.view; 143 if (DBG) { 144 View startView = (startValues != null) ? startValues.view : null; 145 Log.d(LOG_TAG, "Fade.onAppear: startView, startVis, endView, endVis = " + 146 startView + ", " + startVisibility + ", " + endView + ", " + endVisibility); 147 } 148 endView.setTransitionAlpha(0); 149 TransitionListener transitionListener = new TransitionListenerAdapter() { 150 boolean mCanceled = false; 151 float mPausedAlpha; 152 153 @Override 154 public void onTransitionCancel(Transition transition) { 155 endView.setTransitionAlpha(1); 156 mCanceled = true; 157 } 158 159 @Override 160 public void onTransitionEnd(Transition transition) { 161 if (!mCanceled) { 162 endView.setTransitionAlpha(1); 163 } 164 } 165 166 @Override 167 public void onTransitionPause(Transition transition) { 168 mPausedAlpha = endView.getTransitionAlpha(); 169 endView.setTransitionAlpha(1); 170 } 171 172 @Override 173 public void onTransitionResume(Transition transition) { 174 endView.setTransitionAlpha(mPausedAlpha); 175 } 176 }; 177 addListener(transitionListener); 178 return createAnimation(endView, 0, 1, null); 179 } 180 181 @Override 182 public Animator onDisappear(ViewGroup sceneRoot, 183 TransitionValues startValues, int startVisibility, 184 TransitionValues endValues, int endVisibility) { 185 if ((mFadingMode & OUT) != OUT) { 186 return null; 187 } 188 View view = null; 189 View startView = (startValues != null) ? startValues.view : null; 190 View endView = (endValues != null) ? endValues.view : null; 191 if (DBG) { 192 Log.d(LOG_TAG, "Fade.onDisappear: startView, startVis, endView, endVis = " + 193 startView + ", " + startVisibility + ", " + endView + ", " + endVisibility); 194 } 195 View overlayView = null; 196 View viewToKeep = null; 197 if (endView == null || endView.getParent() == null) { 198 if (endView != null) { 199 // endView was removed from its parent - add it to the overlay 200 view = overlayView = endView; 201 } else if (startView != null) { 202 // endView does not exist. Use startView only under certain 203 // conditions, because placing a view in an overlay necessitates 204 // it being removed from its current parent 205 if (startView.getParent() == null) { 206 // no parent - safe to use 207 view = overlayView = startView; 208 } else if (startView.getParent() instanceof View && 209 startView.getParent().getParent() == null) { 210 View startParent = (View) startView.getParent(); 211 int id = startParent.getId(); 212 if (id != View.NO_ID && sceneRoot.findViewById(id) != null && mCanRemoveViews) { 213 // no parent, but its parent is unparented but the parent 214 // hierarchy has been replaced by a new hierarchy with the same id 215 // and it is safe to un-parent startView 216 view = overlayView = startView; 217 } 218 } 219 } 220 } else { 221 // visibility change 222 if (endVisibility == View.INVISIBLE) { 223 view = endView; 224 viewToKeep = view; 225 } else { 226 // Becoming GONE 227 if (startView == endView) { 228 view = endView; 229 viewToKeep = view; 230 } else { 231 view = startView; 232 overlayView = view; 233 } 234 } 235 } 236 final int finalVisibility = endVisibility; 237 // TODO: add automatic facility to Visibility superclass for keeping views around 238 if (overlayView != null) { 239 // TODO: Need to do this for general case of adding to overlay 240 int screenX = (Integer) startValues.values.get(PROPNAME_SCREEN_X); 241 int screenY = (Integer) startValues.values.get(PROPNAME_SCREEN_Y); 242 int[] loc = new int[2]; 243 sceneRoot.getLocationOnScreen(loc); 244 overlayView.offsetLeftAndRight((screenX - loc[0]) - overlayView.getLeft()); 245 overlayView.offsetTopAndBottom((screenY - loc[1]) - overlayView.getTop()); 246 sceneRoot.getOverlay().add(overlayView); 247 // TODO: add automatic facility to Visibility superclass for keeping views around 248 final float startAlpha = 1; 249 float endAlpha = 0; 250 final View finalView = view; 251 final View finalOverlayView = overlayView; 252 final View finalViewToKeep = viewToKeep; 253 final ViewGroup finalSceneRoot = sceneRoot; 254 final AnimatorListenerAdapter endListener = new AnimatorListenerAdapter() { 255 @Override 256 public void onAnimationEnd(Animator animation) { 257 finalView.setTransitionAlpha(startAlpha); 258 // TODO: restore view offset from overlay repositioning 259 if (finalViewToKeep != null) { 260 finalViewToKeep.setVisibility(finalVisibility); 261 } 262 if (finalOverlayView != null) { 263 finalSceneRoot.getOverlay().remove(finalOverlayView); 264 } 265 } 266 267 @Override 268 public void onAnimationPause(Animator animation) { 269 if (finalOverlayView != null) { 270 finalSceneRoot.getOverlay().remove(finalOverlayView); 271 } 272 } 273 274 @Override 275 public void onAnimationResume(Animator animation) { 276 if (finalOverlayView != null) { 277 finalSceneRoot.getOverlay().add(finalOverlayView); 278 } 279 } 280 }; 281 return createAnimation(view, startAlpha, endAlpha, endListener); 282 } 283 if (viewToKeep != null) { 284 // TODO: find a different way to do this, like just changing the view to be 285 // VISIBLE for the duration of the transition 286 viewToKeep.setVisibility((View.VISIBLE)); 287 // TODO: add automatic facility to Visibility superclass for keeping views around 288 final float startAlpha = 1; 289 float endAlpha = 0; 290 final View finalView = view; 291 final View finalOverlayView = overlayView; 292 final View finalViewToKeep = viewToKeep; 293 final ViewGroup finalSceneRoot = sceneRoot; 294 final AnimatorListenerAdapter endListener = new AnimatorListenerAdapter() { 295 boolean mCanceled = false; 296 float mPausedAlpha = -1; 297 298 @Override 299 public void onAnimationPause(Animator animation) { 300 if (finalViewToKeep != null && !mCanceled) { 301 finalViewToKeep.setVisibility(finalVisibility); 302 } 303 mPausedAlpha = finalView.getTransitionAlpha(); 304 finalView.setTransitionAlpha(startAlpha); 305 } 306 307 @Override 308 public void onAnimationResume(Animator animation) { 309 if (finalViewToKeep != null && !mCanceled) { 310 finalViewToKeep.setVisibility(View.VISIBLE); 311 } 312 finalView.setTransitionAlpha(mPausedAlpha); 313 } 314 315 @Override 316 public void onAnimationCancel(Animator animation) { 317 mCanceled = true; 318 if (mPausedAlpha >= 0) { 319 finalView.setTransitionAlpha(mPausedAlpha); 320 } 321 } 322 323 @Override 324 public void onAnimationEnd(Animator animation) { 325 if (!mCanceled) { 326 finalView.setTransitionAlpha(startAlpha); 327 } 328 // TODO: restore view offset from overlay repositioning 329 if (finalViewToKeep != null && !mCanceled) { 330 finalViewToKeep.setVisibility(finalVisibility); 331 } 332 if (finalOverlayView != null) { 333 finalSceneRoot.getOverlay().remove(finalOverlayView); 334 } 335 } 336 }; 337 return createAnimation(view, startAlpha, endAlpha, endListener); 338 } 339 return null; 340 } 341 342 }