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