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 */
16package com.android.mail.bitmap;
17
18import android.animation.ValueAnimator;
19import android.animation.ValueAnimator.AnimatorUpdateListener;
20import android.content.res.Resources;
21import android.graphics.Bitmap;
22import android.graphics.BitmapFactory;
23import android.graphics.Canvas;
24import android.graphics.ColorFilter;
25import android.graphics.Matrix;
26import android.graphics.Paint;
27import android.graphics.PixelFormat;
28import android.graphics.Rect;
29import android.graphics.drawable.Drawable;
30
31import 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 */
37public 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