blob: 2972c4f9b48dde12785377a450cdaf87dec55d27 [file] [log] [blame]
Sunny Goyal34846382014-07-09 00:09:28 -07001package com.android.launcher3;
2
3import android.animation.ObjectAnimator;
Sunny Goyal95abbb32014-08-04 10:53:22 -07004import android.content.res.Resources.Theme;
5import android.content.res.TypedArray;
Sunny Goyal34846382014-07-09 00:09:28 -07006import android.graphics.Canvas;
Sunny Goyal95abbb32014-08-04 10:53:22 -07007import android.graphics.Color;
Sunny Goyal34846382014-07-09 00:09:28 -07008import android.graphics.ColorFilter;
9import android.graphics.Paint;
Sunny Goyal34846382014-07-09 00:09:28 -070010import android.graphics.PixelFormat;
11import android.graphics.Rect;
12import android.graphics.RectF;
13import android.graphics.drawable.Drawable;
14
15class PreloadIconDrawable extends Drawable {
Sunny Goyal95abbb32014-08-04 10:53:22 -070016
Sunny Goyal34846382014-07-09 00:09:28 -070017 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
Sunny Goyal95abbb32014-08-04 10:53:22 -070021 private static final float MIN_SATUNATION = 0.2f;
22 private static final float MIN_LIGHTNESS = 0.6f;
Sunny Goyal34846382014-07-09 00:09:28 -070023
Sunny Goyal95abbb32014-08-04 10:53:22 -070024 private static final float ICON_SCALE_FACTOR = 0.5f;
25 private static final int DEFAULT_COLOR = 0xFF009688;
Sunny Goyal34846382014-07-09 00:09:28 -070026
Sunny Goyal95abbb32014-08-04 10:53:22 -070027 private static final Rect sTempRect = new Rect();
Sunny Goyal34846382014-07-09 00:09:28 -070028
Sunny Goyal95abbb32014-08-04 10:53:22 -070029 private final RectF mIndicatorRect = new RectF();
30 private boolean mIndicatorRectDirty;
31
32 private final Paint mPaint;
Sunny Goyal34846382014-07-09 00:09:28 -070033 final Drawable mIcon;
34
Sunny Goyal95abbb32014-08-04 10:53:22 -070035 private Drawable mBgDrawable;
36 private int mRingOutset;
37
38 private int mIndicatorColor = 0;
39
Sunny Goyal34846382014-07-09 00:09:28 -070040 /**
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;
Sunny Goyal34846382014-07-09 00:09:28 -070045
46 private float mAnimationProgress = ANIMATION_PROGRESS_STOPPED;
47 private ObjectAnimator mAnimator;
48
Sunny Goyal95abbb32014-08-04 10:53:22 -070049 public PreloadIconDrawable(Drawable icon, Theme theme) {
Sunny Goyal34846382014-07-09 00:09:28 -070050 mIcon = icon;
51
Sunny Goyal95abbb32014-08-04 10:53:22 -070052 mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
53 mPaint.setStyle(Paint.Style.STROKE);
54 mPaint.setStrokeCap(Paint.Cap.ROUND);
Sunny Goyal34846382014-07-09 00:09:28 -070055
Sunny Goyal95abbb32014-08-04 10:53:22 -070056 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);
Sunny Goyal34846382014-07-09 00:09:28 -070080 }
Sunny Goyal95abbb32014-08-04 10:53:22 -070081 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;
Sunny Goyal34846382014-07-09 00:09:28 -0700109 }
110
111 @Override
112 public void draw(Canvas canvas) {
Sunny Goyal95abbb32014-08-04 10:53:22 -0700113 final Rect r = new Rect(getBounds());
114 if (canvas.getClipBounds(sTempRect) && !Rect.intersects(sTempRect, r)) {
Sunny Goyal34846382014-07-09 00:09:28 -0700115 // The draw region has been clipped.
116 return;
117 }
Sunny Goyal95abbb32014-08-04 10:53:22 -0700118 if (mIndicatorRectDirty) {
119 initIndicatorRect();
120 }
Sunny Goyal34846382014-07-09 00:09:28 -0700121 final float iconScale;
122
123 if ((mAnimationProgress >= ANIMATION_PROGRESS_STARTED)
124 && (mAnimationProgress < ANIMATION_PROGRESS_COMPLETED)) {
125 mPaint.setAlpha((int) ((1 - mAnimationProgress) * 255));
Sunny Goyal95abbb32014-08-04 10:53:22 -0700126 mBgDrawable.setAlpha(mPaint.getAlpha());
127 mBgDrawable.draw(canvas);
128 canvas.drawOval(mIndicatorRect, mPaint);
Sunny Goyal34846382014-07-09 00:09:28 -0700129
Sunny Goyal95abbb32014-08-04 10:53:22 -0700130 iconScale = ICON_SCALE_FACTOR + (1 - ICON_SCALE_FACTOR) * mAnimationProgress;
Sunny Goyal34846382014-07-09 00:09:28 -0700131 } else if (mAnimationProgress == ANIMATION_PROGRESS_STOPPED) {
132 mPaint.setAlpha(255);
133 iconScale = ICON_SCALE_FACTOR;
Sunny Goyal95abbb32014-08-04 10:53:22 -0700134 mBgDrawable.setAlpha(255);
135 mBgDrawable.draw(canvas);
Sunny Goyal34846382014-07-09 00:09:28 -0700136
137 if (mProgress >= 100) {
Sunny Goyal95abbb32014-08-04 10:53:22 -0700138 canvas.drawOval(mIndicatorRect, mPaint);
Sunny Goyal34846382014-07-09 00:09:28 -0700139 } else if (mProgress > 0) {
Sunny Goyal95abbb32014-08-04 10:53:22 -0700140 canvas.drawArc(mIndicatorRect, -90, mProgress * 3.6f, false, mPaint);
Sunny Goyal34846382014-07-09 00:09:28 -0700141 }
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
Sunny Goyal34846382014-07-09 00:09:28 -0700153 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;
Sunny Goyal34846382014-07-09 00:09:28 -0700170
171 // Stop Animation
172 if (mAnimator != null) {
173 mAnimator.cancel();
174 mAnimator = null;
175 }
176 mAnimationProgress = ANIMATION_PROGRESS_STOPPED;
Sunny Goyal95abbb32014-08-04 10:53:22 -0700177 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 }
Sunny Goyal34846382014-07-09 00:09:28 -0700185
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 */
Sunny Goyal34942622014-08-29 17:20:55 -0700193 public void maybePerformFinishedAnimation() {
Sunny Goyal34846382014-07-09 00:09:28 -0700194 if (mAnimationProgress > ANIMATION_PROGRESS_STOPPED) {
Sunny Goyal34942622014-08-29 17:20:55 -0700195 return;
Sunny Goyal34846382014-07-09 00:09:28 -0700196 }
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 }
Sunny Goyal95abbb32014-08-04 10:53:22 -0700216
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 }
Sunny Goyal34846382014-07-09 00:09:28 -0700249}