Fade.java revision 23c61f6bc57a611d97d333bce0d8fe00ab81af4c
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.animation.ObjectAnimator; 22import android.util.Log; 23import android.view.View; 24import 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>A Fade transition can be described in a resource file by using the 34 * tag <code>fade</code>, along with the standard 35 * attributes of {@link android.R.styleable#Fade} and 36 * {@link android.R.styleable#Transition}.</p> 37 38 */ 39public class Fade extends Visibility { 40 41 private static boolean DBG = Transition.DBG && false; 42 43 private static final String LOG_TAG = "Fade"; 44 private static final String PROPNAME_ALPHA = "android:fade:alpha"; 45 private static final String PROPNAME_SCREEN_X = "android:fade:screenX"; 46 private static final String PROPNAME_SCREEN_Y = "android:fade:screenY"; 47 48 /** 49 * Fading mode used in {@link #Fade(int)} to make the transition 50 * operate on targets that are appearing. Maybe be combined with 51 * {@link #OUT} to fade both in and out. 52 */ 53 public static final int IN = 0x1; 54 /** 55 * Fading mode used in {@link #Fade(int)} to make the transition 56 * operate on targets that are disappearing. Maybe be combined with 57 * {@link #IN} to fade both in and out. 58 */ 59 public static final int OUT = 0x2; 60 61 private int mFadingMode; 62 63 /** 64 * Constructs a Fade transition that will fade targets in and out. 65 */ 66 public Fade() { 67 this(IN | OUT); 68 } 69 70 /** 71 * Constructs a Fade transition that will fade targets in 72 * and/or out, according to the value of fadingMode. 73 * 74 * @param fadingMode The behavior of this transition, a combination of 75 * {@link #IN} and {@link #OUT}. 76 */ 77 public Fade(int fadingMode) { 78 mFadingMode = fadingMode; 79 } 80 81 /** 82 * Utility method to handle creating and running the Animator. 83 */ 84 private Animator createAnimation(View view, float startAlpha, float endAlpha, 85 AnimatorListenerAdapter listener) { 86 if (startAlpha == endAlpha) { 87 // run listener if we're noop'ing the animation, to get the end-state results now 88 if (listener != null) { 89 listener.onAnimationEnd(null); 90 } 91 return null; 92 } 93 final ObjectAnimator anim = ObjectAnimator.ofFloat(view, "alpha", startAlpha, endAlpha); 94 if (DBG) { 95 Log.d(LOG_TAG, "Created animator " + anim); 96 } 97 if (listener != null) { 98 anim.addListener(listener); 99 anim.addPauseListener(listener); 100 } 101 return anim; 102 } 103 104 private void captureValues(TransitionValues transitionValues) { 105 float alpha = transitionValues.view.getAlpha(); 106 transitionValues.values.put(PROPNAME_ALPHA, alpha); 107 int[] loc = new int[2]; 108 transitionValues.view.getLocationOnScreen(loc); 109 transitionValues.values.put(PROPNAME_SCREEN_X, loc[0]); 110 transitionValues.values.put(PROPNAME_SCREEN_Y, loc[1]); 111 } 112 113 @Override 114 public void captureStartValues(TransitionValues transitionValues) { 115 super.captureStartValues(transitionValues); 116 captureValues(transitionValues); 117 } 118 119 120 @Override 121 public void captureEndValues(TransitionValues transitionValues) { 122 super.captureEndValues(transitionValues); 123 } 124 125 @Override 126 public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, 127 TransitionValues endValues) { 128 Animator animator = super.createAnimator(sceneRoot, startValues, endValues); 129 if (animator == null && startValues != null && endValues != null) { 130 boolean endVisible = isVisible(endValues); 131 final View endView = endValues.view; 132 float endAlpha = endView.getAlpha(); 133 float startAlpha = (Float) startValues.values.get(PROPNAME_ALPHA); 134 if ((endVisible && startAlpha < endAlpha && (mFadingMode & Fade.IN) != 0) || 135 (!endVisible && startAlpha > endAlpha && (mFadingMode & Fade.OUT) != 0)) { 136 animator = createAnimation(endView, startAlpha, endAlpha, null); 137 } 138 } 139 return animator; 140 } 141 142 @Override 143 public Animator onAppear(ViewGroup sceneRoot, 144 TransitionValues startValues, int startVisibility, 145 TransitionValues endValues, int endVisibility) { 146 if ((mFadingMode & IN) != IN || endValues == null) { 147 return null; 148 } 149 final View endView = endValues.view; 150 if (DBG) { 151 View startView = (startValues != null) ? startValues.view : null; 152 Log.d(LOG_TAG, "Fade.onAppear: startView, startVis, endView, endVis = " + 153 startView + ", " + startVisibility + ", " + endView + ", " + endVisibility); 154 } 155 // if alpha < 1, just fade it in from the current value 156 if (endView.getAlpha() == 1.0f) { 157 endView.setAlpha(0); 158 TransitionListener transitionListener = new TransitionListenerAdapter() { 159 boolean mCanceled = false; 160 float mPausedAlpha; 161 162 @Override 163 public void onTransitionCancel(Transition transition) { 164 endView.setAlpha(1); 165 mCanceled = true; 166 } 167 168 @Override 169 public void onTransitionEnd(Transition transition) { 170 if (!mCanceled) { 171 endView.setAlpha(1); 172 } 173 } 174 175 @Override 176 public void onTransitionPause(Transition transition) { 177 mPausedAlpha = endView.getAlpha(); 178 endView.setAlpha(1); 179 } 180 181 @Override 182 public void onTransitionResume(Transition transition) { 183 endView.setAlpha(mPausedAlpha); 184 } 185 }; 186 addListener(transitionListener); 187 } 188 return createAnimation(endView, endView.getAlpha(), 1, null); 189 } 190 191 @Override 192 public Animator onDisappear(ViewGroup sceneRoot, 193 TransitionValues startValues, int startVisibility, 194 TransitionValues endValues, int endVisibility) { 195 if ((mFadingMode & OUT) != OUT) { 196 return null; 197 } 198 View view; 199 View startView = (startValues != null) ? startValues.view : null; 200 View endView = (endValues != null) ? endValues.view : null; 201 if (DBG) { 202 Log.d(LOG_TAG, "Fade.onDisappear: startView, startVis, endView, endVis = " + 203 startView + ", " + startVisibility + ", " + endView + ", " + endVisibility); 204 } 205 View overlayView = null; 206 View viewToKeep = null; 207 if (endView == null || endView.getParent() == null) { 208 // view was removed: add the start view to the Overlay 209 view = startView; 210 overlayView = view; 211 } else { 212 // visibility change 213 if (endVisibility == View.INVISIBLE) { 214 view = endView; 215 viewToKeep = view; 216 } else { 217 // Becoming GONE 218 if (startView == endView) { 219 view = endView; 220 viewToKeep = view; 221 } else { 222 view = startView; 223 overlayView = view; 224 } 225 } 226 } 227 final int finalVisibility = endVisibility; 228 // TODO: add automatic facility to Visibility superclass for keeping views around 229 if (overlayView != null) { 230 // TODO: Need to do this for general case of adding to overlay 231 int screenX = (Integer) startValues.values.get(PROPNAME_SCREEN_X); 232 int screenY = (Integer) startValues.values.get(PROPNAME_SCREEN_Y); 233 int[] loc = new int[2]; 234 sceneRoot.getLocationOnScreen(loc); 235 overlayView.offsetLeftAndRight((screenX - loc[0]) - overlayView.getLeft()); 236 overlayView.offsetTopAndBottom((screenY - loc[1]) - overlayView.getTop()); 237 sceneRoot.getOverlay().add(overlayView); 238 // TODO: add automatic facility to Visibility superclass for keeping views around 239 final float startAlpha = view.getAlpha(); 240 float endAlpha = 0; 241 final View finalView = view; 242 final View finalOverlayView = overlayView; 243 final View finalViewToKeep = viewToKeep; 244 final ViewGroup finalSceneRoot = sceneRoot; 245 final AnimatorListenerAdapter endListener = new AnimatorListenerAdapter() { 246 @Override 247 public void onAnimationEnd(Animator animation) { 248 finalView.setAlpha(startAlpha); 249 // TODO: restore view offset from overlay repositioning 250 if (finalViewToKeep != null) { 251 finalViewToKeep.setVisibility(finalVisibility); 252 } 253 if (finalOverlayView != null) { 254 finalSceneRoot.getOverlay().remove(finalOverlayView); 255 } 256 } 257 258 @Override 259 public void onAnimationPause(Animator animation) { 260 if (finalOverlayView != null) { 261 finalSceneRoot.getOverlay().remove(finalOverlayView); 262 } 263 } 264 265 @Override 266 public void onAnimationResume(Animator animation) { 267 if (finalOverlayView != null) { 268 finalSceneRoot.getOverlay().add(finalOverlayView); 269 } 270 } 271 }; 272 return createAnimation(view, startAlpha, endAlpha, endListener); 273 } 274 if (viewToKeep != null) { 275 // TODO: find a different way to do this, like just changing the view to be 276 // VISIBLE for the duration of the transition 277 viewToKeep.setVisibility((View.VISIBLE)); 278 // TODO: add automatic facility to Visibility superclass for keeping views around 279 final float startAlpha = view.getAlpha(); 280 float endAlpha = 0; 281 final View finalView = view; 282 final View finalOverlayView = overlayView; 283 final View finalViewToKeep = viewToKeep; 284 final ViewGroup finalSceneRoot = sceneRoot; 285 final AnimatorListenerAdapter endListener = new AnimatorListenerAdapter() { 286 boolean mCanceled = false; 287 float mPausedAlpha = -1; 288 289 @Override 290 public void onAnimationPause(Animator animation) { 291 if (finalViewToKeep != null && !mCanceled) { 292 finalViewToKeep.setVisibility(finalVisibility); 293 } 294 mPausedAlpha = finalView.getAlpha(); 295 finalView.setAlpha(startAlpha); 296 } 297 298 @Override 299 public void onAnimationResume(Animator animation) { 300 if (finalViewToKeep != null && !mCanceled) { 301 finalViewToKeep.setVisibility(View.VISIBLE); 302 } 303 finalView.setAlpha(mPausedAlpha); 304 } 305 306 @Override 307 public void onAnimationCancel(Animator animation) { 308 mCanceled = true; 309 if (mPausedAlpha >= 0) { 310 finalView.setAlpha(mPausedAlpha); 311 } 312 } 313 314 @Override 315 public void onAnimationEnd(Animator animation) { 316 if (!mCanceled) { 317 finalView.setAlpha(startAlpha); 318 } 319 // TODO: restore view offset from overlay repositioning 320 if (finalViewToKeep != null && !mCanceled) { 321 finalViewToKeep.setVisibility(finalVisibility); 322 } 323 if (finalOverlayView != null) { 324 finalSceneRoot.getOverlay().remove(finalOverlayView); 325 } 326 } 327 }; 328 return createAnimation(view, startAlpha, endAlpha, endListener); 329 } 330 return null; 331 } 332 333}