blob: bcb59c448d5f1ee7cfd0f1cbe24c6deb2fd04fe1 [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());
Sunny Goyal71b5c0b2015-01-08 16:59:04 -080057 applyPreloaderTheme(theme);
Sunny Goyal95abbb32014-08-04 10:53:22 -070058 onLevelChange(0);
59 }
60
Sunny Goyal71b5c0b2015-01-08 16:59:04 -080061 public void applyPreloaderTheme(Theme t) {
Sunny Goyal95abbb32014-08-04 10:53:22 -070062 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
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);
Sunny Goyal34846382014-07-09 00:09:28 -070079 }
Sunny Goyal95abbb32014-08-04 10:53:22 -070080 mIndicatorRectDirty = true;
81 }
82
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 */
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;
Sunny Goyal34846382014-07-09 00:09:28 -0700108 }
109
110 @Override
111 public void draw(Canvas canvas) {
Sunny Goyal95abbb32014-08-04 10:53:22 -0700112 final Rect r = new Rect(getBounds());
113 if (canvas.getClipBounds(sTempRect) && !Rect.intersects(sTempRect, r)) {
Sunny Goyal34846382014-07-09 00:09:28 -0700114 // The draw region has been clipped.
115 return;
116 }
Sunny Goyal95abbb32014-08-04 10:53:22 -0700117 if (mIndicatorRectDirty) {
118 initIndicatorRect();
119 }
Sunny Goyal34846382014-07-09 00:09:28 -0700120 final float iconScale;
121
122 if ((mAnimationProgress >= ANIMATION_PROGRESS_STARTED)
123 && (mAnimationProgress < ANIMATION_PROGRESS_COMPLETED)) {
124 mPaint.setAlpha((int) ((1 - mAnimationProgress) * 255));
Sunny Goyal95abbb32014-08-04 10:53:22 -0700125 mBgDrawable.setAlpha(mPaint.getAlpha());
126 mBgDrawable.draw(canvas);
127 canvas.drawOval(mIndicatorRect, mPaint);
Sunny Goyal34846382014-07-09 00:09:28 -0700128
Sunny Goyal95abbb32014-08-04 10:53:22 -0700129 iconScale = ICON_SCALE_FACTOR + (1 - ICON_SCALE_FACTOR) * mAnimationProgress;
Sunny Goyal34846382014-07-09 00:09:28 -0700130 } else if (mAnimationProgress == ANIMATION_PROGRESS_STOPPED) {
131 mPaint.setAlpha(255);
132 iconScale = ICON_SCALE_FACTOR;
Sunny Goyal95abbb32014-08-04 10:53:22 -0700133 mBgDrawable.setAlpha(255);
134 mBgDrawable.draw(canvas);
Sunny Goyal34846382014-07-09 00:09:28 -0700135
136 if (mProgress >= 100) {
Sunny Goyal95abbb32014-08-04 10:53:22 -0700137 canvas.drawOval(mIndicatorRect, mPaint);
Sunny Goyal34846382014-07-09 00:09:28 -0700138 } else if (mProgress > 0) {
Sunny Goyal95abbb32014-08-04 10:53:22 -0700139 canvas.drawArc(mIndicatorRect, -90, mProgress * 3.6f, false, mPaint);
Sunny Goyal34846382014-07-09 00:09:28 -0700140 }
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
Sunny Goyal34846382014-07-09 00:09:28 -0700152 public int getOpacity() {
153 return PixelFormat.TRANSLUCENT;
154 }
155
156 @Override
157 public void setAlpha(int alpha) {
158 mIcon.setAlpha(alpha);
159 }
160
161 @Override
162 public void setColorFilter(ColorFilter cf) {
163 mIcon.setColorFilter(cf);
164 }
165
166 @Override
167 protected boolean onLevelChange(int level) {
168 mProgress = level;
Sunny Goyal34846382014-07-09 00:09:28 -0700169
170 // Stop Animation
171 if (mAnimator != null) {
172 mAnimator.cancel();
173 mAnimator = null;
174 }
175 mAnimationProgress = ANIMATION_PROGRESS_STOPPED;
Sunny Goyal95abbb32014-08-04 10:53:22 -0700176 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 }
Sunny Goyal34846382014-07-09 00:09:28 -0700184
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 */
Sunny Goyal34942622014-08-29 17:20:55 -0700192 public void maybePerformFinishedAnimation() {
Sunny Goyal34846382014-07-09 00:09:28 -0700193 if (mAnimationProgress > ANIMATION_PROGRESS_STOPPED) {
Sunny Goyal34942622014-08-29 17:20:55 -0700194 return;
Sunny Goyal34846382014-07-09 00:09:28 -0700195 }
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
205 public void setAnimationProgress(float progress) {
206 if (progress != mAnimationProgress) {
207 mAnimationProgress = progress;
208 invalidateSelf();
209 }
210 }
211
212 public float getAnimationProgress() {
213 return mAnimationProgress;
214 }
Sunny Goyal95abbb32014-08-04 10:53:22 -0700215
216 @Override
217 public int getIntrinsicHeight() {
218 return mIcon.getIntrinsicHeight();
219 }
220
221 @Override
222 public int getIntrinsicWidth() {
223 return mIcon.getIntrinsicWidth();
224 }
225
226 private int getIndicatorColor() {
227 if (mIndicatorColor != 0) {
228 return mIndicatorColor;
229 }
230 if (!(mIcon instanceof FastBitmapDrawable)) {
231 mIndicatorColor = DEFAULT_COLOR;
232 return mIndicatorColor;
233 }
234 mIndicatorColor = Utilities.findDominantColorByHue(
235 ((FastBitmapDrawable) mIcon).getBitmap(), 20);
236
237 // Make sure that the dominant color has enough saturation to be visible properly.
238 float[] hsv = new float[3];
239 Color.colorToHSV(mIndicatorColor, hsv);
240 if (hsv[1] < MIN_SATUNATION) {
241 mIndicatorColor = DEFAULT_COLOR;
242 return mIndicatorColor;
243 }
244 hsv[2] = Math.max(MIN_LIGHTNESS, hsv[2]);
245 mIndicatorColor = Color.HSVToColor(hsv);
246 return mIndicatorColor;
247 }
Sunny Goyal34846382014-07-09 00:09:28 -0700248}