• 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 import android.view.animation.PathInterpolator;
38 
39 import androidx.annotation.Nullable;
40 import androidx.annotation.VisibleForTesting;
41 import androidx.core.graphics.ColorUtils;
42 
43 public class FastBitmapDrawable extends Drawable implements Drawable.Callback {
44 
45     private static final Interpolator ACCEL = new AccelerateInterpolator();
46     private static final Interpolator DEACCEL = new DecelerateInterpolator();
47     private static final Interpolator HOVER_EMPHASIZED_DECELERATE_INTERPOLATOR =
48             new PathInterpolator(0.05f, 0.7f, 0.1f, 1.0f);
49 
50     @VisibleForTesting protected static final float PRESSED_SCALE = 1.1f;
51     @VisibleForTesting protected static final float HOVERED_SCALE = 1.1f;
52     public static final int WHITE_SCRIM_ALPHA = 138;
53 
54     private static final float DISABLED_DESATURATION = 1f;
55     private static final float DISABLED_BRIGHTNESS = 0.5f;
56     protected static final int FULLY_OPAQUE = 255;
57 
58     public static final int CLICK_FEEDBACK_DURATION = 200;
59     public static final int HOVER_FEEDBACK_DURATION = 300;
60 
61     private static boolean sFlagHoverEnabled = false;
62 
63     protected final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG);
64     protected final Bitmap mBitmap;
65     protected final int mIconColor;
66 
67     @Nullable private ColorFilter mColorFilter;
68 
69     @VisibleForTesting protected boolean mIsPressed;
70     @VisibleForTesting protected boolean mIsHovered;
71     protected boolean mIsDisabled;
72     float mDisabledAlpha = 1f;
73 
74     // Animator and properties for the fast bitmap drawable's scale
75     @VisibleForTesting protected static final FloatProperty<FastBitmapDrawable> SCALE
76             = new FloatProperty<FastBitmapDrawable>("scale") {
77         @Override
78         public Float get(FastBitmapDrawable fastBitmapDrawable) {
79             return fastBitmapDrawable.mScale;
80         }
81 
82         @Override
83         public void setValue(FastBitmapDrawable fastBitmapDrawable, float value) {
84             fastBitmapDrawable.mScale = value;
85             fastBitmapDrawable.invalidateSelf();
86         }
87     };
88     @VisibleForTesting protected ObjectAnimator mScaleAnimation;
89     private float mScale = 1;
90     private int mAlpha = 255;
91 
92     private Drawable mBadge;
93 
FastBitmapDrawable(Bitmap b)94     public FastBitmapDrawable(Bitmap b) {
95         this(b, Color.TRANSPARENT);
96     }
97 
FastBitmapDrawable(BitmapInfo info)98     public FastBitmapDrawable(BitmapInfo info) {
99         this(info.icon, info.color);
100     }
101 
FastBitmapDrawable(Bitmap b, int iconColor)102     protected FastBitmapDrawable(Bitmap b, int iconColor) {
103         mBitmap = b;
104         mIconColor = iconColor;
105         setFilterBitmap(true);
106     }
107 
108     @Override
onBoundsChange(Rect bounds)109     protected void onBoundsChange(Rect bounds) {
110         super.onBoundsChange(bounds);
111         updateBadgeBounds(bounds);
112     }
113 
updateBadgeBounds(Rect bounds)114     private void updateBadgeBounds(Rect bounds) {
115         if (mBadge != null) {
116             setBadgeBounds(mBadge, bounds);
117         }
118     }
119 
120     @Override
draw(Canvas canvas)121     public final void draw(Canvas canvas) {
122         if (mScale != 1f) {
123             int count = canvas.save();
124             Rect bounds = getBounds();
125             canvas.scale(mScale, mScale, bounds.exactCenterX(), bounds.exactCenterY());
126             drawInternal(canvas, bounds);
127             if (mBadge != null) {
128                 mBadge.draw(canvas);
129             }
130             canvas.restoreToCount(count);
131         } else {
132             drawInternal(canvas, getBounds());
133             if (mBadge != null) {
134                 mBadge.draw(canvas);
135             }
136         }
137     }
138 
drawInternal(Canvas canvas, Rect bounds)139     protected void drawInternal(Canvas canvas, Rect bounds) {
140         canvas.drawBitmap(mBitmap, null, bounds, mPaint);
141     }
142 
143     /**
144      * Returns the primary icon color, slightly tinted white
145      */
getIconColor()146     public int getIconColor() {
147         int whiteScrim = setColorAlphaBound(Color.WHITE, WHITE_SCRIM_ALPHA);
148         return ColorUtils.compositeColors(whiteScrim, mIconColor);
149     }
150 
151     /**
152      * Returns if this represents a themed icon
153      */
isThemed()154     public boolean isThemed() {
155         return false;
156     }
157 
158     @Override
setColorFilter(ColorFilter cf)159     public void setColorFilter(ColorFilter cf) {
160         mColorFilter = cf;
161         updateFilter();
162     }
163 
164     @Override
getOpacity()165     public int getOpacity() {
166         return PixelFormat.TRANSLUCENT;
167     }
168 
169     @Override
setAlpha(int alpha)170     public void setAlpha(int alpha) {
171         if (mAlpha != alpha) {
172             mAlpha = alpha;
173             mPaint.setAlpha(alpha);
174             invalidateSelf();
175             if (mBadge != null) {
176                 mBadge.setAlpha(alpha);
177             }
178         }
179     }
180 
181     @Override
setFilterBitmap(boolean filterBitmap)182     public void setFilterBitmap(boolean filterBitmap) {
183         mPaint.setFilterBitmap(filterBitmap);
184         mPaint.setAntiAlias(filterBitmap);
185     }
186 
187     @Override
getAlpha()188     public int getAlpha() {
189         return mAlpha;
190     }
191 
resetScale()192     public void resetScale() {
193         if (mScaleAnimation != null) {
194             mScaleAnimation.cancel();
195             mScaleAnimation = null;
196         }
197         mScale = 1;
198         invalidateSelf();
199     }
200 
getAnimatedScale()201     public float getAnimatedScale() {
202         return mScaleAnimation == null ? 1 : mScale;
203     }
204 
205     @Override
getIntrinsicWidth()206     public int getIntrinsicWidth() {
207         return mBitmap.getWidth();
208     }
209 
210     @Override
getIntrinsicHeight()211     public int getIntrinsicHeight() {
212         return mBitmap.getHeight();
213     }
214 
215     @Override
getMinimumWidth()216     public int getMinimumWidth() {
217         return getBounds().width();
218     }
219 
220     @Override
getMinimumHeight()221     public int getMinimumHeight() {
222         return getBounds().height();
223     }
224 
225     @Override
isStateful()226     public boolean isStateful() {
227         return true;
228     }
229 
230     @Override
getColorFilter()231     public ColorFilter getColorFilter() {
232         return mPaint.getColorFilter();
233     }
234 
235     @Override
onStateChange(int[] state)236     protected boolean onStateChange(int[] state) {
237         boolean isPressed = false;
238         boolean isHovered = false;
239         for (int s : state) {
240             if (s == android.R.attr.state_pressed) {
241                 isPressed = true;
242                 break;
243             } else if (sFlagHoverEnabled && s == android.R.attr.state_hovered) {
244                 isHovered = true;
245                 // Do not break on hovered state, as pressed state should take precedence.
246             }
247         }
248         if (mIsPressed != isPressed || mIsHovered != isHovered) {
249             if (mScaleAnimation != null) {
250                 mScaleAnimation.cancel();
251             }
252 
253             float endScale = isPressed ? PRESSED_SCALE : (isHovered ? HOVERED_SCALE : 1f);
254             if (mScale != endScale) {
255                 if (isVisible()) {
256                     Interpolator interpolator =
257                             isPressed != mIsPressed ? (isPressed ? ACCEL : DEACCEL)
258                                     : HOVER_EMPHASIZED_DECELERATE_INTERPOLATOR;
259                     int duration =
260                             isPressed != mIsPressed ? CLICK_FEEDBACK_DURATION
261                                     : HOVER_FEEDBACK_DURATION;
262                     mScaleAnimation = ObjectAnimator.ofFloat(this, SCALE, endScale);
263                     mScaleAnimation.setDuration(duration);
264                     mScaleAnimation.setInterpolator(interpolator);
265                     mScaleAnimation.start();
266                 } else {
267                     mScale = endScale;
268                     invalidateSelf();
269                 }
270             }
271             mIsPressed = isPressed;
272             mIsHovered = isHovered;
273             return true;
274         }
275         return false;
276     }
277 
setIsDisabled(boolean isDisabled)278     public void setIsDisabled(boolean isDisabled) {
279         if (mIsDisabled != isDisabled) {
280             mIsDisabled = isDisabled;
281             updateFilter();
282         }
283     }
284 
isDisabled()285     protected boolean isDisabled() {
286         return mIsDisabled;
287     }
288 
setBadge(Drawable badge)289     public void setBadge(Drawable badge) {
290         if (mBadge != null) {
291             mBadge.setCallback(null);
292         }
293         mBadge = badge;
294         if (mBadge != null) {
295             mBadge.setCallback(this);
296         }
297         updateBadgeBounds(getBounds());
298         updateFilter();
299     }
300 
301     /**
302      * Updates the paint to reflect the current brightness and saturation.
303      */
updateFilter()304     protected void updateFilter() {
305         mPaint.setColorFilter(mIsDisabled ? getDisabledColorFilter(mDisabledAlpha) : mColorFilter);
306         if (mBadge != null) {
307             mBadge.setColorFilter(getColorFilter());
308         }
309         invalidateSelf();
310     }
311 
newConstantState()312     protected FastBitmapConstantState newConstantState() {
313         return new FastBitmapConstantState(mBitmap, mIconColor);
314     }
315 
316     @Override
getConstantState()317     public final ConstantState getConstantState() {
318         FastBitmapConstantState cs = newConstantState();
319         cs.mIsDisabled = mIsDisabled;
320         if (mBadge != null) {
321             cs.mBadgeConstantState = mBadge.getConstantState();
322         }
323         return cs;
324     }
325 
getDisabledColorFilter()326     public static ColorFilter getDisabledColorFilter() {
327         return getDisabledColorFilter(1);
328     }
329 
getDisabledColorFilter(float disabledAlpha)330     private static ColorFilter getDisabledColorFilter(float disabledAlpha) {
331         ColorMatrix tempBrightnessMatrix = new ColorMatrix();
332         ColorMatrix tempFilterMatrix = new ColorMatrix();
333 
334         tempFilterMatrix.setSaturation(1f - DISABLED_DESATURATION);
335         float scale = 1 - DISABLED_BRIGHTNESS;
336         int brightnessI =   (int) (255 * DISABLED_BRIGHTNESS);
337         float[] mat = tempBrightnessMatrix.getArray();
338         mat[0] = scale;
339         mat[6] = scale;
340         mat[12] = scale;
341         mat[4] = brightnessI;
342         mat[9] = brightnessI;
343         mat[14] = brightnessI;
344         mat[18] = disabledAlpha;
345         tempFilterMatrix.preConcat(tempBrightnessMatrix);
346         return new ColorMatrixColorFilter(tempFilterMatrix);
347     }
348 
getDisabledColor(int color)349     protected static final int getDisabledColor(int color) {
350         int component = (Color.red(color) + Color.green(color) + Color.blue(color)) / 3;
351         float scale = 1 - DISABLED_BRIGHTNESS;
352         int brightnessI = (int) (255 * DISABLED_BRIGHTNESS);
353         component = Math.min(Math.round(scale * component + brightnessI), FULLY_OPAQUE);
354         return Color.rgb(component, component, component);
355     }
356 
357     /**
358      * Sets the bounds for the badge drawable based on the main icon bounds
359      */
setBadgeBounds(Drawable badge, Rect iconBounds)360     public static void setBadgeBounds(Drawable badge, Rect iconBounds) {
361         int size = getBadgeSizeForIconSize(iconBounds.width());
362         badge.setBounds(iconBounds.right - size, iconBounds.bottom - size,
363                 iconBounds.right, iconBounds.bottom);
364     }
365 
366     @Override
invalidateDrawable(Drawable who)367     public void invalidateDrawable(Drawable who) {
368         if (who == mBadge) {
369             invalidateSelf();
370         }
371     }
372 
373     @Override
scheduleDrawable(Drawable who, Runnable what, long when)374     public void scheduleDrawable(Drawable who, Runnable what, long when) {
375         if (who == mBadge) {
376             scheduleSelf(what, when);
377         }
378     }
379 
380     @Override
unscheduleDrawable(Drawable who, Runnable what)381     public void unscheduleDrawable(Drawable who, Runnable what) {
382         unscheduleSelf(what);
383     }
384 
385     /**
386      * Sets whether hover state functionality is enabled.
387      */
setFlagHoverEnabled(boolean isFlagHoverEnabled)388     public static void setFlagHoverEnabled(boolean isFlagHoverEnabled) {
389         sFlagHoverEnabled = isFlagHoverEnabled;
390     }
391 
392     protected static class FastBitmapConstantState extends ConstantState {
393         protected final Bitmap mBitmap;
394         protected final int mIconColor;
395 
396         // These are initialized later so that subclasses don't need to
397         // pass everything in constructor
398         protected boolean mIsDisabled;
399         private ConstantState mBadgeConstantState;
400 
FastBitmapConstantState(Bitmap bitmap, int color)401         public FastBitmapConstantState(Bitmap bitmap, int color) {
402             mBitmap = bitmap;
403             mIconColor = color;
404         }
405 
createDrawable()406         protected FastBitmapDrawable createDrawable() {
407             return new FastBitmapDrawable(mBitmap, mIconColor);
408         }
409 
410         @Override
newDrawable()411         public final FastBitmapDrawable newDrawable() {
412             FastBitmapDrawable drawable = createDrawable();
413             drawable.setIsDisabled(mIsDisabled);
414             if (mBadgeConstantState != null) {
415                 drawable.setBadge(mBadgeConstantState.newDrawable());
416             }
417             return drawable;
418         }
419 
420         @Override
getChangingConfigurations()421         public int getChangingConfigurations() {
422             return 0;
423         }
424     }
425 }
426