• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.launcher3.icons;
18 
19 import static com.android.launcher3.icons.BaseIconFactory.getBadgeSizeForIconSize;
20 import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
21 
22 import android.animation.ObjectAnimator;
23 import android.graphics.Bitmap;
24 import android.graphics.Canvas;
25 import android.graphics.Color;
26 import android.graphics.ColorFilter;
27 import android.graphics.ColorMatrix;
28 import android.graphics.ColorMatrixColorFilter;
29 import android.graphics.Paint;
30 import android.graphics.PixelFormat;
31 import android.graphics.Rect;
32 import android.graphics.drawable.Drawable;
33 import android.util.FloatProperty;
34 import android.view.animation.AccelerateInterpolator;
35 import android.view.animation.DecelerateInterpolator;
36 import android.view.animation.Interpolator;
37 
38 import androidx.annotation.Nullable;
39 import androidx.core.graphics.ColorUtils;
40 
41 public class FastBitmapDrawable extends Drawable implements Drawable.Callback {
42 
43     private static final Interpolator ACCEL = new AccelerateInterpolator();
44     private static final Interpolator DEACCEL = new DecelerateInterpolator();
45 
46     private static final float PRESSED_SCALE = 1.1f;
47     public static final int WHITE_SCRIM_ALPHA = 138;
48 
49     private static final float DISABLED_DESATURATION = 1f;
50     private static final float DISABLED_BRIGHTNESS = 0.5f;
51     protected static final int FULLY_OPAQUE = 255;
52 
53     public static final int CLICK_FEEDBACK_DURATION = 200;
54 
55     protected final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG);
56     protected final Bitmap mBitmap;
57     protected final int mIconColor;
58 
59     @Nullable private ColorFilter mColorFilter;
60 
61     private boolean mIsPressed;
62     protected boolean mIsDisabled;
63     float mDisabledAlpha = 1f;
64 
65     // Animator and properties for the fast bitmap drawable's scale
66     private static final FloatProperty<FastBitmapDrawable> SCALE
67             = new FloatProperty<FastBitmapDrawable>("scale") {
68         @Override
69         public Float get(FastBitmapDrawable fastBitmapDrawable) {
70             return fastBitmapDrawable.mScale;
71         }
72 
73         @Override
74         public void setValue(FastBitmapDrawable fastBitmapDrawable, float value) {
75             fastBitmapDrawable.mScale = value;
76             fastBitmapDrawable.invalidateSelf();
77         }
78     };
79     private ObjectAnimator mScaleAnimation;
80     private float mScale = 1;
81     private int mAlpha = 255;
82 
83     private Drawable mBadge;
84 
FastBitmapDrawable(Bitmap b)85     public FastBitmapDrawable(Bitmap b) {
86         this(b, Color.TRANSPARENT);
87     }
88 
FastBitmapDrawable(BitmapInfo info)89     public FastBitmapDrawable(BitmapInfo info) {
90         this(info.icon, info.color);
91     }
92 
FastBitmapDrawable(Bitmap b, int iconColor)93     protected FastBitmapDrawable(Bitmap b, int iconColor) {
94         mBitmap = b;
95         mIconColor = iconColor;
96         setFilterBitmap(true);
97     }
98 
99     @Override
onBoundsChange(Rect bounds)100     protected void onBoundsChange(Rect bounds) {
101         super.onBoundsChange(bounds);
102         updateBadgeBounds(bounds);
103     }
104 
updateBadgeBounds(Rect bounds)105     private void updateBadgeBounds(Rect bounds) {
106         if (mBadge != null) {
107             setBadgeBounds(mBadge, bounds);
108         }
109     }
110 
111     @Override
draw(Canvas canvas)112     public final void draw(Canvas canvas) {
113         if (mScale != 1f) {
114             int count = canvas.save();
115             Rect bounds = getBounds();
116             canvas.scale(mScale, mScale, bounds.exactCenterX(), bounds.exactCenterY());
117             drawInternal(canvas, bounds);
118             if (mBadge != null) {
119                 mBadge.draw(canvas);
120             }
121             canvas.restoreToCount(count);
122         } else {
123             drawInternal(canvas, getBounds());
124             if (mBadge != null) {
125                 mBadge.draw(canvas);
126             }
127         }
128     }
129 
drawInternal(Canvas canvas, Rect bounds)130     protected void drawInternal(Canvas canvas, Rect bounds) {
131         canvas.drawBitmap(mBitmap, null, bounds, mPaint);
132     }
133 
134     /**
135      * Returns the primary icon color, slightly tinted white
136      */
getIconColor()137     public int getIconColor() {
138         int whiteScrim = setColorAlphaBound(Color.WHITE, WHITE_SCRIM_ALPHA);
139         return ColorUtils.compositeColors(whiteScrim, mIconColor);
140     }
141 
142     /**
143      * Returns if this represents a themed icon
144      */
isThemed()145     public boolean isThemed() {
146         return false;
147     }
148 
149     @Override
setColorFilter(ColorFilter cf)150     public void setColorFilter(ColorFilter cf) {
151         mColorFilter = cf;
152         updateFilter();
153     }
154 
155     @Override
getOpacity()156     public int getOpacity() {
157         return PixelFormat.TRANSLUCENT;
158     }
159 
160     @Override
setAlpha(int alpha)161     public void setAlpha(int alpha) {
162         if (mAlpha != alpha) {
163             mAlpha = alpha;
164             mPaint.setAlpha(alpha);
165             invalidateSelf();
166         }
167     }
168 
169     @Override
setFilterBitmap(boolean filterBitmap)170     public void setFilterBitmap(boolean filterBitmap) {
171         mPaint.setFilterBitmap(filterBitmap);
172         mPaint.setAntiAlias(filterBitmap);
173     }
174 
175     @Override
getAlpha()176     public int getAlpha() {
177         return mAlpha;
178     }
179 
resetScale()180     public void resetScale() {
181         if (mScaleAnimation != null) {
182             mScaleAnimation.cancel();
183             mScaleAnimation = null;
184         }
185         mScale = 1;
186         invalidateSelf();
187     }
188 
getAnimatedScale()189     public float getAnimatedScale() {
190         return mScaleAnimation == null ? 1 : mScale;
191     }
192 
193     @Override
getIntrinsicWidth()194     public int getIntrinsicWidth() {
195         return mBitmap.getWidth();
196     }
197 
198     @Override
getIntrinsicHeight()199     public int getIntrinsicHeight() {
200         return mBitmap.getHeight();
201     }
202 
203     @Override
getMinimumWidth()204     public int getMinimumWidth() {
205         return getBounds().width();
206     }
207 
208     @Override
getMinimumHeight()209     public int getMinimumHeight() {
210         return getBounds().height();
211     }
212 
213     @Override
isStateful()214     public boolean isStateful() {
215         return true;
216     }
217 
218     @Override
getColorFilter()219     public ColorFilter getColorFilter() {
220         return mPaint.getColorFilter();
221     }
222 
223     @Override
onStateChange(int[] state)224     protected boolean onStateChange(int[] state) {
225         boolean isPressed = false;
226         for (int s : state) {
227             if (s == android.R.attr.state_pressed) {
228                 isPressed = true;
229                 break;
230             }
231         }
232         if (mIsPressed != isPressed) {
233             mIsPressed = isPressed;
234 
235             if (mScaleAnimation != null) {
236                 mScaleAnimation.cancel();
237                 mScaleAnimation = null;
238             }
239 
240             if (mIsPressed) {
241                 // Animate when going to pressed state
242                 mScaleAnimation = ObjectAnimator.ofFloat(this, SCALE, PRESSED_SCALE);
243                 mScaleAnimation.setDuration(CLICK_FEEDBACK_DURATION);
244                 mScaleAnimation.setInterpolator(ACCEL);
245                 mScaleAnimation.start();
246             } else {
247                 if (isVisible()) {
248                     mScaleAnimation = ObjectAnimator.ofFloat(this, SCALE, 1f);
249                     mScaleAnimation.setDuration(CLICK_FEEDBACK_DURATION);
250                     mScaleAnimation.setInterpolator(DEACCEL);
251                     mScaleAnimation.start();
252                 } else {
253                     mScale = 1f;
254                     invalidateSelf();
255                 }
256             }
257             return true;
258         }
259         return false;
260     }
261 
setIsDisabled(boolean isDisabled)262     public void setIsDisabled(boolean isDisabled) {
263         if (mIsDisabled != isDisabled) {
264             mIsDisabled = isDisabled;
265             updateFilter();
266         }
267     }
268 
isDisabled()269     protected boolean isDisabled() {
270         return mIsDisabled;
271     }
272 
setBadge(Drawable badge)273     public void setBadge(Drawable badge) {
274         if (mBadge != null) {
275             mBadge.setCallback(null);
276         }
277         mBadge = badge;
278         if (mBadge != null) {
279             mBadge.setCallback(this);
280         }
281         updateBadgeBounds(getBounds());
282         updateFilter();
283     }
284 
285     /**
286      * Updates the paint to reflect the current brightness and saturation.
287      */
updateFilter()288     protected void updateFilter() {
289         mPaint.setColorFilter(mIsDisabled ? getDisabledColorFilter(mDisabledAlpha) : mColorFilter);
290         if (mBadge != null) {
291             mBadge.setColorFilter(getColorFilter());
292         }
293         invalidateSelf();
294     }
295 
newConstantState()296     protected FastBitmapConstantState newConstantState() {
297         return new FastBitmapConstantState(mBitmap, mIconColor);
298     }
299 
300     @Override
getConstantState()301     public final ConstantState getConstantState() {
302         FastBitmapConstantState cs = newConstantState();
303         cs.mIsDisabled = mIsDisabled;
304         if (mBadge != null) {
305             cs.mBadgeConstantState = mBadge.getConstantState();
306         }
307         return cs;
308     }
309 
getDisabledColorFilter()310     public static ColorFilter getDisabledColorFilter() {
311         return getDisabledColorFilter(1);
312     }
313 
getDisabledColorFilter(float disabledAlpha)314     private static ColorFilter getDisabledColorFilter(float disabledAlpha) {
315         ColorMatrix tempBrightnessMatrix = new ColorMatrix();
316         ColorMatrix tempFilterMatrix = new ColorMatrix();
317 
318         tempFilterMatrix.setSaturation(1f - DISABLED_DESATURATION);
319         float scale = 1 - DISABLED_BRIGHTNESS;
320         int brightnessI =   (int) (255 * DISABLED_BRIGHTNESS);
321         float[] mat = tempBrightnessMatrix.getArray();
322         mat[0] = scale;
323         mat[6] = scale;
324         mat[12] = scale;
325         mat[4] = brightnessI;
326         mat[9] = brightnessI;
327         mat[14] = brightnessI;
328         mat[18] = disabledAlpha;
329         tempFilterMatrix.preConcat(tempBrightnessMatrix);
330         return new ColorMatrixColorFilter(tempFilterMatrix);
331     }
332 
getDisabledColor(int color)333     protected static final int getDisabledColor(int color) {
334         int component = (Color.red(color) + Color.green(color) + Color.blue(color)) / 3;
335         float scale = 1 - DISABLED_BRIGHTNESS;
336         int brightnessI = (int) (255 * DISABLED_BRIGHTNESS);
337         component = Math.min(Math.round(scale * component + brightnessI), FULLY_OPAQUE);
338         return Color.rgb(component, component, component);
339     }
340 
341     /**
342      * Sets the bounds for the badge drawable based on the main icon bounds
343      */
setBadgeBounds(Drawable badge, Rect iconBounds)344     public static void setBadgeBounds(Drawable badge, Rect iconBounds) {
345         int size = getBadgeSizeForIconSize(iconBounds.width());
346         badge.setBounds(iconBounds.right - size, iconBounds.bottom - size,
347                 iconBounds.right, iconBounds.bottom);
348     }
349 
350     @Override
invalidateDrawable(Drawable who)351     public void invalidateDrawable(Drawable who) {
352         if (who == mBadge) {
353             invalidateSelf();
354         }
355     }
356 
357     @Override
scheduleDrawable(Drawable who, Runnable what, long when)358     public void scheduleDrawable(Drawable who, Runnable what, long when) {
359         if (who == mBadge) {
360             scheduleSelf(what, when);
361         }
362     }
363 
364     @Override
unscheduleDrawable(Drawable who, Runnable what)365     public void unscheduleDrawable(Drawable who, Runnable what) {
366         unscheduleSelf(what);
367     }
368 
369     protected static class FastBitmapConstantState extends ConstantState {
370         protected final Bitmap mBitmap;
371         protected final int mIconColor;
372 
373         // These are initialized later so that subclasses don't need to
374         // pass everything in constructor
375         protected boolean mIsDisabled;
376         private ConstantState mBadgeConstantState;
377 
FastBitmapConstantState(Bitmap bitmap, int color)378         public FastBitmapConstantState(Bitmap bitmap, int color) {
379             mBitmap = bitmap;
380             mIconColor = color;
381         }
382 
createDrawable()383         protected FastBitmapDrawable createDrawable() {
384             return new FastBitmapDrawable(mBitmap, mIconColor);
385         }
386 
387         @Override
newDrawable()388         public final FastBitmapDrawable newDrawable() {
389             FastBitmapDrawable drawable = createDrawable();
390             drawable.setIsDisabled(mIsDisabled);
391             if (mBadgeConstantState != null) {
392                 drawable.setBadge(mBadgeConstantState.newDrawable());
393             }
394             return drawable;
395         }
396 
397         @Override
getChangingConfigurations()398         public int getChangingConfigurations() {
399             return 0;
400         }
401     }
402 }
403