diff options
Diffstat (limited to 'InCallUI/src/com/android/incallui/AnimationUtils.java')
-rw-r--r-- | InCallUI/src/com/android/incallui/AnimationUtils.java | 279 |
1 files changed, 279 insertions, 0 deletions
diff --git a/InCallUI/src/com/android/incallui/AnimationUtils.java b/InCallUI/src/com/android/incallui/AnimationUtils.java new file mode 100644 index 000000000..2bf730cca --- /dev/null +++ b/InCallUI/src/com/android/incallui/AnimationUtils.java @@ -0,0 +1,279 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.incallui; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.graphics.Canvas; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; +import android.util.Log; +import android.view.View; +import android.view.ViewPropertyAnimator; +import android.widget.ImageView; + +/** + * Utilities for Animation. + */ +public class AnimationUtils { + private static final String LOG_TAG = AnimationUtils.class.getSimpleName(); + /** + * Turn on when you're interested in fading animation. Intentionally untied from other debug + * settings. + */ + private static final boolean FADE_DBG = false; + + /** + * Duration for animations in msec, which can be used with + * {@link ViewPropertyAnimator#setDuration(long)} for example. + */ + public static final int ANIMATION_DURATION = 250; + + private AnimationUtils() { + } + + /** + * Simple Utility class that runs fading animations on specified views. + */ + public static class Fade { + + // View tag that's set during the fade-out animation; see hide() and + // isFadingOut(). + private static final int FADE_STATE_KEY = R.id.fadeState; + private static final String FADING_OUT = "fading_out"; + + /** + * Sets the visibility of the specified view to View.VISIBLE and then + * fades it in. If the view is already visible (and not in the middle + * of a fade-out animation), this method will return without doing + * anything. + * + * @param view The view to be faded in + */ + public static void show(final View view) { + if (FADE_DBG) log("Fade: SHOW view " + view + "..."); + if (FADE_DBG) log("Fade: - visibility = " + view.getVisibility()); + if ((view.getVisibility() != View.VISIBLE) || isFadingOut(view)) { + view.animate().cancel(); + // ...and clear the FADE_STATE_KEY tag in case we just + // canceled an in-progress fade-out animation. + view.setTag(FADE_STATE_KEY, null); + + view.setAlpha(0); + view.setVisibility(View.VISIBLE); + view.animate().setDuration(ANIMATION_DURATION); + view.animate().alpha(1); + if (FADE_DBG) log("Fade: ==> SHOW " + view + + " DONE. Set visibility = " + View.VISIBLE); + } else { + if (FADE_DBG) log("Fade: ==> Ignoring, already visible AND not fading out."); + } + } + + /** + * Fades out the specified view and then sets its visibility to the + * specified value (either View.INVISIBLE or View.GONE). If the view + * is not currently visibile, the method will return without doing + * anything. + * + * Note that *during* the fade-out the view itself will still have + * visibility View.VISIBLE, although the isFadingOut() method will + * return true (in case the UI code needs to detect this state.) + * + * @param view The view to be hidden + * @param visibility The value to which the view's visibility will be + * set after it fades out. + * Must be either View.INVISIBLE or View.GONE. + */ + public static void hide(final View view, final int visibility) { + if (FADE_DBG) log("Fade: HIDE view " + view + "..."); + if (view.getVisibility() == View.VISIBLE && + (visibility == View.INVISIBLE || visibility == View.GONE)) { + + // Use a view tag to mark this view as being in the middle + // of a fade-out animation. + view.setTag(FADE_STATE_KEY, FADING_OUT); + + view.animate().cancel(); + view.animate().setDuration(ANIMATION_DURATION); + view.animate().alpha(0f).setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + view.setAlpha(1); + view.setVisibility(visibility); + view.animate().setListener(null); + // ...and we're done with the fade-out, so clear the view tag. + view.setTag(FADE_STATE_KEY, null); + if (FADE_DBG) log("Fade: HIDE " + view + + " DONE. Set visibility = " + visibility); + } + }); + } + } + + /** + * @return true if the specified view is currently in the middle + * of a fade-out animation. (During the fade-out, the view's + * visibility is still VISIBLE, although in many cases the UI + * should behave as if it's already invisible or gone. This + * method allows the UI code to detect that state.) + * + * @see #hide(View, int) + */ + public static boolean isFadingOut(final View view) { + if (FADE_DBG) { + log("Fade: isFadingOut view " + view + "..."); + log("Fade: - getTag() returns: " + view.getTag(FADE_STATE_KEY)); + log("Fade: - returning: " + (view.getTag(FADE_STATE_KEY) == FADING_OUT)); + } + return (view.getTag(FADE_STATE_KEY) == FADING_OUT); + } + + } + + /** + * Drawable achieving cross-fade, just like TransitionDrawable. We can have + * call-backs via animator object (see also {@link CrossFadeDrawable#getAnimator()}). + */ + private static class CrossFadeDrawable extends LayerDrawable { + private final ObjectAnimator mAnimator; + + public CrossFadeDrawable(Drawable[] layers) { + super(layers); + mAnimator = ObjectAnimator.ofInt(this, "crossFadeAlpha", 0xff, 0); + } + + private int mCrossFadeAlpha; + + /** + * This will be used from ObjectAnimator. + * Note: this method is protected by proguard.flags so that it won't be removed + * automatically. + */ + @SuppressWarnings("unused") + public void setCrossFadeAlpha(int alpha) { + mCrossFadeAlpha = alpha; + invalidateSelf(); + } + + public ObjectAnimator getAnimator() { + return mAnimator; + } + + @Override + public void draw(Canvas canvas) { + Drawable first = getDrawable(0); + Drawable second = getDrawable(1); + + if (mCrossFadeAlpha > 0) { + first.setAlpha(mCrossFadeAlpha); + first.draw(canvas); + first.setAlpha(255); + } + + if (mCrossFadeAlpha < 0xff) { + second.setAlpha(0xff - mCrossFadeAlpha); + second.draw(canvas); + second.setAlpha(0xff); + } + } + } + + private static CrossFadeDrawable newCrossFadeDrawable(Drawable first, Drawable second) { + Drawable[] layers = new Drawable[2]; + layers[0] = first; + layers[1] = second; + return new CrossFadeDrawable(layers); + } + + /** + * Starts cross-fade animation using TransitionDrawable. Nothing will happen if "from" and "to" + * are the same. + */ + public static void startCrossFade( + final ImageView imageView, final Drawable from, final Drawable to) { + // We skip the cross-fade when those two Drawables are equal, or they are BitmapDrawables + // pointing to the same Bitmap. + final boolean areSameImage = from.equals(to) || + ((from instanceof BitmapDrawable) + && (to instanceof BitmapDrawable) + && ((BitmapDrawable) from).getBitmap() + .equals(((BitmapDrawable) to).getBitmap())); + if (!areSameImage) { + if (FADE_DBG) { + log("Start cross-fade animation for " + imageView + + "(" + Integer.toHexString(from.hashCode()) + " -> " + + Integer.toHexString(to.hashCode()) + ")"); + } + + CrossFadeDrawable crossFadeDrawable = newCrossFadeDrawable(from, to); + ObjectAnimator animator = crossFadeDrawable.getAnimator(); + imageView.setImageDrawable(crossFadeDrawable); + animator.setDuration(ANIMATION_DURATION); + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + if (FADE_DBG) { + log("cross-fade animation start (" + + Integer.toHexString(from.hashCode()) + " -> " + + Integer.toHexString(to.hashCode()) + ")"); + } + } + + @Override + public void onAnimationEnd(Animator animation) { + if (FADE_DBG) { + log("cross-fade animation ended (" + + Integer.toHexString(from.hashCode()) + " -> " + + Integer.toHexString(to.hashCode()) + ")"); + } + animation.removeAllListeners(); + // Workaround for issue 6300562; this will force the drawable to the + // resultant one regardless of animation glitch. + imageView.setImageDrawable(to); + } + }); + animator.start(); + + /* We could use TransitionDrawable here, but it may cause some weird animation in + * some corner cases. See issue 6300562 + * TODO: decide which to be used in the long run. TransitionDrawable is old but system + * one. Ours uses new animation framework and thus have callback (great for testing), + * while no framework support for the exact class. + + Drawable[] layers = new Drawable[2]; + layers[0] = from; + layers[1] = to; + TransitionDrawable transitionDrawable = new TransitionDrawable(layers); + imageView.setImageDrawable(transitionDrawable); + transitionDrawable.startTransition(ANIMATION_DURATION); */ + imageView.setTag(to); + } else { + if (FADE_DBG) { + log("*Not* start cross-fade. " + imageView); + } + } + } + + // Debugging / testing code + + private static void log(String msg) { + Log.d(LOG_TAG, msg); + } +}
\ No newline at end of file |