• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2012 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 com.android.phone;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.ObjectAnimator;
22 import android.graphics.Canvas;
23 import android.graphics.drawable.BitmapDrawable;
24 import android.graphics.drawable.Drawable;
25 import android.graphics.drawable.LayerDrawable;
26 import android.util.Log;
27 import android.view.View;
28 import android.view.ViewPropertyAnimator;
29 import android.widget.ImageView;
30 
31 /**
32  * Utilities for Animation.
33  */
34 public class AnimationUtils {
35     private static final String LOG_TAG = AnimationUtils.class.getSimpleName();
36     /**
37      * Turn on when you're interested in fading animation. Intentionally untied from other debug
38      * settings.
39      */
40     private static final boolean FADE_DBG = false;
41 
42     /**
43      * Duration for animations in msec, which can be used with
44      * {@link ViewPropertyAnimator#setDuration(long)} for example.
45      */
46     public static final int ANIMATION_DURATION = 250;
47 
AnimationUtils()48     private AnimationUtils() {
49     }
50 
51     /**
52      * Simple Utility class that runs fading animations on specified views.
53      */
54     public static class Fade {
55 
56         // View tag that's set during the fade-out animation; see hide() and
57         // isFadingOut().
58         private static final int FADE_STATE_KEY = R.id.fadeState;
59         private static final String FADING_OUT = "fading_out";
60 
61         /**
62          * Sets the visibility of the specified view to View.VISIBLE and then
63          * fades it in. If the view is already visible (and not in the middle
64          * of a fade-out animation), this method will return without doing
65          * anything.
66          *
67          * @param view The view to be faded in
68          */
show(final View view)69         public static void show(final View view) {
70             if (FADE_DBG) log("Fade: SHOW view " + view + "...");
71             if (FADE_DBG) log("Fade: - visibility = " + view.getVisibility());
72             if ((view.getVisibility() != View.VISIBLE) || isFadingOut(view)) {
73                 view.animate().cancel();
74                 // ...and clear the FADE_STATE_KEY tag in case we just
75                 // canceled an in-progress fade-out animation.
76                 view.setTag(FADE_STATE_KEY, null);
77 
78                 view.setAlpha(0);
79                 view.setVisibility(View.VISIBLE);
80                 view.animate().setDuration(ANIMATION_DURATION);
81                 view.animate().alpha(1);
82                 if (FADE_DBG) log("Fade: ==> SHOW " + view
83                                   + " DONE.  Set visibility = " + View.VISIBLE);
84             } else {
85                 if (FADE_DBG) log("Fade: ==> Ignoring, already visible AND not fading out.");
86             }
87         }
88 
89         /**
90          * Fades out the specified view and then sets its visibility to the
91          * specified value (either View.INVISIBLE or View.GONE). If the view
92          * is not currently visibile, the method will return without doing
93          * anything.
94          *
95          * Note that *during* the fade-out the view itself will still have
96          * visibility View.VISIBLE, although the isFadingOut() method will
97          * return true (in case the UI code needs to detect this state.)
98          *
99          * @param view The view to be hidden
100          * @param visibility The value to which the view's visibility will be
101          *                   set after it fades out.
102          *                   Must be either View.INVISIBLE or View.GONE.
103          */
hide(final View view, final int visibility)104         public static void hide(final View view, final int visibility) {
105             if (FADE_DBG) log("Fade: HIDE view " + view + "...");
106             if (view.getVisibility() == View.VISIBLE &&
107                 (visibility == View.INVISIBLE || visibility == View.GONE)) {
108 
109                 // Use a view tag to mark this view as being in the middle
110                 // of a fade-out animation.
111                 view.setTag(FADE_STATE_KEY, FADING_OUT);
112 
113                 view.animate().cancel();
114                 view.animate().setDuration(ANIMATION_DURATION);
115                 view.animate().alpha(0f).setListener(new AnimatorListenerAdapter() {
116                     @Override
117                     public void onAnimationEnd(Animator animation) {
118                         view.setAlpha(1);
119                         view.setVisibility(visibility);
120                         view.animate().setListener(null);
121                         // ...and we're done with the fade-out, so clear the view tag.
122                         view.setTag(FADE_STATE_KEY, null);
123                         if (FADE_DBG) log("Fade: HIDE " + view
124                                 + " DONE.  Set visibility = " + visibility);
125                     }
126                 });
127             }
128         }
129 
130         /**
131          * @return true if the specified view is currently in the middle
132          * of a fade-out animation.  (During the fade-out, the view's
133          * visibility is still VISIBLE, although in many cases the UI
134          * should behave as if it's already invisible or gone.  This
135          * method allows the UI code to detect that state.)
136          *
137          * @see #hide(View, int)
138          */
isFadingOut(final View view)139         public static boolean isFadingOut(final View view) {
140             if (FADE_DBG) {
141                 log("Fade: isFadingOut view " + view + "...");
142                 log("Fade:   - getTag() returns: " + view.getTag(FADE_STATE_KEY));
143                 log("Fade:   - returning: " + (view.getTag(FADE_STATE_KEY) == FADING_OUT));
144             }
145             return (view.getTag(FADE_STATE_KEY) == FADING_OUT);
146         }
147 
148     }
149 
150     /**
151      * Drawable achieving cross-fade, just like TransitionDrawable. We can have
152      * call-backs via animator object (see also {@link CrossFadeDrawable#getAnimator()}).
153      */
154     private static class CrossFadeDrawable extends LayerDrawable {
155         private final ObjectAnimator mAnimator;
156 
CrossFadeDrawable(Drawable[] layers)157         public CrossFadeDrawable(Drawable[] layers) {
158             super(layers);
159             mAnimator = ObjectAnimator.ofInt(this, "crossFadeAlpha", 0xff, 0);
160         }
161 
162         private int mCrossFadeAlpha;
163 
164         /**
165          * This will be used from ObjectAnimator.
166          * Note: this method is protected by proguard.flags so that it won't be removed
167          * automatically.
168          */
169         @SuppressWarnings("unused")
setCrossFadeAlpha(int alpha)170         public void setCrossFadeAlpha(int alpha) {
171             mCrossFadeAlpha = alpha;
172             invalidateSelf();
173         }
174 
getAnimator()175         public ObjectAnimator getAnimator() {
176             return mAnimator;
177         }
178 
179         @Override
draw(Canvas canvas)180         public void draw(Canvas canvas) {
181             Drawable first = getDrawable(0);
182             Drawable second = getDrawable(1);
183 
184             if (mCrossFadeAlpha > 0) {
185                 first.setAlpha(mCrossFadeAlpha);
186                 first.draw(canvas);
187                 first.setAlpha(255);
188             }
189 
190             if (mCrossFadeAlpha < 0xff) {
191                 second.setAlpha(0xff - mCrossFadeAlpha);
192                 second.draw(canvas);
193                 second.setAlpha(0xff);
194             }
195         }
196     }
197 
newCrossFadeDrawable(Drawable first, Drawable second)198     private static CrossFadeDrawable newCrossFadeDrawable(Drawable first, Drawable second) {
199         Drawable[] layers = new Drawable[2];
200         layers[0] = first;
201         layers[1] = second;
202         return new CrossFadeDrawable(layers);
203     }
204 
205     /**
206      * Starts cross-fade animation using TransitionDrawable. Nothing will happen if "from" and "to"
207      * are the same.
208      */
startCrossFade( final ImageView imageView, final Drawable from, final Drawable to)209     public static void startCrossFade(
210             final ImageView imageView, final Drawable from, final Drawable to) {
211         // We skip the cross-fade when those two Drawables are equal, or they are BitmapDrawables
212         // pointing to the same Bitmap.
213         final boolean areSameImage = from.equals(to) ||
214                 ((from instanceof BitmapDrawable)
215                         && (to instanceof BitmapDrawable)
216                         && ((BitmapDrawable) from).getBitmap()
217                                 .equals(((BitmapDrawable) to).getBitmap()));
218         if (!areSameImage) {
219             if (FADE_DBG) {
220                 log("Start cross-fade animation for " + imageView
221                         + "(" + Integer.toHexString(from.hashCode()) + " -> "
222                         + Integer.toHexString(to.hashCode()) + ")");
223             }
224 
225             CrossFadeDrawable crossFadeDrawable = newCrossFadeDrawable(from, to);
226             ObjectAnimator animator = crossFadeDrawable.getAnimator();
227             imageView.setImageDrawable(crossFadeDrawable);
228             animator.setDuration(ANIMATION_DURATION);
229             animator.addListener(new AnimatorListenerAdapter() {
230                 @Override
231                 public void onAnimationStart(Animator animation) {
232                     if (FADE_DBG) {
233                         log("cross-fade animation start ("
234                                 + Integer.toHexString(from.hashCode()) + " -> "
235                                 + Integer.toHexString(to.hashCode()) + ")");
236                     }
237                 }
238 
239                 @Override
240                 public void onAnimationEnd(Animator animation) {
241                     if (FADE_DBG) {
242                         log("cross-fade animation ended ("
243                                 + Integer.toHexString(from.hashCode()) + " -> "
244                                 + Integer.toHexString(to.hashCode()) + ")");
245                     }
246                     animation.removeAllListeners();
247                     // Workaround for issue 6300562; this will force the drawable to the
248                     // resultant one regardless of animation glitch.
249                     imageView.setImageDrawable(to);
250                 }
251             });
252             animator.start();
253 
254             /* We could use TransitionDrawable here, but it may cause some weird animation in
255              * some corner cases. See issue 6300562
256              * TODO: decide which to be used in the long run. TransitionDrawable is old but system
257              * one. Ours uses new animation framework and thus have callback (great for testing),
258              * while no framework support for the exact class.
259 
260             Drawable[] layers = new Drawable[2];
261             layers[0] = from;
262             layers[1] = to;
263             TransitionDrawable transitionDrawable = new TransitionDrawable(layers);
264             imageView.setImageDrawable(transitionDrawable);
265             transitionDrawable.startTransition(ANIMATION_DURATION); */
266             imageView.setTag(to);
267         } else {
268             if (FADE_DBG) {
269                 log("*Not* start cross-fade. " + imageView);
270             }
271         }
272     }
273 
274     // Debugging / testing code
275 
log(String msg)276     private static void log(String msg) {
277         Log.d(LOG_TAG, msg);
278     }
279 }