Chet Haase | faebd8f | 2012-05-18 14:17:57 -0700 | [diff] [blame] | 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 | */ |
Chet Haase | 6ebe3de | 2013-06-17 16:50:50 -0700 | [diff] [blame] | 16 | |
Chet Haase | faebd8f | 2012-05-18 14:17:57 -0700 | [diff] [blame] | 17 | package android.view.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; |
Chet Haase | c81a849 | 2013-07-12 12:54:38 -0700 | [diff] [blame] | 27 | import android.graphics.Color; |
Chet Haase | faebd8f | 2012-05-18 14:17:57 -0700 | [diff] [blame] | 28 | import android.graphics.Rect; |
| 29 | import android.graphics.drawable.BitmapDrawable; |
| 30 | import android.graphics.drawable.Drawable; |
| 31 | import android.util.Log; |
| 32 | import android.view.SurfaceView; |
| 33 | import android.view.TextureView; |
| 34 | import android.view.View; |
| 35 | import android.view.ViewGroup; |
Chet Haase | 4f0c467 | 2013-06-05 17:49:01 -0700 | [diff] [blame] | 36 | import android.view.ViewOverlay; |
Chet Haase | faebd8f | 2012-05-18 14:17:57 -0700 | [diff] [blame] | 37 | |
Chet Haase | 0873518 | 2013-06-04 10:44:40 -0700 | [diff] [blame] | 38 | import java.util.Map; |
Chet Haase | faebd8f | 2012-05-18 14:17:57 -0700 | [diff] [blame] | 39 | |
| 40 | /** |
| 41 | * This transition captures bitmap representations of target views before and |
| 42 | * after the scene change and fades between them. |
| 43 | * |
| 44 | * <p>Note: This transition is not compatible with {@link TextureView} |
| 45 | * or {@link SurfaceView}.</p> |
| 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 | |
Chet Haase | 4f0c467 | 2013-06-05 17:49:01 -0700 | [diff] [blame] | 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; |
Chet Haase | c81a849 | 2013-07-12 12:54:38 -0700 | [diff] [blame] | 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; |
Chet Haase | 4f0c467 | 2013-06-05 17:49:01 -0700 | [diff] [blame] | 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 void setFadeBehavior(int fadeBehavior) { |
Chet Haase | c81a849 | 2013-07-12 12:54:38 -0700 | [diff] [blame] | 125 | if (fadeBehavior >= FADE_BEHAVIOR_CROSSFADE && fadeBehavior <= FADE_BEHAVIOR_OUT_IN) { |
Chet Haase | 4f0c467 | 2013-06-05 17:49:01 -0700 | [diff] [blame] | 126 | mFadeBehavior = fadeBehavior; |
| 127 | } |
| 128 | } |
| 129 | |
| 130 | public int getFadeBehavior() { |
| 131 | return mFadeBehavior; |
| 132 | } |
| 133 | |
| 134 | /** |
| 135 | * Sets the type of resizing behavior that will be used during the |
| 136 | * transition animation, one of {@link #RESIZE_BEHAVIOR_NONE} and |
| 137 | * {@link #RESIZE_BEHAVIOR_SCALE}. |
| 138 | * |
| 139 | * @param resizeBehavior The type of resizing behavior to use when this |
| 140 | * transition is run. |
| 141 | */ |
| 142 | public void setResizeBehavior(int resizeBehavior) { |
| 143 | if (resizeBehavior >= RESIZE_BEHAVIOR_NONE && resizeBehavior <= RESIZE_BEHAVIOR_SCALE) { |
| 144 | mResizeBehavior = resizeBehavior; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | public int getResizeBehavior() { |
| 149 | return mResizeBehavior; |
| 150 | } |
| 151 | |
Chet Haase | faebd8f | 2012-05-18 14:17:57 -0700 | [diff] [blame] | 152 | @Override |
Chet Haase | 2ea7f8b | 2013-06-21 15:00:05 -0700 | [diff] [blame] | 153 | protected Animator play(ViewGroup sceneRoot, TransitionValues startValues, |
Chet Haase | faebd8f | 2012-05-18 14:17:57 -0700 | [diff] [blame] | 154 | TransitionValues endValues) { |
| 155 | if (startValues == null || endValues == null) { |
Chet Haase | 2ea7f8b | 2013-06-21 15:00:05 -0700 | [diff] [blame] | 156 | return null; |
Chet Haase | faebd8f | 2012-05-18 14:17:57 -0700 | [diff] [blame] | 157 | } |
Chet Haase | c81a849 | 2013-07-12 12:54:38 -0700 | [diff] [blame] | 158 | final boolean useParentOverlay = mFadeBehavior != FADE_BEHAVIOR_REVEAL; |
Chet Haase | 4f0c467 | 2013-06-05 17:49:01 -0700 | [diff] [blame] | 159 | final View view = endValues.view; |
Chet Haase | 0873518 | 2013-06-04 10:44:40 -0700 | [diff] [blame] | 160 | Map<String, Object> startVals = startValues.values; |
| 161 | Map<String, Object> endVals = endValues.values; |
Chet Haase | 2ea7f8b | 2013-06-21 15:00:05 -0700 | [diff] [blame] | 162 | Rect startBounds = (Rect) startVals.get(PROPNAME_BOUNDS); |
| 163 | Rect endBounds = (Rect) endVals.get(PROPNAME_BOUNDS); |
Chet Haase | faebd8f | 2012-05-18 14:17:57 -0700 | [diff] [blame] | 164 | Bitmap startBitmap = (Bitmap) startVals.get(PROPNAME_BITMAP); |
| 165 | Bitmap endBitmap = (Bitmap) endVals.get(PROPNAME_BITMAP); |
Chet Haase | 2ea7f8b | 2013-06-21 15:00:05 -0700 | [diff] [blame] | 166 | final BitmapDrawable startDrawable = (BitmapDrawable) startVals.get(PROPNAME_DRAWABLE); |
| 167 | final BitmapDrawable endDrawable = (BitmapDrawable) endVals.get(PROPNAME_DRAWABLE); |
Chet Haase | faebd8f | 2012-05-18 14:17:57 -0700 | [diff] [blame] | 168 | if (Transition.DBG) { |
| 169 | Log.d(LOG_TAG, "StartBitmap.sameAs(endBitmap) = " + startBitmap.sameAs(endBitmap) + |
| 170 | " for start, end: " + startBitmap + ", " + endBitmap); |
| 171 | } |
| 172 | if (startDrawable != null && endDrawable != null && !startBitmap.sameAs(endBitmap)) { |
Chet Haase | c81a849 | 2013-07-12 12:54:38 -0700 | [diff] [blame] | 173 | ViewOverlay overlay = useParentOverlay ? |
| 174 | ((ViewGroup) view.getParent()).getOverlay() : view.getOverlay(); |
Chet Haase | 4f0c467 | 2013-06-05 17:49:01 -0700 | [diff] [blame] | 175 | if (mFadeBehavior == FADE_BEHAVIOR_REVEAL) { |
| 176 | overlay.add(endDrawable); |
| 177 | } |
| 178 | overlay.add(startDrawable); |
Chet Haase | 2ea7f8b | 2013-06-21 15:00:05 -0700 | [diff] [blame] | 179 | // The transition works by placing the end drawable under the start drawable and |
| 180 | // gradually fading out the start drawable. So it's not really a cross-fade, but rather |
| 181 | // a reveal of the end scene over time. Also, animate the bounds of both drawables |
| 182 | // to mimic the change in the size of the view itself between scenes. |
Chet Haase | c81a849 | 2013-07-12 12:54:38 -0700 | [diff] [blame] | 183 | ObjectAnimator anim; |
| 184 | if (mFadeBehavior == FADE_BEHAVIOR_OUT_IN) { |
| 185 | // Fade out completely halfway through the transition |
| 186 | anim = ObjectAnimator.ofInt(startDrawable, "alpha", 255, 0, 0); |
| 187 | } else { |
| 188 | anim = ObjectAnimator.ofInt(startDrawable, "alpha", 0); |
| 189 | } |
Chet Haase | 2ea7f8b | 2013-06-21 15:00:05 -0700 | [diff] [blame] | 190 | anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { |
| 191 | @Override |
| 192 | public void onAnimationUpdate(ValueAnimator animation) { |
| 193 | // TODO: some way to auto-invalidate views based on drawable changes? callbacks? |
| 194 | view.invalidate(startDrawable.getBounds()); |
| 195 | } |
| 196 | }); |
| 197 | ObjectAnimator anim1 = null; |
Chet Haase | c81a849 | 2013-07-12 12:54:38 -0700 | [diff] [blame] | 198 | if (mFadeBehavior == FADE_BEHAVIOR_OUT_IN) { |
| 199 | // start fading in halfway through the transition |
| 200 | anim1 = ObjectAnimator.ofFloat(view, View.ALPHA, 0, 0, 1); |
| 201 | } else if (mFadeBehavior == FADE_BEHAVIOR_CROSSFADE) { |
Chet Haase | 2ea7f8b | 2013-06-21 15:00:05 -0700 | [diff] [blame] | 202 | anim1 = ObjectAnimator.ofFloat(view, View.ALPHA, 0, 1); |
Chet Haase | faebd8f | 2012-05-18 14:17:57 -0700 | [diff] [blame] | 203 | } |
Chet Haase | 2ea7f8b | 2013-06-21 15:00:05 -0700 | [diff] [blame] | 204 | if (Transition.DBG) { |
| 205 | Log.d(LOG_TAG, "Crossfade: created anim " + anim + " for start, end values " + |
| 206 | startValues + ", " + endValues); |
| 207 | } |
| 208 | anim.addListener(new AnimatorListenerAdapter() { |
| 209 | @Override |
| 210 | public void onAnimationEnd(Animator animation) { |
Chet Haase | c81a849 | 2013-07-12 12:54:38 -0700 | [diff] [blame] | 211 | ViewOverlay overlay = useParentOverlay ? |
| 212 | ((ViewGroup) view.getParent()).getOverlay() : view.getOverlay(); |
Chet Haase | 2ea7f8b | 2013-06-21 15:00:05 -0700 | [diff] [blame] | 213 | overlay.remove(startDrawable); |
| 214 | if (mFadeBehavior == FADE_BEHAVIOR_REVEAL) { |
| 215 | overlay.remove(endDrawable); |
| 216 | } |
| 217 | } |
| 218 | }); |
| 219 | AnimatorSet set = new AnimatorSet(); |
| 220 | set.playTogether(anim); |
| 221 | if (anim1 != null) { |
| 222 | set.playTogether(anim1); |
| 223 | } |
| 224 | if (mResizeBehavior == RESIZE_BEHAVIOR_SCALE && !startBounds.equals(endBounds)) { |
| 225 | if (Transition.DBG) { |
| 226 | Log.d(LOG_TAG, "animating from startBounds to endBounds: " + |
| 227 | startBounds + ", " + endBounds); |
| 228 | } |
| 229 | Animator anim2 = ObjectAnimator.ofObject(startDrawable, "bounds", |
| 230 | sRectEvaluator, startBounds, endBounds); |
| 231 | set.playTogether(anim2); |
| 232 | if (mResizeBehavior == RESIZE_BEHAVIOR_SCALE) { |
| 233 | // TODO: How to handle resizing with a CROSSFADE (vs. REVEAL) effect |
| 234 | // when we are animating the view directly? |
| 235 | Animator anim3 = ObjectAnimator.ofObject(endDrawable, "bounds", |
| 236 | sRectEvaluator, startBounds, endBounds); |
| 237 | set.playTogether(anim3); |
Chet Haase | 4f0c467 | 2013-06-05 17:49:01 -0700 | [diff] [blame] | 238 | } |
Chet Haase | faebd8f | 2012-05-18 14:17:57 -0700 | [diff] [blame] | 239 | } |
Chet Haase | 2ea7f8b | 2013-06-21 15:00:05 -0700 | [diff] [blame] | 240 | return set; |
| 241 | } else { |
| 242 | return null; |
Chet Haase | 4f0c467 | 2013-06-05 17:49:01 -0700 | [diff] [blame] | 243 | } |
Chet Haase | faebd8f | 2012-05-18 14:17:57 -0700 | [diff] [blame] | 244 | } |
| 245 | |
| 246 | @Override |
| 247 | protected void captureValues(TransitionValues values, boolean start) { |
| 248 | View view = values.view; |
Chet Haase | 4f0c467 | 2013-06-05 17:49:01 -0700 | [diff] [blame] | 249 | Rect bounds = new Rect(0, 0, view.getWidth(), view.getHeight()); |
Chet Haase | c81a849 | 2013-07-12 12:54:38 -0700 | [diff] [blame] | 250 | if (mFadeBehavior != FADE_BEHAVIOR_REVEAL) { |
Chet Haase | 4f0c467 | 2013-06-05 17:49:01 -0700 | [diff] [blame] | 251 | bounds.offset(view.getLeft(), view.getTop()); |
| 252 | } |
| 253 | values.values.put(PROPNAME_BOUNDS, bounds); |
Chet Haase | faebd8f | 2012-05-18 14:17:57 -0700 | [diff] [blame] | 254 | |
| 255 | if (Transition.DBG) { |
| 256 | Log.d(LOG_TAG, "Captured bounds " + values.values.get(PROPNAME_BOUNDS) + ": start = " + |
| 257 | start); |
| 258 | } |
| 259 | Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), |
| 260 | Bitmap.Config.ARGB_8888); |
| 261 | if (view instanceof TextureView) { |
| 262 | bitmap = ((TextureView) view).getBitmap(); |
| 263 | } else { |
| 264 | Canvas c = new Canvas(bitmap); |
| 265 | view.draw(c); |
| 266 | } |
| 267 | values.values.put(PROPNAME_BITMAP, bitmap); |
| 268 | // TODO: I don't have resources, can't call the non-deprecated method? |
| 269 | BitmapDrawable drawable = new BitmapDrawable(bitmap); |
| 270 | // TODO: lrtb will be wrong if the view has transXY set |
Chet Haase | 4f0c467 | 2013-06-05 17:49:01 -0700 | [diff] [blame] | 271 | drawable.setBounds(bounds); |
Chet Haase | faebd8f | 2012-05-18 14:17:57 -0700 | [diff] [blame] | 272 | values.values.put(PROPNAME_DRAWABLE, drawable); |
| 273 | } |
| 274 | |
| 275 | } |