blob: b064c47fc72389f995d59fb758b97f3dbb460bea [file] [log] [blame]
package com.android.launcher3;
import android.animation.ObjectAnimator;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
public class PreloadIconDrawable extends Drawable {
private static final float ANIMATION_PROGRESS_STOPPED = -1.0f;
private static final float ANIMATION_PROGRESS_STARTED = 0f;
private static final float ANIMATION_PROGRESS_COMPLETED = 1.0f;
private static final float MIN_SATUNATION = 0.2f;
private static final float MIN_LIGHTNESS = 0.6f;
private static final float ICON_SCALE_FACTOR = 0.5f;
private static final int DEFAULT_COLOR = 0xFF009688;
private static final Rect sTempRect = new Rect();
private final RectF mIndicatorRect = new RectF();
private boolean mIndicatorRectDirty;
private final Paint mPaint;
public final Drawable mIcon;
private Drawable mBgDrawable;
private int mRingOutset;
private int mIndicatorColor = 0;
/**
* Indicates the progress of the preloader [0-100]. If it goes above 100, only the icon
* is shown with no progress bar.
*/
private int mProgress = 0;
private float mAnimationProgress = ANIMATION_PROGRESS_STOPPED;
private ObjectAnimator mAnimator;
public PreloadIconDrawable(Drawable icon, Theme theme) {
mIcon = icon;
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeCap(Paint.Cap.ROUND);
setBounds(icon.getBounds());
applyPreloaderTheme(theme);
onLevelChange(0);
}
public void applyPreloaderTheme(Theme t) {
TypedArray ta = t.obtainStyledAttributes(R.styleable.PreloadIconDrawable);
mBgDrawable = ta.getDrawable(R.styleable.PreloadIconDrawable_background);
mBgDrawable.setFilterBitmap(true);
mPaint.setStrokeWidth(ta.getDimension(R.styleable.PreloadIconDrawable_indicatorSize, 0));
mRingOutset = ta.getDimensionPixelSize(R.styleable.PreloadIconDrawable_ringOutset, 0);
ta.recycle();
onBoundsChange(getBounds());
invalidateSelf();
}
@Override
protected void onBoundsChange(Rect bounds) {
mIcon.setBounds(bounds);
if (mBgDrawable != null) {
sTempRect.set(bounds);
sTempRect.inset(-mRingOutset, -mRingOutset);
mBgDrawable.setBounds(sTempRect);
}
mIndicatorRectDirty = true;
}
public int getOutset() {
return mRingOutset;
}
/**
* The size of the indicator is same as the content region of the {@link #mBgDrawable} minus
* half the stroke size to accommodate the indicator.
*/
private void initIndicatorRect() {
Drawable d = mBgDrawable;
Rect bounds = d.getBounds();
d.getPadding(sTempRect);
// Amount by which padding has to be scaled
float paddingScaleX = ((float) bounds.width()) / d.getIntrinsicWidth();
float paddingScaleY = ((float) bounds.height()) / d.getIntrinsicHeight();
mIndicatorRect.set(
bounds.left + sTempRect.left * paddingScaleX,
bounds.top + sTempRect.top * paddingScaleY,
bounds.right - sTempRect.right * paddingScaleX,
bounds.bottom - sTempRect.bottom * paddingScaleY);
float inset = mPaint.getStrokeWidth() / 2;
mIndicatorRect.inset(inset, inset);
mIndicatorRectDirty = false;
}
@Override
public void draw(Canvas canvas) {
final Rect r = new Rect(getBounds());
if (canvas.getClipBounds(sTempRect) && !Rect.intersects(sTempRect, r)) {
// The draw region has been clipped.
return;
}
if (mIndicatorRectDirty) {
initIndicatorRect();
}
final float iconScale;
if ((mAnimationProgress >= ANIMATION_PROGRESS_STARTED)
&& (mAnimationProgress < ANIMATION_PROGRESS_COMPLETED)) {
mPaint.setAlpha((int) ((1 - mAnimationProgress) * 255));
mBgDrawable.setAlpha(mPaint.getAlpha());
mBgDrawable.draw(canvas);
canvas.drawOval(mIndicatorRect, mPaint);
iconScale = ICON_SCALE_FACTOR + (1 - ICON_SCALE_FACTOR) * mAnimationProgress;
} else if (mAnimationProgress == ANIMATION_PROGRESS_STOPPED) {
mPaint.setAlpha(255);
iconScale = ICON_SCALE_FACTOR;
mBgDrawable.setAlpha(255);
mBgDrawable.draw(canvas);
if (mProgress >= 100) {
canvas.drawOval(mIndicatorRect, mPaint);
} else if (mProgress > 0) {
canvas.drawArc(mIndicatorRect, -90, mProgress * 3.6f, false, mPaint);
}
} else {
iconScale = 1;
}
canvas.save();
canvas.scale(iconScale, iconScale, r.exactCenterX(), r.exactCenterY());
mIcon.draw(canvas);
canvas.restore();
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
@Override
public void setAlpha(int alpha) {
mIcon.setAlpha(alpha);
}
@Override
public void setColorFilter(ColorFilter cf) {
mIcon.setColorFilter(cf);
}
@Override
protected boolean onLevelChange(int level) {
mProgress = level;
// Stop Animation
if (mAnimator != null) {
mAnimator.cancel();
mAnimator = null;
}
mAnimationProgress = ANIMATION_PROGRESS_STOPPED;
if (level > 0) {
// Set the paint color only when the level changes, so that the dominant color
// is only calculated when needed.
mPaint.setColor(getIndicatorColor());
}
if (mIcon instanceof FastBitmapDrawable) {
((FastBitmapDrawable) mIcon).setState(level <= 0 ?
FastBitmapDrawable.State.DISABLED : FastBitmapDrawable.State.NORMAL);
}
invalidateSelf();
return true;
}
/**
* Runs the finish animation if it is has not been run after last level change.
*/
public void maybePerformFinishedAnimation() {
if (mAnimationProgress > ANIMATION_PROGRESS_STOPPED) {
return;
}
if (mAnimator != null) {
mAnimator.cancel();
}
setAnimationProgress(ANIMATION_PROGRESS_STARTED);
mAnimator = ObjectAnimator.ofFloat(this, "animationProgress",
ANIMATION_PROGRESS_STARTED, ANIMATION_PROGRESS_COMPLETED);
mAnimator.start();
}
public void setAnimationProgress(float progress) {
if (progress != mAnimationProgress) {
mAnimationProgress = progress;
invalidateSelf();
}
}
public float getAnimationProgress() {
return mAnimationProgress;
}
public boolean hasNotCompleted() {
return mAnimationProgress < ANIMATION_PROGRESS_COMPLETED;
}
@Override
public int getIntrinsicHeight() {
return mIcon.getIntrinsicHeight();
}
@Override
public int getIntrinsicWidth() {
return mIcon.getIntrinsicWidth();
}
private int getIndicatorColor() {
if (mIndicatorColor != 0) {
return mIndicatorColor;
}
if (!(mIcon instanceof FastBitmapDrawable)) {
mIndicatorColor = DEFAULT_COLOR;
return mIndicatorColor;
}
mIndicatorColor = Utilities.findDominantColorByHue(
((FastBitmapDrawable) mIcon).getBitmap(), 20);
// Make sure that the dominant color has enough saturation to be visible properly.
float[] hsv = new float[3];
Color.colorToHSV(mIndicatorColor, hsv);
if (hsv[1] < MIN_SATUNATION) {
mIndicatorColor = DEFAULT_COLOR;
return mIndicatorColor;
}
hsv[2] = Math.max(MIN_LIGHTNESS, hsv[2]);
mIndicatorColor = Color.HSVToColor(hsv);
return mIndicatorColor;
}
}