blob: 69ce872a24fbc9c5e6eb685259fa86ddd0907f61 [file] [log] [blame]
Aurimas Liutikas93554f22022-04-19 16:51:35 -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 */
16
17package android.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;
27import android.graphics.Rect;
28import android.graphics.drawable.BitmapDrawable;
29import android.util.Log;
30import android.view.SurfaceView;
31import android.view.TextureView;
32import android.view.View;
33import android.view.ViewGroup;
34import android.view.ViewOverlay;
35
36import 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 */
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
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}