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;
18 
19 import static com.android.launcher3.anim.Interpolators.ACCEL;
20 import static com.android.launcher3.anim.Interpolators.DEACCEL;
21 
22 import android.animation.ObjectAnimator;
23 import android.content.Context;
24 import android.graphics.Bitmap;
25 import android.graphics.Canvas;
26 import android.graphics.Color;
27 import android.graphics.ColorFilter;
28 import android.graphics.ColorMatrix;
29 import android.graphics.ColorMatrixColorFilter;
30 import android.graphics.Paint;
31 import android.graphics.PixelFormat;
32 import android.graphics.Rect;
33 import android.graphics.drawable.Drawable;
34 import android.util.Property;
35 
36 import com.android.launcher3.graphics.PlaceHolderIconDrawable;
37 import com.android.launcher3.icons.BitmapInfo;
38 import com.android.launcher3.model.data.ItemInfoWithIcon;
39 import com.android.launcher3.util.Themes;
40 
41 
42 public class FastBitmapDrawable extends Drawable {
43 
44     private static final float PRESSED_SCALE = 1.1f;
45 
46     private static final float DISABLED_DESATURATION = 1f;
47     private static final float DISABLED_BRIGHTNESS = 0.5f;
48 
49     public static final int CLICK_FEEDBACK_DURATION = 200;
50 
51     private static ColorFilter sDisabledFColorFilter;
52 
53     protected final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG);
54     protected Bitmap mBitmap;
55     protected final int mIconColor;
56 
57     private boolean mIsPressed;
58     private boolean mIsDisabled;
59     private float mDisabledAlpha = 1f;
60 
61     // Animator and properties for the fast bitmap drawable's scale
62     private static final Property<FastBitmapDrawable, Float> SCALE
63             = new Property<FastBitmapDrawable, Float>(Float.TYPE, "scale") {
64         @Override
65         public Float get(FastBitmapDrawable fastBitmapDrawable) {
66             return fastBitmapDrawable.mScale;
67         }
68 
69         @Override
70         public void set(FastBitmapDrawable fastBitmapDrawable, Float value) {
71             fastBitmapDrawable.mScale = value;
72             fastBitmapDrawable.invalidateSelf();
73         }
74     };
75     private ObjectAnimator mScaleAnimation;
76     private float mScale = 1;
77 
78     private int mAlpha = 255;
79 
FastBitmapDrawable(Bitmap b)80     public FastBitmapDrawable(Bitmap b) {
81         this(b, Color.TRANSPARENT);
82     }
83 
FastBitmapDrawable(BitmapInfo info)84     public FastBitmapDrawable(BitmapInfo info) {
85         this(info.icon, info.color);
86     }
87 
FastBitmapDrawable(Bitmap b, int iconColor)88     protected FastBitmapDrawable(Bitmap b, int iconColor) {
89         this(b, iconColor, false);
90     }
91 
FastBitmapDrawable(Bitmap b, int iconColor, boolean isDisabled)92     protected FastBitmapDrawable(Bitmap b, int iconColor, boolean isDisabled) {
93         mBitmap = b;
94         mIconColor = iconColor;
95         setFilterBitmap(true);
96         setIsDisabled(isDisabled);
97     }
98 
99     @Override
draw(Canvas canvas)100     public final void draw(Canvas canvas) {
101         if (mScale != 1f) {
102             int count = canvas.save();
103             Rect bounds = getBounds();
104             canvas.scale(mScale, mScale, bounds.exactCenterX(), bounds.exactCenterY());
105             drawInternal(canvas, bounds);
106             canvas.restoreToCount(count);
107         } else {
108             drawInternal(canvas, getBounds());
109         }
110     }
111 
drawInternal(Canvas canvas, Rect bounds)112     protected void drawInternal(Canvas canvas, Rect bounds) {
113         canvas.drawBitmap(mBitmap, null, bounds, mPaint);
114     }
115 
116     @Override
setColorFilter(ColorFilter cf)117     public void setColorFilter(ColorFilter cf) {
118         // No op
119     }
120 
121     @Override
getOpacity()122     public int getOpacity() {
123         return PixelFormat.TRANSLUCENT;
124     }
125 
126     @Override
setAlpha(int alpha)127     public void setAlpha(int alpha) {
128         if (mAlpha != alpha) {
129             mAlpha = alpha;
130             mPaint.setAlpha(alpha);
131             invalidateSelf();
132         }
133     }
134 
135     @Override
setFilterBitmap(boolean filterBitmap)136     public void setFilterBitmap(boolean filterBitmap) {
137         mPaint.setFilterBitmap(filterBitmap);
138         mPaint.setAntiAlias(filterBitmap);
139     }
140 
getAlpha()141     public int getAlpha() {
142         return mAlpha;
143     }
144 
setScale(float scale)145     public void setScale(float scale) {
146         if (mScaleAnimation != null) {
147             mScaleAnimation.cancel();
148             mScaleAnimation = null;
149         }
150         mScale = scale;
151         invalidateSelf();
152     }
153 
getAnimatedScale()154     public float getAnimatedScale() {
155         return mScaleAnimation == null ? 1 : mScale;
156     }
157 
getScale()158     public float getScale() {
159         return mScale;
160     }
161 
162     @Override
getIntrinsicWidth()163     public int getIntrinsicWidth() {
164         return mBitmap.getWidth();
165     }
166 
167     @Override
getIntrinsicHeight()168     public int getIntrinsicHeight() {
169         return mBitmap.getHeight();
170     }
171 
172     @Override
getMinimumWidth()173     public int getMinimumWidth() {
174         return getBounds().width();
175     }
176 
177     @Override
getMinimumHeight()178     public int getMinimumHeight() {
179         return getBounds().height();
180     }
181 
182     @Override
isStateful()183     public boolean isStateful() {
184         return true;
185     }
186 
187     @Override
getColorFilter()188     public ColorFilter getColorFilter() {
189         return mPaint.getColorFilter();
190     }
191 
192     @Override
onStateChange(int[] state)193     protected boolean onStateChange(int[] state) {
194         boolean isPressed = false;
195         for (int s : state) {
196             if (s == android.R.attr.state_pressed) {
197                 isPressed = true;
198                 break;
199             }
200         }
201         if (mIsPressed != isPressed) {
202             mIsPressed = isPressed;
203 
204             if (mScaleAnimation != null) {
205                 mScaleAnimation.cancel();
206                 mScaleAnimation = null;
207             }
208 
209             if (mIsPressed) {
210                 // Animate when going to pressed state
211                 mScaleAnimation = ObjectAnimator.ofFloat(this, SCALE, PRESSED_SCALE);
212                 mScaleAnimation.setDuration(CLICK_FEEDBACK_DURATION);
213                 mScaleAnimation.setInterpolator(ACCEL);
214                 mScaleAnimation.start();
215             } else {
216                 if (isVisible()) {
217                     mScaleAnimation = ObjectAnimator.ofFloat(this, SCALE, 1f);
218                     mScaleAnimation.setDuration(CLICK_FEEDBACK_DURATION);
219                     mScaleAnimation.setInterpolator(DEACCEL);
220                     mScaleAnimation.start();
221                 } else {
222                     mScale = 1f;
223                     invalidateSelf();
224                 }
225             }
226             return true;
227         }
228         return false;
229     }
230 
setIsDisabled(boolean isDisabled)231     public void setIsDisabled(boolean isDisabled) {
232         if (mIsDisabled != isDisabled) {
233             mIsDisabled = isDisabled;
234             updateFilter();
235         }
236     }
237 
isDisabled()238     protected boolean isDisabled() {
239         return mIsDisabled;
240     }
241 
getDisabledColorFilter()242     private ColorFilter getDisabledColorFilter() {
243         if (sDisabledFColorFilter == null) {
244             ColorMatrix tempBrightnessMatrix = new ColorMatrix();
245             ColorMatrix tempFilterMatrix = new ColorMatrix();
246 
247             tempFilterMatrix.setSaturation(1f - DISABLED_DESATURATION);
248             float scale = 1 - DISABLED_BRIGHTNESS;
249             int brightnessI =   (int) (255 * DISABLED_BRIGHTNESS);
250             float[] mat = tempBrightnessMatrix.getArray();
251             mat[0] = scale;
252             mat[6] = scale;
253             mat[12] = scale;
254             mat[4] = brightnessI;
255             mat[9] = brightnessI;
256             mat[14] = brightnessI;
257             mat[18] = mDisabledAlpha;
258             tempFilterMatrix.preConcat(tempBrightnessMatrix);
259             sDisabledFColorFilter = new ColorMatrixColorFilter(tempFilterMatrix);
260         }
261         return sDisabledFColorFilter;
262     }
263 
264     /**
265      * Updates the paint to reflect the current brightness and saturation.
266      */
updateFilter()267     protected void updateFilter() {
268         mPaint.setColorFilter(mIsDisabled ? getDisabledColorFilter() : null);
269         invalidateSelf();
270     }
271 
272     @Override
getConstantState()273     public ConstantState getConstantState() {
274         return new MyConstantState(mBitmap, mIconColor, mIsDisabled);
275     }
276 
277     protected static class MyConstantState extends ConstantState {
278         protected final Bitmap mBitmap;
279         protected final int mIconColor;
280         protected final boolean mIsDisabled;
281 
MyConstantState(Bitmap bitmap, int color, boolean isDisabled)282         public MyConstantState(Bitmap bitmap, int color, boolean isDisabled) {
283             mBitmap = bitmap;
284             mIconColor = color;
285             mIsDisabled = isDisabled;
286         }
287 
288         @Override
newDrawable()289         public FastBitmapDrawable newDrawable() {
290             return new FastBitmapDrawable(mBitmap, mIconColor, mIsDisabled);
291         }
292 
293         @Override
getChangingConfigurations()294         public int getChangingConfigurations() {
295             return 0;
296         }
297     }
298 
299     /**
300      * Interface to be implemented by custom {@link BitmapInfo} to handle drawable construction
301      */
302     public interface Factory {
303 
304         /**
305          * Called to create a new drawable
306          */
newDrawable()307         FastBitmapDrawable newDrawable();
308     }
309 
310     /**
311      * Returns a FastBitmapDrawable with the icon.
312      */
newIcon(Context context, ItemInfoWithIcon info)313     public static FastBitmapDrawable newIcon(Context context, ItemInfoWithIcon info) {
314         FastBitmapDrawable drawable = newIcon(context, info.bitmap);
315         drawable.setIsDisabled(info.isDisabled());
316         return drawable;
317     }
318 
319     /**
320      * Creates a drawable for the provided BitmapInfo
321      */
newIcon(Context context, BitmapInfo info)322     public static FastBitmapDrawable newIcon(Context context, BitmapInfo info) {
323         final FastBitmapDrawable drawable;
324         if (info instanceof Factory) {
325             drawable = ((Factory) info).newDrawable();
326         } else if (info.isLowRes()) {
327             drawable = new PlaceHolderIconDrawable(info, context);
328         } else {
329             drawable = new FastBitmapDrawable(info);
330         }
331         drawable.mDisabledAlpha = Themes.getFloat(context, R.attr.disabledIconAlpha, 1f);
332         return drawable;
333     }
334 }
335