Home | History | Annotate | Download | only in bitmap
      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 
     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
     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
     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 
    115     public ContactDrawable getContactDrawable() {
    116         return mContactDrawable;
    117     }
    118 
    119     @Override
    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 
    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
    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
    184         public void setAlpha(final int alpha) {
    185             mPaint.setAlpha(alpha);
    186         }
    187 
    188         @Override
    189         public void setColorFilter(final ColorFilter cf) {
    190             mPaint.setColorFilter(cf);
    191         }
    192 
    193         @Override
    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          */
    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          */
    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