blob: 18bb57fb4dd05aa697e1afba4a60d5458379f768 [file] [log] [blame]
Chet Haasefaebd8f2012-05-18 14:17:57 -07001/*
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 Haase6ebe3de2013-06-17 16:50:50 -070016
Chet Haasefaebd8f2012-05-18 14:17:57 -070017package android.view.transition;
18
19import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.AnimatorSet;
22import android.animation.ObjectAnimator;
23import android.animation.RectEvaluator;
24import android.animation.ValueAnimator;
25import android.graphics.Bitmap;
26import android.graphics.Canvas;
Chet Haasec81a8492013-07-12 12:54:38 -070027import android.graphics.Color;
Chet Haasefaebd8f2012-05-18 14:17:57 -070028import android.graphics.Rect;
29import android.graphics.drawable.BitmapDrawable;
30import android.graphics.drawable.Drawable;
31import android.util.Log;
32import android.view.SurfaceView;
33import android.view.TextureView;
34import android.view.View;
35import android.view.ViewGroup;
Chet Haase4f0c4672013-06-05 17:49:01 -070036import android.view.ViewOverlay;
Chet Haasefaebd8f2012-05-18 14:17:57 -070037
Chet Haase08735182013-06-04 10:44:40 -070038import java.util.Map;
Chet Haasefaebd8f2012-05-18 14:17:57 -070039
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 */
47public 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 Haase4f0c4672013-06-05 17:49:01 -070059 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 Haasec81a8492013-07-12 12:54:38 -070083 /**
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 Haase4f0c4672013-06-05 17:49:01 -070092
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 Haasec81a8492013-07-12 12:54:38 -0700125 if (fadeBehavior >= FADE_BEHAVIOR_CROSSFADE && fadeBehavior <= FADE_BEHAVIOR_OUT_IN) {
Chet Haase4f0c4672013-06-05 17:49:01 -0700126 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 Haasefaebd8f2012-05-18 14:17:57 -0700152 @Override
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700153 protected Animator play(ViewGroup sceneRoot, TransitionValues startValues,
Chet Haasefaebd8f2012-05-18 14:17:57 -0700154 TransitionValues endValues) {
155 if (startValues == null || endValues == null) {
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700156 return null;
Chet Haasefaebd8f2012-05-18 14:17:57 -0700157 }
Chet Haasec81a8492013-07-12 12:54:38 -0700158 final boolean useParentOverlay = mFadeBehavior != FADE_BEHAVIOR_REVEAL;
Chet Haase4f0c4672013-06-05 17:49:01 -0700159 final View view = endValues.view;
Chet Haase08735182013-06-04 10:44:40 -0700160 Map<String, Object> startVals = startValues.values;
161 Map<String, Object> endVals = endValues.values;
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700162 Rect startBounds = (Rect) startVals.get(PROPNAME_BOUNDS);
163 Rect endBounds = (Rect) endVals.get(PROPNAME_BOUNDS);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700164 Bitmap startBitmap = (Bitmap) startVals.get(PROPNAME_BITMAP);
165 Bitmap endBitmap = (Bitmap) endVals.get(PROPNAME_BITMAP);
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700166 final BitmapDrawable startDrawable = (BitmapDrawable) startVals.get(PROPNAME_DRAWABLE);
167 final BitmapDrawable endDrawable = (BitmapDrawable) endVals.get(PROPNAME_DRAWABLE);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700168 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 Haasec81a8492013-07-12 12:54:38 -0700173 ViewOverlay overlay = useParentOverlay ?
174 ((ViewGroup) view.getParent()).getOverlay() : view.getOverlay();
Chet Haase4f0c4672013-06-05 17:49:01 -0700175 if (mFadeBehavior == FADE_BEHAVIOR_REVEAL) {
176 overlay.add(endDrawable);
177 }
178 overlay.add(startDrawable);
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700179 // 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 Haasec81a8492013-07-12 12:54:38 -0700183 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 Haase2ea7f8b2013-06-21 15:00:05 -0700190 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 Haasec81a8492013-07-12 12:54:38 -0700198 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 Haase2ea7f8b2013-06-21 15:00:05 -0700202 anim1 = ObjectAnimator.ofFloat(view, View.ALPHA, 0, 1);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700203 }
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700204 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 Haasec81a8492013-07-12 12:54:38 -0700211 ViewOverlay overlay = useParentOverlay ?
212 ((ViewGroup) view.getParent()).getOverlay() : view.getOverlay();
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700213 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 Haase4f0c4672013-06-05 17:49:01 -0700238 }
Chet Haasefaebd8f2012-05-18 14:17:57 -0700239 }
Chet Haase2ea7f8b2013-06-21 15:00:05 -0700240 return set;
241 } else {
242 return null;
Chet Haase4f0c4672013-06-05 17:49:01 -0700243 }
Chet Haasefaebd8f2012-05-18 14:17:57 -0700244 }
245
246 @Override
247 protected void captureValues(TransitionValues values, boolean start) {
248 View view = values.view;
Chet Haase4f0c4672013-06-05 17:49:01 -0700249 Rect bounds = new Rect(0, 0, view.getWidth(), view.getHeight());
Chet Haasec81a8492013-07-12 12:54:38 -0700250 if (mFadeBehavior != FADE_BEHAVIOR_REVEAL) {
Chet Haase4f0c4672013-06-05 17:49:01 -0700251 bounds.offset(view.getLeft(), view.getTop());
252 }
253 values.values.put(PROPNAME_BOUNDS, bounds);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700254
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 Haase4f0c4672013-06-05 17:49:01 -0700271 drawable.setBounds(bounds);
Chet Haasefaebd8f2012-05-18 14:17:57 -0700272 values.values.put(PROPNAME_DRAWABLE, drawable);
273 }
274
275}