| 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; |
| |
| 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; |
| 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).setGhostModeEnabled(level <= 0); |
| } |
| |
| 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; |
| } |
| |
| @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; |
| } |
| } |