PreloadIconDrawable.java revision 349426234e8c5a0e5bcf2c8d94dbb9844b5f724a
1package com.android.launcher3;
2
3import android.animation.ObjectAnimator;
4import android.content.res.Resources.Theme;
5import android.content.res.TypedArray;
6import android.graphics.Canvas;
7import android.graphics.Color;
8import android.graphics.ColorFilter;
9import android.graphics.Paint;
10import android.graphics.PixelFormat;
11import android.graphics.Rect;
12import android.graphics.RectF;
13import android.graphics.drawable.Drawable;
14
15class 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