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