/* * Copyright (C) 2013 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.mail.bitmap; import android.animation.ValueAnimator; import android.animation.ValueAnimator.AnimatorUpdateListener; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.drawable.Drawable; import com.android.mail.R; /** * Custom FlipDrawable which has a {@link ContactDrawable} on the front, * and a {@link CheckmarkDrawable} on the back. */ public class CheckableContactFlipDrawable extends FlipDrawable implements AnimatorUpdateListener { private final ContactDrawable mContactDrawable; private final CheckmarkDrawable mCheckmarkDrawable; private final ValueAnimator mCheckmarkScaleAnimator; private final ValueAnimator mCheckmarkAlphaAnimator; private static final int POST_FLIP_DURATION_MS = 150; private static final float CHECKMARK_SCALE_BEGIN_VALUE = 0.2f; private static final float CHECKMARK_ALPHA_BEGIN_VALUE = 0f; /** Must be <= 1f since the animation value is used as a percentage. */ private static final float END_VALUE = 1f; public CheckableContactFlipDrawable(final Resources res, final int flipDurationMs) { super(new ContactDrawable(res), new CheckmarkDrawable(res), flipDurationMs, 0 /* preFlipDurationMs */, POST_FLIP_DURATION_MS); mContactDrawable = (ContactDrawable) mFront; mCheckmarkDrawable = (CheckmarkDrawable) mBack; // We will create checkmark animations that are synchronized with the flipping animation. // The entire delay + duration of the checkmark animation needs to equal the entire // duration of the flip animation (where delay is 0). // The checkmark animation is in effect only when the back drawable is being shown. // For the flip animation duration
[_][]|[][_]
        // The checkmark animation will be    |--delay--|-duration-|

        // Need delay to skip the first half of the flip duration.
        final long animationDelay = mPreFlipDurationMs + mFlipDurationMs / 2;
        // Actual duration is the second half of the flip duration.
        final long animationDuration = mFlipDurationMs / 2 + mPostFlipDurationMs;

        mCheckmarkScaleAnimator = ValueAnimator.ofFloat(CHECKMARK_SCALE_BEGIN_VALUE, END_VALUE)
                .setDuration(animationDuration);
        mCheckmarkScaleAnimator.setStartDelay(animationDelay);
        mCheckmarkScaleAnimator.addUpdateListener(this);

        mCheckmarkAlphaAnimator = ValueAnimator.ofFloat(CHECKMARK_ALPHA_BEGIN_VALUE, END_VALUE)
                .setDuration(animationDuration);
        mCheckmarkAlphaAnimator.setStartDelay(animationDelay);
        mCheckmarkAlphaAnimator.addUpdateListener(this);
    }

    @Override
    public void reset(final boolean side) {
        super.reset(side);
        if (mCheckmarkScaleAnimator == null) {
            // Call from super's constructor. Not yet initialized.
            return;
        }
        mCheckmarkScaleAnimator.cancel();
        mCheckmarkAlphaAnimator.cancel();
        mCheckmarkDrawable.setScaleAnimatorValue(side ? CHECKMARK_SCALE_BEGIN_VALUE : END_VALUE);
        mCheckmarkDrawable.setAlphaAnimatorValue(side ? CHECKMARK_ALPHA_BEGIN_VALUE : END_VALUE);
    }

    @Override
    public void flip() {
        super.flip();
        // Keep the checkmark animators in sync with the flip animator.
        if (mCheckmarkScaleAnimator.isStarted()) {
            mCheckmarkScaleAnimator.reverse();
            mCheckmarkAlphaAnimator.reverse();
        } else {
            if (!getSideFlippingTowards() /* front to back */) {
                mCheckmarkScaleAnimator.start();
                mCheckmarkAlphaAnimator.start();
            } else /* back to front */ {
                mCheckmarkScaleAnimator.reverse();
                mCheckmarkAlphaAnimator.reverse();
            }
        }
    }

    public ContactDrawable getContactDrawable() {
        return mContactDrawable;
    }

    @Override
    public void onAnimationUpdate(final ValueAnimator animation) {
        //noinspection ConstantConditions
        final float value = (Float) animation.getAnimatedValue();

        if (animation == mCheckmarkScaleAnimator) {
            mCheckmarkDrawable.setScaleAnimatorValue(value);
        } else if (animation == mCheckmarkAlphaAnimator) {
            mCheckmarkDrawable.setAlphaAnimatorValue(value);
        }
    }

    /**
     * Meant to be used as the with a FlipDrawable. The animator driving this Drawable should be
     * more or less in sync with the containing FlipDrawable's flip animator.
     */
    private static class CheckmarkDrawable extends Drawable {

        private static Bitmap CHECKMARK;
        private static int sBackgroundColor;

        private final Paint mPaint;

        private float mScaleFraction;
        private float mAlphaFraction;

        private static final Matrix sMatrix = new Matrix();

        public CheckmarkDrawable(final Resources res) {
            if (CHECKMARK == null) {
                CHECKMARK = BitmapFactory.decodeResource(res, R.drawable.ic_check_wht_24dp);
                sBackgroundColor = res.getColor(R.color.checkmark_tile_background_color);
            }
            mPaint = new Paint();
            mPaint.setAntiAlias(true);
            mPaint.setFilterBitmap(true);
            mPaint.setColor(sBackgroundColor);
        }

        @Override
        public void draw(final Canvas canvas) {
            final Rect bounds = getBounds();
            if (!isVisible() || bounds.isEmpty()) {
                return;
            }

            canvas.drawCircle(bounds.centerX(), bounds.centerY(), bounds.width() / 2, mPaint);

            // Scale the checkmark.
            sMatrix.reset();
            sMatrix.setScale(mScaleFraction, mScaleFraction, CHECKMARK.getWidth() / 2,
                    CHECKMARK.getHeight() / 2);
            sMatrix.postTranslate(bounds.centerX() - CHECKMARK.getWidth() / 2,
                    bounds.centerY() - CHECKMARK.getHeight() / 2);

            // Fade the checkmark.
            final int oldAlpha = mPaint.getAlpha();
            // Interpolate the alpha.
            mPaint.setAlpha((int) (oldAlpha * mAlphaFraction));
            canvas.drawBitmap(CHECKMARK, sMatrix, mPaint);
            // Restore the alpha.
            mPaint.setAlpha(oldAlpha);
        }

        @Override
        public void setAlpha(final int alpha) {
            mPaint.setAlpha(alpha);
        }

        @Override
        public void setColorFilter(final ColorFilter cf) {
            mPaint.setColorFilter(cf);
        }

        @Override
        public int getOpacity() {
            // Always a gray background.
            return PixelFormat.OPAQUE;
        }

        /**
         * Set value as a fraction from 0f to 1f.
         */
        public void setScaleAnimatorValue(final float value) {
            final float old = mScaleFraction;
            mScaleFraction = value;
            if (old != mScaleFraction) {
                invalidateSelf();
            }
        }

        /**
         * Set value as a fraction from 0f to 1f.
         */
        public void setAlphaAnimatorValue(final float value) {
            final float old = mAlphaFraction;
            mAlphaFraction = value;
            if (old != mAlphaFraction) {
                invalidateSelf();
            }
        }
    }
}