summaryrefslogtreecommitdiffstats
path: root/src/com/android/launcher3/PreloadIconDrawable.java
blob: 2972c4f9b48dde12785377a450cdaf87dec55d27 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
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());
        applyTheme(theme);
        onLevelChange(0);
    }

    @Override
    public void applyTheme(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;
    }
}