• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
PreloadIconDrawable(Drawable icon, Theme theme)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 
applyPreloaderTheme(Theme t)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
onBoundsChange(Rect bounds)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 
getOutset()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      */
initIndicatorRect()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
draw(Canvas canvas)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
getOpacity()152     public int getOpacity() {
153         return PixelFormat.TRANSLUCENT;
154     }
155 
156     @Override
setAlpha(int alpha)157     public void setAlpha(int alpha) {
158         mIcon.setAlpha(alpha);
159     }
160 
161     @Override
setColorFilter(ColorFilter cf)162     public void setColorFilter(ColorFilter cf) {
163         mIcon.setColorFilter(cf);
164     }
165 
166     @Override
onLevelChange(int level)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).setGhostModeEnabled(level <= 0);
183         }
184 
185         invalidateSelf();
186         return true;
187     }
188 
189     /**
190      * Runs the finish animation if it is has not been run after last level change.
191      */
maybePerformFinishedAnimation()192     public void maybePerformFinishedAnimation() {
193         if (mAnimationProgress > ANIMATION_PROGRESS_STOPPED) {
194             return;
195         }
196         if (mAnimator != null) {
197             mAnimator.cancel();
198         }
199         setAnimationProgress(ANIMATION_PROGRESS_STARTED);
200         mAnimator = ObjectAnimator.ofFloat(this, "animationProgress",
201                 ANIMATION_PROGRESS_STARTED, ANIMATION_PROGRESS_COMPLETED);
202         mAnimator.start();
203     }
204 
setAnimationProgress(float progress)205     public void setAnimationProgress(float progress) {
206         if (progress != mAnimationProgress) {
207             mAnimationProgress = progress;
208             invalidateSelf();
209         }
210     }
211 
getAnimationProgress()212     public float getAnimationProgress() {
213         return mAnimationProgress;
214     }
215 
hasNotCompleted()216     public boolean hasNotCompleted() {
217         return mAnimationProgress < ANIMATION_PROGRESS_COMPLETED;
218     }
219 
220     @Override
getIntrinsicHeight()221     public int getIntrinsicHeight() {
222         return mIcon.getIntrinsicHeight();
223     }
224 
225     @Override
getIntrinsicWidth()226     public int getIntrinsicWidth() {
227         return mIcon.getIntrinsicWidth();
228     }
229 
getIndicatorColor()230     private int getIndicatorColor() {
231         if (mIndicatorColor != 0) {
232             return mIndicatorColor;
233         }
234         if (!(mIcon instanceof FastBitmapDrawable)) {
235             mIndicatorColor = DEFAULT_COLOR;
236             return mIndicatorColor;
237         }
238         mIndicatorColor = Utilities.findDominantColorByHue(
239                 ((FastBitmapDrawable) mIcon).getBitmap(), 20);
240 
241         // Make sure that the dominant color has enough saturation to be visible properly.
242         float[] hsv = new float[3];
243         Color.colorToHSV(mIndicatorColor, hsv);
244         if (hsv[1] < MIN_SATUNATION) {
245             mIndicatorColor = DEFAULT_COLOR;
246             return mIndicatorColor;
247         }
248         hsv[2] = Math.max(MIN_LIGHTNESS, hsv[2]);
249         mIndicatorColor = Color.HSVToColor(hsv);
250         return mIndicatorColor;
251     }
252 }
253