| /* |
| * 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.bitmap.BitmapCache; |
| import com.android.mail.R.color; |
| import com.android.mail.R.drawable; |
| |
| /** |
| * Custom FlipDrawable which has a {@link ContactGridDrawable} on the front, |
| * and a {@link CheckmarkDrawable} on the back. |
| */ |
| public class ContactCheckableGridDrawable extends FlipDrawable implements AnimatorUpdateListener { |
| |
| private final ContactGridDrawable mContactGridDrawable; |
| 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 ContactCheckableGridDrawable(final Resources res, final int flipDurationMs) { |
| super(new ContactGridDrawable(res), new CheckmarkDrawable(res), flipDurationMs, |
| 0 /* preFlipDurationMs */, POST_FLIP_DURATION_MS); |
| |
| mContactGridDrawable = (ContactGridDrawable) 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 <pre>[_][]|[][_]<post> |
| // 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 getOrCreateDrawable(final int i) { |
| return mContactGridDrawable.getOrCreateDrawable(i); |
| } |
| |
| public void setBitmapCache(final BitmapCache cache) { |
| mContactGridDrawable.setBitmapCache(cache); |
| } |
| |
| public void setContactResolver(final ContactResolver contactResolver) { |
| mContactGridDrawable.setContactResolver(contactResolver); |
| } |
| |
| public int getCount() { |
| return mContactGridDrawable.getCount(); |
| } |
| |
| public void setCount(final int count) { |
| mContactGridDrawable.setCount(count); |
| // Side effect needs to happen here too. |
| setBounds(0, 0, 0, 0); |
| } |
| |
| @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, drawable.ic_avatar_check); |
| sBackgroundColor = res.getColor(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.drawRect(getBounds(), 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(); |
| } |
| } |
| } |
| } |