blob: 134439da51258252e877c4569347d0159cb821fe [file] [log] [blame]
/*
* 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();
}
}
}
}