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.AnimatorSet; 22 import android.animation.ObjectAnimator; 23 import android.animation.RectEvaluator; 24 import android.animation.ValueAnimator; 25 import android.graphics.Bitmap; 26 import android.graphics.Canvas; 27 import android.graphics.Rect; 28 import android.graphics.drawable.BitmapDrawable; 29 import android.util.Log; 30 import android.view.SurfaceView; 31 import android.view.TextureView; 32 import android.view.View; 33 import android.view.ViewGroup; 34 import android.view.ViewOverlay; 35 36 import java.util.Map; 37 38 /** 39 * This transition captures bitmap representations of target views before and 40 * after the scene change and fades between them. 41 * 42 * <p>Note: This transition is not compatible with {@link TextureView} 43 * or {@link SurfaceView}.</p> 44 * 45 * @hide 46 */ 47 public class Crossfade extends Transition { 48 // TODO: Add a hook that lets a Transition call user code to query whether it should run on 49 // a given target view. This would save bitmap comparisons in this transition, for example. 50 51 private static final String LOG_TAG = "Crossfade"; 52 53 private static final String PROPNAME_BITMAP = "android:crossfade:bitmap"; 54 private static final String PROPNAME_DRAWABLE = "android:crossfade:drawable"; 55 private static final String PROPNAME_BOUNDS = "android:crossfade:bounds"; 56 57 private static RectEvaluator sRectEvaluator = new RectEvaluator(); 58 59 private int mFadeBehavior = FADE_BEHAVIOR_REVEAL; 60 private int mResizeBehavior = RESIZE_BEHAVIOR_SCALE; 61 62 /** 63 * Flag specifying that the fading animation should cross-fade 64 * between the old and new representation of all affected target 65 * views. This means that the old representation will fade out 66 * while the new one fades in. This effect may work well on views 67 * without solid backgrounds, such as TextViews. 68 * 69 * @see #setFadeBehavior(int) 70 */ 71 public static final int FADE_BEHAVIOR_CROSSFADE = 0; 72 /** 73 * Flag specifying that the fading animation should reveal the 74 * new representation of all affected target views. This means 75 * that the old representation will fade out, gradually 76 * revealing the new representation, which remains opaque 77 * the whole time. This effect may work well on views 78 * with solid backgrounds, such as ImageViews. 79 * 80 * @see #setFadeBehavior(int) 81 */ 82 public static final int FADE_BEHAVIOR_REVEAL = 1; 83 /** 84 * Flag specifying that the fading animation should first fade 85 * out the original representation completely and then fade in the 86 * new one. This effect may be more suitable than the other 87 * fade behaviors for views with. 88 * 89 * @see #setFadeBehavior(int) 90 */ 91 public static final int FADE_BEHAVIOR_OUT_IN = 2; 92 93 /** 94 * Flag specifying that the transition should not animate any 95 * changes in size between the old and new target views. 96 * This means that no scaling will take place as a result of 97 * this transition 98 * 99 * @see #setResizeBehavior(int) 100 */ 101 public static final int RESIZE_BEHAVIOR_NONE = 0; 102 /** 103 * Flag specifying that the transition should animate any 104 * changes in size between the old and new target views. 105 * This means that the animation will scale the start/end 106 * representations of affected views from the starting size 107 * to the ending size over the course of the animation. 108 * This effect may work well on images, but is not recommended 109 * for text. 110 * 111 * @see #setResizeBehavior(int) 112 */ 113 public static final int RESIZE_BEHAVIOR_SCALE = 1; 114 115 // TODO: Add fade/resize behaviors to xml resources 116 117 /** 118 * Sets the type of fading animation that will be run, one of 119 * {@link #FADE_BEHAVIOR_CROSSFADE} and {@link #FADE_BEHAVIOR_REVEAL}. 120 * 121 * @param fadeBehavior The type of fading animation to use when this 122 * transition is run. 123 */ 124 public Crossfade setFadeBehavior(int fadeBehavior) { 125 if (fadeBehavior >= FADE_BEHAVIOR_CROSSFADE && fadeBehavior <= FADE_BEHAVIOR_OUT_IN) { 126 mFadeBehavior = fadeBehavior; 127 } 128 return this; 129 } 130 131 /** 132 * Returns the fading behavior of the animation. 133 * 134 * @return This crossfade object. 135 * @see #setFadeBehavior(int) 136 */ 137 public int getFadeBehavior() { 138 return mFadeBehavior; 139 } 140 141 /** 142 * Sets the type of resizing behavior that will be used during the 143 * transition animation, one of {@link #RESIZE_BEHAVIOR_NONE} and 144 * {@link #RESIZE_BEHAVIOR_SCALE}. 145 * 146 * @param resizeBehavior The type of resizing behavior to use when this 147 * transition is run. 148 */ 149 public Crossfade setResizeBehavior(int resizeBehavior) { 150 if (resizeBehavior >= RESIZE_BEHAVIOR_NONE && resizeBehavior <= RESIZE_BEHAVIOR_SCALE) { 151 mResizeBehavior = resizeBehavior; 152 } 153 return this; 154 } 155 156 /** 157 * Returns the resizing behavior of the animation. 158 * 159 * @return This crossfade object. 160 * @see #setResizeBehavior(int) 161 */ 162 public int getResizeBehavior() { 163 return mResizeBehavior; 164 } 165 166 @Override 167 public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, 168 TransitionValues endValues) { 169 if (startValues == null || endValues == null) { 170 return null; 171 } 172 final boolean useParentOverlay = mFadeBehavior != FADE_BEHAVIOR_REVEAL; 173 final View view = endValues.view; 174 Map<String, Object> startVals = startValues.values; 175 Map<String, Object> endVals = endValues.values; 176 Rect startBounds = (Rect) startVals.get(PROPNAME_BOUNDS); 177 Rect endBounds = (Rect) endVals.get(PROPNAME_BOUNDS); 178 Bitmap startBitmap = (Bitmap) startVals.get(PROPNAME_BITMAP); 179 Bitmap endBitmap = (Bitmap) endVals.get(PROPNAME_BITMAP); 180 final BitmapDrawable startDrawable = (BitmapDrawable) startVals.get(PROPNAME_DRAWABLE); 181 final BitmapDrawable endDrawable = (BitmapDrawable) endVals.get(PROPNAME_DRAWABLE); 182 if (Transition.DBG) { 183 Log.d(LOG_TAG, "StartBitmap.sameAs(endBitmap) = " + startBitmap.sameAs(endBitmap) + 184 " for start, end: " + startBitmap + ", " + endBitmap); 185 } 186 if (startDrawable != null && endDrawable != null && !startBitmap.sameAs(endBitmap)) { 187 ViewOverlay overlay = useParentOverlay ? 188 ((ViewGroup) view.getParent()).getOverlay() : view.getOverlay(); 189 if (mFadeBehavior == FADE_BEHAVIOR_REVEAL) { 190 overlay.add(endDrawable); 191 } 192 overlay.add(startDrawable); 193 // The transition works by placing the end drawable under the start drawable and 194 // gradually fading out the start drawable. So it's not really a cross-fade, but rather 195 // a reveal of the end scene over time. Also, animate the bounds of both drawables 196 // to mimic the change in the size of the view itself between scenes. 197 ObjectAnimator anim; 198 if (mFadeBehavior == FADE_BEHAVIOR_OUT_IN) { 199 // Fade out completely halfway through the transition 200 anim = ObjectAnimator.ofInt(startDrawable, "alpha", 255, 0, 0); 201 } else { 202 anim = ObjectAnimator.ofInt(startDrawable, "alpha", 0); 203 } 204 anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 205 @Override 206 public void onAnimationUpdate(ValueAnimator animation) { 207 // TODO: some way to auto-invalidate views based on drawable changes? callbacks? 208 view.invalidate(startDrawable.getBounds()); 209 } 210 }); 211 ObjectAnimator anim1 = null; 212 if (mFadeBehavior == FADE_BEHAVIOR_OUT_IN) { 213 // start fading in halfway through the transition 214 anim1 = ObjectAnimator.ofFloat(view, View.ALPHA, 0, 0, 1); 215 } else if (mFadeBehavior == FADE_BEHAVIOR_CROSSFADE) { 216 anim1 = ObjectAnimator.ofFloat(view, View.ALPHA, 0, 1); 217 } 218 if (Transition.DBG) { 219 Log.d(LOG_TAG, "Crossfade: created anim " + anim + " for start, end values " + 220 startValues + ", " + endValues); 221 } 222 anim.addListener(new AnimatorListenerAdapter() { 223 @Override 224 public void onAnimationEnd(Animator animation) { 225 ViewOverlay overlay = useParentOverlay ? 226 ((ViewGroup) view.getParent()).getOverlay() : view.getOverlay(); 227 overlay.remove(startDrawable); 228 if (mFadeBehavior == FADE_BEHAVIOR_REVEAL) { 229 overlay.remove(endDrawable); 230 } 231 } 232 }); 233 AnimatorSet set = new AnimatorSet(); 234 set.playTogether(anim); 235 if (anim1 != null) { 236 set.playTogether(anim1); 237 } 238 if (mResizeBehavior == RESIZE_BEHAVIOR_SCALE && !startBounds.equals(endBounds)) { 239 if (Transition.DBG) { 240 Log.d(LOG_TAG, "animating from startBounds to endBounds: " + 241 startBounds + ", " + endBounds); 242 } 243 Animator anim2 = ObjectAnimator.ofObject(startDrawable, "bounds", 244 sRectEvaluator, startBounds, endBounds); 245 set.playTogether(anim2); 246 if (mResizeBehavior == RESIZE_BEHAVIOR_SCALE) { 247 // TODO: How to handle resizing with a CROSSFADE (vs. REVEAL) effect 248 // when we are animating the view directly? 249 Animator anim3 = ObjectAnimator.ofObject(endDrawable, "bounds", 250 sRectEvaluator, startBounds, endBounds); 251 set.playTogether(anim3); 252 } 253 } 254 return set; 255 } else { 256 return null; 257 } 258 } 259 260 private void captureValues(TransitionValues transitionValues) { 261 View view = transitionValues.view; 262 Rect bounds = new Rect(0, 0, view.getWidth(), view.getHeight()); 263 if (mFadeBehavior != FADE_BEHAVIOR_REVEAL) { 264 bounds.offset(view.getLeft(), view.getTop()); 265 } 266 transitionValues.values.put(PROPNAME_BOUNDS, bounds); 267 268 if (Transition.DBG) { 269 Log.d(LOG_TAG, "Captured bounds " + transitionValues.values.get(PROPNAME_BOUNDS)); 270 } 271 Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), 272 Bitmap.Config.ARGB_8888); 273 if (view instanceof TextureView) { 274 bitmap = ((TextureView) view).getBitmap(); 275 } else { 276 Canvas c = new Canvas(bitmap); 277 view.draw(c); 278 } 279 transitionValues.values.put(PROPNAME_BITMAP, bitmap); 280 // TODO: I don't have resources, can't call the non-deprecated method? 281 BitmapDrawable drawable = new BitmapDrawable(bitmap); 282 // TODO: lrtb will be wrong if the view has transXY set 283 drawable.setBounds(bounds); 284 transitionValues.values.put(PROPNAME_DRAWABLE, drawable); 285 } 286 287 @Override 288 public void captureStartValues(TransitionValues transitionValues) { 289 captureValues(transitionValues); 290 } 291 292 @Override 293 public void captureEndValues(TransitionValues transitionValues) { 294 captureValues(transitionValues); 295 } 296 } 297