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 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     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         applyTheme(theme);
     58         onLevelChange(0);
     59     }
     60 
     61     @Override
     62     public void applyTheme(Theme t) {
     63         TypedArray ta = t.obtainStyledAttributes(R.styleable.PreloadIconDrawable);
     64         mBgDrawable = ta.getDrawable(R.styleable.PreloadIconDrawable_background);
     65         mBgDrawable.setFilterBitmap(true);
     66         mPaint.setStrokeWidth(ta.getDimension(R.styleable.PreloadIconDrawable_indicatorSize, 0));
     67         mRingOutset = ta.getDimensionPixelSize(R.styleable.PreloadIconDrawable_ringOutset, 0);
     68         ta.recycle();
     69         onBoundsChange(getBounds());
     70         invalidateSelf();
     71     }
     72 
     73     @Override
     74     protected void onBoundsChange(Rect bounds) {
     75         mIcon.setBounds(bounds);
     76         if (mBgDrawable != null) {
     77             sTempRect.set(bounds);
     78             sTempRect.inset(-mRingOutset, -mRingOutset);
     79             mBgDrawable.setBounds(sTempRect);
     80         }
     81         mIndicatorRectDirty = true;
     82     }
     83 
     84     public int getOutset() {
     85         return mRingOutset;
     86     }
     87 
     88     /**
     89      * The size of the indicator is same as the content region of the {@link #mBgDrawable} minus
     90      * half the stroke size to accommodate the indicator.
     91      */
     92     private void initIndicatorRect() {
     93         Drawable d = mBgDrawable;
     94         Rect bounds = d.getBounds();
     95 
     96         d.getPadding(sTempRect);
     97         // Amount by which padding has to be scaled
     98         float paddingScaleX = ((float) bounds.width()) / d.getIntrinsicWidth();
     99         float paddingScaleY = ((float) bounds.height()) / d.getIntrinsicHeight();
    100         mIndicatorRect.set(
    101                 bounds.left + sTempRect.left * paddingScaleX,
    102                 bounds.top + sTempRect.top * paddingScaleY,
    103                 bounds.right - sTempRect.right * paddingScaleX,
    104                 bounds.bottom - sTempRect.bottom * paddingScaleY);
    105 
    106         float inset = mPaint.getStrokeWidth() / 2;
    107         mIndicatorRect.inset(inset, inset);
    108         mIndicatorRectDirty = false;
    109     }
    110 
    111     @Override
    112     public void draw(Canvas canvas) {
    113         final Rect r = new Rect(getBounds());
    114         if (canvas.getClipBounds(sTempRect) && !Rect.intersects(sTempRect, r)) {
    115             // The draw region has been clipped.
    116             return;
    117         }
    118         if (mIndicatorRectDirty) {
    119             initIndicatorRect();
    120         }
    121         final float iconScale;
    122 
    123         if ((mAnimationProgress >= ANIMATION_PROGRESS_STARTED)
    124                 && (mAnimationProgress < ANIMATION_PROGRESS_COMPLETED)) {
    125             mPaint.setAlpha((int) ((1 - mAnimationProgress) * 255));
    126             mBgDrawable.setAlpha(mPaint.getAlpha());
    127             mBgDrawable.draw(canvas);
    128             canvas.drawOval(mIndicatorRect, mPaint);
    129 
    130             iconScale = ICON_SCALE_FACTOR + (1 - ICON_SCALE_FACTOR) * mAnimationProgress;
    131         } else if (mAnimationProgress == ANIMATION_PROGRESS_STOPPED) {
    132             mPaint.setAlpha(255);
    133             iconScale = ICON_SCALE_FACTOR;
    134             mBgDrawable.setAlpha(255);
    135             mBgDrawable.draw(canvas);
    136 
    137             if (mProgress >= 100) {
    138                 canvas.drawOval(mIndicatorRect, mPaint);
    139             } else if (mProgress > 0) {
    140                 canvas.drawArc(mIndicatorRect, -90, mProgress * 3.6f, false, mPaint);
    141             }
    142         } else {
    143             iconScale = 1;
    144         }
    145 
    146         canvas.save();
    147         canvas.scale(iconScale, iconScale, r.exactCenterX(), r.exactCenterY());
    148         mIcon.draw(canvas);
    149         canvas.restore();
    150     }
    151 
    152     @Override
    153     public int getOpacity() {
    154         return PixelFormat.TRANSLUCENT;
    155     }
    156 
    157     @Override
    158     public void setAlpha(int alpha) {
    159         mIcon.setAlpha(alpha);
    160     }
    161 
    162     @Override
    163     public void setColorFilter(ColorFilter cf) {
    164         mIcon.setColorFilter(cf);
    165     }
    166 
    167     @Override
    168     protected boolean onLevelChange(int level) {
    169         mProgress = level;
    170 
    171         // Stop Animation
    172         if (mAnimator != null) {
    173             mAnimator.cancel();
    174             mAnimator = null;
    175         }
    176         mAnimationProgress = ANIMATION_PROGRESS_STOPPED;
    177         if (level > 0) {
    178             // Set the paint color only when the level changes, so that the dominant color
    179             // is only calculated when needed.
    180             mPaint.setColor(getIndicatorColor());
    181         }
    182         if (mIcon instanceof FastBitmapDrawable) {
    183             ((FastBitmapDrawable) mIcon).setGhostModeEnabled(level <= 0);
    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     @Override
    218     public int getIntrinsicHeight() {
    219         return mIcon.getIntrinsicHeight();
    220     }
    221 
    222     @Override
    223     public int getIntrinsicWidth() {
    224         return mIcon.getIntrinsicWidth();
    225     }
    226 
    227     private int getIndicatorColor() {
    228         if (mIndicatorColor != 0) {
    229             return mIndicatorColor;
    230         }
    231         if (!(mIcon instanceof FastBitmapDrawable)) {
    232             mIndicatorColor = DEFAULT_COLOR;
    233             return mIndicatorColor;
    234         }
    235         mIndicatorColor = Utilities.findDominantColorByHue(
    236                 ((FastBitmapDrawable) mIcon).getBitmap(), 20);
    237 
    238         // Make sure that the dominant color has enough saturation to be visible properly.
    239         float[] hsv = new float[3];
    240         Color.colorToHSV(mIndicatorColor, hsv);
    241         if (hsv[1] < MIN_SATUNATION) {
    242             mIndicatorColor = DEFAULT_COLOR;
    243             return mIndicatorColor;
    244         }
    245         hsv[2] = Math.max(MIN_LIGHTNESS, hsv[2]);
    246         mIndicatorColor = Color.HSVToColor(hsv);
    247         return mIndicatorColor;
    248     }
    249 }
    250