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.incallui; 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.view.ViewPropertyAnimator; 27 import android.widget.ImageView; 28 29 /** 30 * Utilities for Animation. 31 */ 32 public class InCallAnimationUtils { 33 private static final String LOG_TAG = InCallAnimationUtils.class.getSimpleName(); 34 /** 35 * Turn on when you're interested in fading animation. Intentionally untied from other debug 36 * settings. 37 */ 38 private static final boolean FADE_DBG = false; 39 40 /** 41 * Duration for animations in msec, which can be used with 42 * {@link ViewPropertyAnimator#setDuration(long)} for example. 43 */ 44 public static final int ANIMATION_DURATION = 250; 45 InCallAnimationUtils()46 private InCallAnimationUtils() { 47 } 48 49 /** 50 * Drawable achieving cross-fade, just like TransitionDrawable. We can have 51 * call-backs via animator object (see also {@link CrossFadeDrawable#getAnimator()}). 52 */ 53 private static class CrossFadeDrawable extends LayerDrawable { 54 private final ObjectAnimator mAnimator; 55 CrossFadeDrawable(Drawable[] layers)56 public CrossFadeDrawable(Drawable[] layers) { 57 super(layers); 58 mAnimator = ObjectAnimator.ofInt(this, "crossFadeAlpha", 0xff, 0); 59 } 60 61 private int mCrossFadeAlpha; 62 63 /** 64 * This will be used from ObjectAnimator. 65 * Note: this method is protected by proguard.flags so that it won't be removed 66 * automatically. 67 */ 68 @SuppressWarnings("unused") setCrossFadeAlpha(int alpha)69 public void setCrossFadeAlpha(int alpha) { 70 mCrossFadeAlpha = alpha; 71 invalidateSelf(); 72 } 73 getAnimator()74 public ObjectAnimator getAnimator() { 75 return mAnimator; 76 } 77 78 @Override draw(Canvas canvas)79 public void draw(Canvas canvas) { 80 Drawable first = getDrawable(0); 81 Drawable second = getDrawable(1); 82 83 if (mCrossFadeAlpha > 0) { 84 first.setAlpha(mCrossFadeAlpha); 85 first.draw(canvas); 86 first.setAlpha(255); 87 } 88 89 if (mCrossFadeAlpha < 0xff) { 90 second.setAlpha(0xff - mCrossFadeAlpha); 91 second.draw(canvas); 92 second.setAlpha(0xff); 93 } 94 } 95 } 96 newCrossFadeDrawable(Drawable first, Drawable second)97 private static CrossFadeDrawable newCrossFadeDrawable(Drawable first, Drawable second) { 98 Drawable[] layers = new Drawable[2]; 99 layers[0] = first; 100 layers[1] = second; 101 return new CrossFadeDrawable(layers); 102 } 103 104 /** 105 * Starts cross-fade animation using TransitionDrawable. Nothing will happen if "from" and "to" 106 * are the same. 107 */ startCrossFade( final ImageView imageView, final Drawable from, final Drawable to)108 public static void startCrossFade( 109 final ImageView imageView, final Drawable from, final Drawable to) { 110 // We skip the cross-fade when those two Drawables are equal, or they are BitmapDrawables 111 // pointing to the same Bitmap. 112 final boolean drawableIsEqual = (from != null && to != null && from.equals(to)); 113 final boolean hasFromImage = ((from instanceof BitmapDrawable) && 114 ((BitmapDrawable) from).getBitmap() != null); 115 final boolean hasToImage = ((to instanceof BitmapDrawable) && 116 ((BitmapDrawable) to).getBitmap() != null); 117 final boolean areSameImage = drawableIsEqual || (hasFromImage && hasToImage && 118 ((BitmapDrawable) from).getBitmap().equals(((BitmapDrawable) to).getBitmap())); 119 120 if (!areSameImage) { 121 if (FADE_DBG) { 122 log("Start cross-fade animation for " + imageView 123 + "(" + Integer.toHexString(from.hashCode()) + " -> " 124 + Integer.toHexString(to.hashCode()) + ")"); 125 } 126 127 CrossFadeDrawable crossFadeDrawable = newCrossFadeDrawable(from, to); 128 ObjectAnimator animator = crossFadeDrawable.getAnimator(); 129 imageView.setImageDrawable(crossFadeDrawable); 130 animator.setDuration(ANIMATION_DURATION); 131 animator.addListener(new AnimatorListenerAdapter() { 132 @Override 133 public void onAnimationStart(Animator animation) { 134 if (FADE_DBG) { 135 log("cross-fade animation start (" 136 + Integer.toHexString(from.hashCode()) + " -> " 137 + Integer.toHexString(to.hashCode()) + ")"); 138 } 139 } 140 141 @Override 142 public void onAnimationEnd(Animator animation) { 143 if (FADE_DBG) { 144 log("cross-fade animation ended (" 145 + Integer.toHexString(from.hashCode()) + " -> " 146 + Integer.toHexString(to.hashCode()) + ")"); 147 } 148 animation.removeAllListeners(); 149 // Workaround for issue 6300562; this will force the drawable to the 150 // resultant one regardless of animation glitch. 151 imageView.setImageDrawable(to); 152 } 153 }); 154 animator.start(); 155 156 /* We could use TransitionDrawable here, but it may cause some weird animation in 157 * some corner cases. See issue 6300562 158 * TODO: decide which to be used in the long run. TransitionDrawable is old but system 159 * one. Ours uses new animation framework and thus have callback (great for testing), 160 * while no framework support for the exact class. 161 162 Drawable[] layers = new Drawable[2]; 163 layers[0] = from; 164 layers[1] = to; 165 TransitionDrawable transitionDrawable = new TransitionDrawable(layers); 166 imageView.setImageDrawable(transitionDrawable); 167 transitionDrawable.startTransition(ANIMATION_DURATION); */ 168 imageView.setTag(to); 169 } else if (!hasFromImage && hasToImage) { 170 imageView.setImageDrawable(to); 171 imageView.setTag(to); 172 } else { 173 if (FADE_DBG) { 174 log("*Not* start cross-fade. " + imageView); 175 } 176 } 177 } 178 179 // Debugging / testing code 180 log(String msg)181 private static void log(String msg) { 182 Log.d(LOG_TAG, msg); 183 } 184 }