Home | History | Annotate | Download | only in launcher3
      1 package com.android.launcher3;
      2 
      3 import android.animation.ObjectAnimator;
      4 import android.content.res.Resources.Theme;
      5 import android.content.res.TypedArray;
      6 import android.graphics.Canvas;
      7 import android.graphics.Color;
      8 import android.graphics.ColorFilter;
      9 import android.graphics.Paint;
     10 import android.graphics.PixelFormat;
     11 import android.graphics.Rect;
     12 import android.graphics.RectF;
     13 import android.graphics.drawable.Drawable;
     14 
     15 public class PreloadIconDrawable extends Drawable {
     16 
     17     private static final float ANIMATION_PROGRESS_STOPPED = -1.0f;
     18     private static final float ANIMATION_PROGRESS_STARTED = 0f;
     19     private static final float ANIMATION_PROGRESS_COMPLETED = 1.0f;
     20 
     21     private static final float MIN_SATUNATION = 0.2f;
     22     private static final float MIN_LIGHTNESS = 0.6f;
     23 
     24     private static final float ICON_SCALE_FACTOR = 0.5f;
     25     private static final int DEFAULT_COLOR = 0xFF009688;
     26 
     27     private static final Rect sTempRect = new Rect();
     28 
     29     private final RectF mIndicatorRect = new RectF();
     30     private boolean mIndicatorRectDirty;
     31 
     32     private final Paint mPaint;
     33     public final Drawable mIcon;
     34 
     35     private Drawable mBgDrawable;
     36     private int mRingOutset;
     37 
     38     private int mIndicatorColor = 0;
     39 
     40     /**
     41      * Indicates the progress of the preloader [0-100]. If it goes above 100, only the icon
     42      * is shown with no progress bar.
     43      */
     44     private int mProgress = 0;
     45 
     46     private float mAnimationProgress = ANIMATION_PROGRESS_STOPPED;
     47     private ObjectAnimator mAnimator;
     48 
     49     public PreloadIconDrawable(Drawable icon, Theme theme) {
     50         mIcon = icon;
     51 
     52         mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
     53         mPaint.setStyle(Paint.Style.STROKE);
     54         mPaint.setStrokeCap(Paint.Cap.ROUND);
     55 
     56         setBounds(icon.getBounds());
     57         applyPreloaderTheme(theme);
     58         onLevelChange(0);
     59     }
     60 
     61     public void applyPreloaderTheme(Theme t) {
     62         TypedArray ta = t.obtainStyledAttributes(R.styleable.PreloadIconDrawable);
     63         mBgDrawable = ta.getDrawable(R.styleable.PreloadIconDrawable_background);
     64         mBgDrawable.setFilterBitmap(true);
     65         mPaint.setStrokeWidth(ta.getDimension(R.styleable.PreloadIconDrawable_indicatorSize, 0));
     66         mRingOutset = ta.getDimensionPixelSize(R.styleable.PreloadIconDrawable_ringOutset, 0);
     67         ta.recycle();
     68         onBoundsChange(getBounds());
     69         invalidateSelf();
     70     }
     71 
     72     @Override
     73     protected void onBoundsChange(Rect bounds) {
     74         mIcon.setBounds(bounds);
     75         if (mBgDrawable != null) {
     76             sTempRect.set(bounds);
     77             sTempRect.inset(-mRingOutset, -mRingOutset);
     78             mBgDrawable.setBounds(sTempRect);
     79         }
     80         mIndicatorRectDirty = true;
     81     }
     82 
     83     public int getOutset() {
     84         return mRingOutset;
     85     }
     86 
     87     /**
     88      * The size of the indicator is same as the content region of the {@link #mBgDrawable} minus
     89      * half the stroke size to accommodate the indicator.
     90      */
     91     private void initIndicatorRect() {
     92         Drawable d = mBgDrawable;
     93         Rect bounds = d.getBounds();
     94 
     95         d.getPadding(sTempRect);
     96         // Amount by which padding has to be scaled
     97         float paddingScaleX = ((float) bounds.width()) / d.getIntrinsicWidth();
     98         float paddingScaleY = ((float) bounds.height()) / d.getIntrinsicHeight();
     99         mIndicatorRect.set(
    100                 bounds.left + sTempRect.left * paddingScaleX,
    101                 bounds.top + sTempRect.top * paddingScaleY,
    102                 bounds.right - sTempRect.right * paddingScaleX,
    103                 bounds.bottom - sTempRect.bottom * paddingScaleY);
    104 
    105         float inset = mPaint.getStrokeWidth() / 2;
    106         mIndicatorRect.inset(inset, inset);
    107         mIndicatorRectDirty = false;
    108     }
    109 
    110     @Override
    111     public void draw(Canvas canvas) {
    112         final Rect r = new Rect(getBounds());
    113         if (canvas.getClipBounds(sTempRect) && !Rect.intersects(sTempRect, r)) {
    114             // The draw region has been clipped.
    115             return;
    116         }
    117         if (mIndicatorRectDirty) {
    118             initIndicatorRect();
    119         }
    120         final float iconScale;
    121 
    122         if ((mAnimationProgress >= ANIMATION_PROGRESS_STARTED)
    123                 && (mAnimationProgress < ANIMATION_PROGRESS_COMPLETED)) {
    124             mPaint.setAlpha((int) ((1 - mAnimationProgress) * 255));
    125             mBgDrawable.setAlpha(mPaint.getAlpha());
    126             mBgDrawable.draw(canvas);
    127             canvas.drawOval(mIndicatorRect, mPaint);
    128 
    129             iconScale = ICON_SCALE_FACTOR + (1 - ICON_SCALE_FACTOR) * mAnimationProgress;
    130         } else if (mAnimationProgress == ANIMATION_PROGRESS_STOPPED) {
    131             mPaint.setAlpha(255);
    132             iconScale = ICON_SCALE_FACTOR;
    133             mBgDrawable.setAlpha(255);
    134             mBgDrawable.draw(canvas);
    135 
    136             if (mProgress >= 100) {
    137                 canvas.drawOval(mIndicatorRect, mPaint);
    138             } else if (mProgress > 0) {
    139                 canvas.drawArc(mIndicatorRect, -90, mProgress * 3.6f, false, mPaint);
    140             }
    141         } else {
    142             iconScale = 1;
    143         }
    144 
    145         canvas.save();
    146         canvas.scale(iconScale, iconScale, r.exactCenterX(), r.exactCenterY());
    147         mIcon.draw(canvas);
    148         canvas.restore();
    149     }
    150 
    151     @Override
    152     public int getOpacity() {
    153         return PixelFormat.TRANSLUCENT;
    154     }
    155 
    156     @Override
    157     public void setAlpha(int alpha) {
    158         mIcon.setAlpha(alpha);
    159     }
    160 
    161     @Override
    162     public void setColorFilter(ColorFilter cf) {
    163         mIcon.setColorFilter(cf);
    164     }
    165 
    166     @Override
    167     protected boolean onLevelChange(int level) {
    168         mProgress = level;
    169 
    170         // Stop Animation
    171         if (mAnimator != null) {
    172             mAnimator.cancel();
    173             mAnimator = null;
    174         }
    175         mAnimationProgress = ANIMATION_PROGRESS_STOPPED;
    176         if (level > 0) {
    177             // Set the paint color only when the level changes, so that the dominant color
    178             // is only calculated when needed.
    179             mPaint.setColor(getIndicatorColor());
    180         }
    181         if (mIcon instanceof FastBitmapDrawable) {
    182             ((FastBitmapDrawable) mIcon).setState(level <= 0 ?
    183                     FastBitmapDrawable.State.DISABLED : FastBitmapDrawable.State.NORMAL);
    184         }
    185 
    186         invalidateSelf();
    187         return true;
    188     }
    189 
    190     /**
    191      * Runs the finish animation if it is has not been run after last level change.
    192      */
    193     public void maybePerformFinishedAnimation() {
    194         if (mAnimationProgress > ANIMATION_PROGRESS_STOPPED) {
    195             return;
    196         }
    197         if (mAnimator != null) {
    198             mAnimator.cancel();
    199         }
    200         setAnimationProgress(ANIMATION_PROGRESS_STARTED);
    201         mAnimator = ObjectAnimator.ofFloat(this, "animationProgress",
    202                 ANIMATION_PROGRESS_STARTED, ANIMATION_PROGRESS_COMPLETED);
    203         mAnimator.start();
    204     }
    205 
    206     public void setAnimationProgress(float progress) {
    207         if (progress != mAnimationProgress) {
    208             mAnimationProgress = progress;
    209             invalidateSelf();
    210         }
    211     }
    212 
    213     public float getAnimationProgress() {
    214         return mAnimationProgress;
    215     }
    216 
    217     public boolean hasNotCompleted() {
    218         return mAnimationProgress < ANIMATION_PROGRESS_COMPLETED;
    219     }
    220 
    221     @Override
    222     public int getIntrinsicHeight() {
    223         return mIcon.getIntrinsicHeight();
    224     }
    225 
    226     @Override
    227     public int getIntrinsicWidth() {
    228         return mIcon.getIntrinsicWidth();
    229     }
    230 
    231     private int getIndicatorColor() {
    232         if (mIndicatorColor != 0) {
    233             return mIndicatorColor;
    234         }
    235         if (!(mIcon instanceof FastBitmapDrawable)) {
    236             mIndicatorColor = DEFAULT_COLOR;
    237             return mIndicatorColor;
    238         }
    239         mIndicatorColor = Utilities.findDominantColorByHue(
    240                 ((FastBitmapDrawable) mIcon).getBitmap(), 20);
    241 
    242         // Make sure that the dominant color has enough saturation to be visible properly.
    243         float[] hsv = new float[3];
    244         Color.colorToHSV(mIndicatorColor, hsv);
    245         if (hsv[1] < MIN_SATUNATION) {
    246             mIndicatorColor = DEFAULT_COLOR;
    247             return mIndicatorColor;
    248         }
    249         hsv[2] = Math.max(MIN_LIGHTNESS, hsv[2]);
    250         mIndicatorColor = Color.HSVToColor(hsv);
    251         return mIndicatorColor;
    252     }
    253 }
    254