blob: f7d9e2e1fac59b508f16df329b4691aa926b1032 [file] [log] [blame]
Santos Cordon7d4ddf62013-07-10 11:58:08 -07001/*
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
17package com.android.phone;
18
19import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.ObjectAnimator;
22import android.graphics.Canvas;
23import android.graphics.drawable.BitmapDrawable;
24import android.graphics.drawable.Drawable;
25import android.graphics.drawable.LayerDrawable;
26import android.util.Log;
27import android.view.View;
28import android.view.ViewPropertyAnimator;
29import android.widget.ImageView;
30
31/**
32 * Utilities for Animation.
33 */
34public 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
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 */
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 */
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 */
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
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")
170 public void setCrossFadeAlpha(int alpha) {
171 mCrossFadeAlpha = alpha;
172 invalidateSelf();
173 }
174
175 public ObjectAnimator getAnimator() {
176 return mAnimator;
177 }
178
179 @Override
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
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 */
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
276 private static void log(String msg) {
277 Log.d(LOG_TAG, msg);
278 }
279}