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 package com.android.mail.bitmap; 17 18 import android.animation.ValueAnimator; 19 import android.animation.ValueAnimator.AnimatorUpdateListener; 20 import android.content.res.Resources; 21 import android.graphics.Bitmap; 22 import android.graphics.BitmapFactory; 23 import android.graphics.Canvas; 24 import android.graphics.ColorFilter; 25 import android.graphics.Matrix; 26 import android.graphics.Paint; 27 import android.graphics.PixelFormat; 28 import android.graphics.Rect; 29 import android.graphics.drawable.Drawable; 30 31 import com.android.mail.R; 32 33 /** 34 * Custom FlipDrawable which has a {@link ContactDrawable} on the front, 35 * and a {@link CheckmarkDrawable} on the back. 36 */ 37 public class CheckableContactFlipDrawable extends FlipDrawable implements AnimatorUpdateListener { 38 39 private final ContactDrawable mContactDrawable; 40 private final CheckmarkDrawable mCheckmarkDrawable; 41 42 private final ValueAnimator mCheckmarkScaleAnimator; 43 private final ValueAnimator mCheckmarkAlphaAnimator; 44 45 private static final int POST_FLIP_DURATION_MS = 150; 46 47 private static final float CHECKMARK_SCALE_BEGIN_VALUE = 0.2f; 48 private static final float CHECKMARK_ALPHA_BEGIN_VALUE = 0f; 49 50 /** Must be <= 1f since the animation value is used as a percentage. */ 51 private static final float END_VALUE = 1f; 52 CheckableContactFlipDrawable(final Resources res, final int flipDurationMs)53 public CheckableContactFlipDrawable(final Resources res, final int flipDurationMs) { 54 super(new ContactDrawable(res), new CheckmarkDrawable(res), flipDurationMs, 55 0 /* preFlipDurationMs */, POST_FLIP_DURATION_MS); 56 57 mContactDrawable = (ContactDrawable) mFront; 58 mCheckmarkDrawable = (CheckmarkDrawable) mBack; 59 60 // We will create checkmark animations that are synchronized with the flipping animation. 61 // The entire delay + duration of the checkmark animation needs to equal the entire 62 // duration of the flip animation (where delay is 0). 63 64 // The checkmark animation is in effect only when the back drawable is being shown. 65 // For the flip animation duration <pre>[_][]|[][_]<post> 66 // The checkmark animation will be |--delay--|-duration-| 67 68 // Need delay to skip the first half of the flip duration. 69 final long animationDelay = mPreFlipDurationMs + mFlipDurationMs / 2; 70 // Actual duration is the second half of the flip duration. 71 final long animationDuration = mFlipDurationMs / 2 + mPostFlipDurationMs; 72 73 mCheckmarkScaleAnimator = ValueAnimator.ofFloat(CHECKMARK_SCALE_BEGIN_VALUE, END_VALUE) 74 .setDuration(animationDuration); 75 mCheckmarkScaleAnimator.setStartDelay(animationDelay); 76 mCheckmarkScaleAnimator.addUpdateListener(this); 77 78 mCheckmarkAlphaAnimator = ValueAnimator.ofFloat(CHECKMARK_ALPHA_BEGIN_VALUE, END_VALUE) 79 .setDuration(animationDuration); 80 mCheckmarkAlphaAnimator.setStartDelay(animationDelay); 81 mCheckmarkAlphaAnimator.addUpdateListener(this); 82 } 83 84 @Override reset(final boolean side)85 public void reset(final boolean side) { 86 super.reset(side); 87 if (mCheckmarkScaleAnimator == null) { 88 // Call from super's constructor. Not yet initialized. 89 return; 90 } 91 mCheckmarkScaleAnimator.cancel(); 92 mCheckmarkAlphaAnimator.cancel(); 93 mCheckmarkDrawable.setScaleAnimatorValue(side ? CHECKMARK_SCALE_BEGIN_VALUE : END_VALUE); 94 mCheckmarkDrawable.setAlphaAnimatorValue(side ? CHECKMARK_ALPHA_BEGIN_VALUE : END_VALUE); 95 } 96 97 @Override flip()98 public void flip() { 99 super.flip(); 100 // Keep the checkmark animators in sync with the flip animator. 101 if (mCheckmarkScaleAnimator.isStarted()) { 102 mCheckmarkScaleAnimator.reverse(); 103 mCheckmarkAlphaAnimator.reverse(); 104 } else { 105 if (!getSideFlippingTowards() /* front to back */) { 106 mCheckmarkScaleAnimator.start(); 107 mCheckmarkAlphaAnimator.start(); 108 } else /* back to front */ { 109 mCheckmarkScaleAnimator.reverse(); 110 mCheckmarkAlphaAnimator.reverse(); 111 } 112 } 113 } 114 getContactDrawable()115 public ContactDrawable getContactDrawable() { 116 return mContactDrawable; 117 } 118 119 @Override onAnimationUpdate(final ValueAnimator animation)120 public void onAnimationUpdate(final ValueAnimator animation) { 121 //noinspection ConstantConditions 122 final float value = (Float) animation.getAnimatedValue(); 123 124 if (animation == mCheckmarkScaleAnimator) { 125 mCheckmarkDrawable.setScaleAnimatorValue(value); 126 } else if (animation == mCheckmarkAlphaAnimator) { 127 mCheckmarkDrawable.setAlphaAnimatorValue(value); 128 } 129 } 130 131 /** 132 * Meant to be used as the with a FlipDrawable. The animator driving this Drawable should be 133 * more or less in sync with the containing FlipDrawable's flip animator. 134 */ 135 private static class CheckmarkDrawable extends Drawable { 136 137 private static Bitmap CHECKMARK; 138 private static int sBackgroundColor; 139 140 private final Paint mPaint; 141 142 private float mScaleFraction; 143 private float mAlphaFraction; 144 145 private static final Matrix sMatrix = new Matrix(); 146 CheckmarkDrawable(final Resources res)147 public CheckmarkDrawable(final Resources res) { 148 if (CHECKMARK == null) { 149 CHECKMARK = BitmapFactory.decodeResource(res, R.drawable.ic_check_wht_24dp); 150 sBackgroundColor = res.getColor(R.color.checkmark_tile_background_color); 151 } 152 mPaint = new Paint(); 153 mPaint.setAntiAlias(true); 154 mPaint.setFilterBitmap(true); 155 mPaint.setColor(sBackgroundColor); 156 } 157 158 @Override draw(final Canvas canvas)159 public void draw(final Canvas canvas) { 160 final Rect bounds = getBounds(); 161 if (!isVisible() || bounds.isEmpty()) { 162 return; 163 } 164 165 canvas.drawCircle(bounds.centerX(), bounds.centerY(), bounds.width() / 2, mPaint); 166 167 // Scale the checkmark. 168 sMatrix.reset(); 169 sMatrix.setScale(mScaleFraction, mScaleFraction, CHECKMARK.getWidth() / 2, 170 CHECKMARK.getHeight() / 2); 171 sMatrix.postTranslate(bounds.centerX() - CHECKMARK.getWidth() / 2, 172 bounds.centerY() - CHECKMARK.getHeight() / 2); 173 174 // Fade the checkmark. 175 final int oldAlpha = mPaint.getAlpha(); 176 // Interpolate the alpha. 177 mPaint.setAlpha((int) (oldAlpha * mAlphaFraction)); 178 canvas.drawBitmap(CHECKMARK, sMatrix, mPaint); 179 // Restore the alpha. 180 mPaint.setAlpha(oldAlpha); 181 } 182 183 @Override setAlpha(final int alpha)184 public void setAlpha(final int alpha) { 185 mPaint.setAlpha(alpha); 186 } 187 188 @Override setColorFilter(final ColorFilter cf)189 public void setColorFilter(final ColorFilter cf) { 190 mPaint.setColorFilter(cf); 191 } 192 193 @Override getOpacity()194 public int getOpacity() { 195 // Always a gray background. 196 return PixelFormat.OPAQUE; 197 } 198 199 /** 200 * Set value as a fraction from 0f to 1f. 201 */ setScaleAnimatorValue(final float value)202 public void setScaleAnimatorValue(final float value) { 203 final float old = mScaleFraction; 204 mScaleFraction = value; 205 if (old != mScaleFraction) { 206 invalidateSelf(); 207 } 208 } 209 210 /** 211 * Set value as a fraction from 0f to 1f. 212 */ setAlphaAnimatorValue(final float value)213 public void setAlphaAnimatorValue(final float value) { 214 final float old = mAlphaFraction; 215 mAlphaFraction = value; 216 if (old != mAlphaFraction) { 217 invalidateSelf(); 218 } 219 } 220 } 221 } 222