Sunny Goyal | 3484638 | 2014-07-09 00:09:28 -0700 | [diff] [blame] | 1 | package com.android.launcher3; |
| 2 | |
| 3 | import android.animation.ObjectAnimator; |
Sunny Goyal | 95abbb3 | 2014-08-04 10:53:22 -0700 | [diff] [blame] | 4 | import android.content.res.Resources.Theme; |
| 5 | import android.content.res.TypedArray; |
Sunny Goyal | 3484638 | 2014-07-09 00:09:28 -0700 | [diff] [blame] | 6 | import android.graphics.Canvas; |
Sunny Goyal | 95abbb3 | 2014-08-04 10:53:22 -0700 | [diff] [blame] | 7 | import android.graphics.Color; |
Sunny Goyal | 3484638 | 2014-07-09 00:09:28 -0700 | [diff] [blame] | 8 | import android.graphics.ColorFilter; |
| 9 | import android.graphics.Paint; |
Sunny Goyal | 3484638 | 2014-07-09 00:09:28 -0700 | [diff] [blame] | 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 { |
Sunny Goyal | 95abbb3 | 2014-08-04 10:53:22 -0700 | [diff] [blame] | 16 | |
Sunny Goyal | 3484638 | 2014-07-09 00:09:28 -0700 | [diff] [blame] | 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 | |
Sunny Goyal | 95abbb3 | 2014-08-04 10:53:22 -0700 | [diff] [blame] | 21 | private static final float MIN_SATUNATION = 0.2f; |
| 22 | private static final float MIN_LIGHTNESS = 0.6f; |
Sunny Goyal | 3484638 | 2014-07-09 00:09:28 -0700 | [diff] [blame] | 23 | |
Sunny Goyal | 95abbb3 | 2014-08-04 10:53:22 -0700 | [diff] [blame] | 24 | private static final float ICON_SCALE_FACTOR = 0.5f; |
| 25 | private static final int DEFAULT_COLOR = 0xFF009688; |
Sunny Goyal | 3484638 | 2014-07-09 00:09:28 -0700 | [diff] [blame] | 26 | |
Sunny Goyal | 95abbb3 | 2014-08-04 10:53:22 -0700 | [diff] [blame] | 27 | private static final Rect sTempRect = new Rect(); |
Sunny Goyal | 3484638 | 2014-07-09 00:09:28 -0700 | [diff] [blame] | 28 | |
Sunny Goyal | 95abbb3 | 2014-08-04 10:53:22 -0700 | [diff] [blame] | 29 | private final RectF mIndicatorRect = new RectF(); |
| 30 | private boolean mIndicatorRectDirty; |
| 31 | |
| 32 | private final Paint mPaint; |
Sunny Goyal | 3484638 | 2014-07-09 00:09:28 -0700 | [diff] [blame] | 33 | final Drawable mIcon; |
| 34 | |
Sunny Goyal | 95abbb3 | 2014-08-04 10:53:22 -0700 | [diff] [blame] | 35 | private Drawable mBgDrawable; |
| 36 | private int mRingOutset; |
| 37 | |
| 38 | private int mIndicatorColor = 0; |
| 39 | |
Sunny Goyal | 3484638 | 2014-07-09 00:09:28 -0700 | [diff] [blame] | 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; |
Sunny Goyal | 3484638 | 2014-07-09 00:09:28 -0700 | [diff] [blame] | 45 | |
| 46 | private float mAnimationProgress = ANIMATION_PROGRESS_STOPPED; |
| 47 | private ObjectAnimator mAnimator; |
| 48 | |
Sunny Goyal | 95abbb3 | 2014-08-04 10:53:22 -0700 | [diff] [blame] | 49 | public PreloadIconDrawable(Drawable icon, Theme theme) { |
Sunny Goyal | 3484638 | 2014-07-09 00:09:28 -0700 | [diff] [blame] | 50 | mIcon = icon; |
| 51 | |
Sunny Goyal | 95abbb3 | 2014-08-04 10:53:22 -0700 | [diff] [blame] | 52 | mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); |
| 53 | mPaint.setStyle(Paint.Style.STROKE); |
| 54 | mPaint.setStrokeCap(Paint.Cap.ROUND); |
Sunny Goyal | 3484638 | 2014-07-09 00:09:28 -0700 | [diff] [blame] | 55 | |
Sunny Goyal | 95abbb3 | 2014-08-04 10:53:22 -0700 | [diff] [blame] | 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); |
Sunny Goyal | 3484638 | 2014-07-09 00:09:28 -0700 | [diff] [blame] | 80 | } |
Sunny Goyal | 95abbb3 | 2014-08-04 10:53:22 -0700 | [diff] [blame] | 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; |
Sunny Goyal | 3484638 | 2014-07-09 00:09:28 -0700 | [diff] [blame] | 109 | } |
| 110 | |
| 111 | @Override |
| 112 | public void draw(Canvas canvas) { |
Sunny Goyal | 95abbb3 | 2014-08-04 10:53:22 -0700 | [diff] [blame] | 113 | final Rect r = new Rect(getBounds()); |
| 114 | if (canvas.getClipBounds(sTempRect) && !Rect.intersects(sTempRect, r)) { |
Sunny Goyal | 3484638 | 2014-07-09 00:09:28 -0700 | [diff] [blame] | 115 | // The draw region has been clipped. |
| 116 | return; |
| 117 | } |
Sunny Goyal | 95abbb3 | 2014-08-04 10:53:22 -0700 | [diff] [blame] | 118 | if (mIndicatorRectDirty) { |
| 119 | initIndicatorRect(); |
| 120 | } |
Sunny Goyal | 3484638 | 2014-07-09 00:09:28 -0700 | [diff] [blame] | 121 | final float iconScale; |
| 122 | |
| 123 | if ((mAnimationProgress >= ANIMATION_PROGRESS_STARTED) |
| 124 | && (mAnimationProgress < ANIMATION_PROGRESS_COMPLETED)) { |
| 125 | mPaint.setAlpha((int) ((1 - mAnimationProgress) * 255)); |
Sunny Goyal | 95abbb3 | 2014-08-04 10:53:22 -0700 | [diff] [blame] | 126 | mBgDrawable.setAlpha(mPaint.getAlpha()); |
| 127 | mBgDrawable.draw(canvas); |
| 128 | canvas.drawOval(mIndicatorRect, mPaint); |
Sunny Goyal | 3484638 | 2014-07-09 00:09:28 -0700 | [diff] [blame] | 129 | |
Sunny Goyal | 95abbb3 | 2014-08-04 10:53:22 -0700 | [diff] [blame] | 130 | iconScale = ICON_SCALE_FACTOR + (1 - ICON_SCALE_FACTOR) * mAnimationProgress; |
Sunny Goyal | 3484638 | 2014-07-09 00:09:28 -0700 | [diff] [blame] | 131 | } else if (mAnimationProgress == ANIMATION_PROGRESS_STOPPED) { |
| 132 | mPaint.setAlpha(255); |
| 133 | iconScale = ICON_SCALE_FACTOR; |
Sunny Goyal | 95abbb3 | 2014-08-04 10:53:22 -0700 | [diff] [blame] | 134 | mBgDrawable.setAlpha(255); |
| 135 | mBgDrawable.draw(canvas); |
Sunny Goyal | 3484638 | 2014-07-09 00:09:28 -0700 | [diff] [blame] | 136 | |
| 137 | if (mProgress >= 100) { |
Sunny Goyal | 95abbb3 | 2014-08-04 10:53:22 -0700 | [diff] [blame] | 138 | canvas.drawOval(mIndicatorRect, mPaint); |
Sunny Goyal | 3484638 | 2014-07-09 00:09:28 -0700 | [diff] [blame] | 139 | } else if (mProgress > 0) { |
Sunny Goyal | 95abbb3 | 2014-08-04 10:53:22 -0700 | [diff] [blame] | 140 | canvas.drawArc(mIndicatorRect, -90, mProgress * 3.6f, false, mPaint); |
Sunny Goyal | 3484638 | 2014-07-09 00:09:28 -0700 | [diff] [blame] | 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 |
Sunny Goyal | 3484638 | 2014-07-09 00:09:28 -0700 | [diff] [blame] | 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; |
Sunny Goyal | 3484638 | 2014-07-09 00:09:28 -0700 | [diff] [blame] | 170 | |
| 171 | // Stop Animation |
| 172 | if (mAnimator != null) { |
| 173 | mAnimator.cancel(); |
| 174 | mAnimator = null; |
| 175 | } |
| 176 | mAnimationProgress = ANIMATION_PROGRESS_STOPPED; |
Sunny Goyal | 95abbb3 | 2014-08-04 10:53:22 -0700 | [diff] [blame] | 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 | } |
Sunny Goyal | 3484638 | 2014-07-09 00:09:28 -0700 | [diff] [blame] | 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 | */ |
Sunny Goyal | 3494262 | 2014-08-29 17:20:55 -0700 | [diff] [blame^] | 193 | public void maybePerformFinishedAnimation() { |
Sunny Goyal | 3484638 | 2014-07-09 00:09:28 -0700 | [diff] [blame] | 194 | if (mAnimationProgress > ANIMATION_PROGRESS_STOPPED) { |
Sunny Goyal | 3494262 | 2014-08-29 17:20:55 -0700 | [diff] [blame^] | 195 | return; |
Sunny Goyal | 3484638 | 2014-07-09 00:09:28 -0700 | [diff] [blame] | 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 | } |
Sunny Goyal | 95abbb3 | 2014-08-04 10:53:22 -0700 | [diff] [blame] | 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 | } |
Sunny Goyal | 3484638 | 2014-07-09 00:09:28 -0700 | [diff] [blame] | 249 | } |