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 android.animation.ObjectAnimator; 20 import android.graphics.Bitmap; 21 import android.graphics.Canvas; 22 import android.graphics.Color; 23 import android.graphics.ColorFilter; 24 import android.graphics.ColorMatrix; 25 import android.graphics.ColorMatrixColorFilter; 26 import android.graphics.Paint; 27 import android.graphics.PixelFormat; 28 import android.graphics.Rect; 29 import android.graphics.drawable.Drawable; 30 import android.util.Property; 31 import android.view.animation.AccelerateInterpolator; 32 import android.view.animation.DecelerateInterpolator; 33 import android.view.animation.Interpolator; 34 35 import androidx.annotation.Nullable; 36 37 public class FastBitmapDrawable extends Drawable { 38 39 private static final Interpolator ACCEL = new AccelerateInterpolator(); 40 private static final Interpolator DEACCEL = new DecelerateInterpolator(); 41 42 private static final float PRESSED_SCALE = 1.1f; 43 44 private static final float DISABLED_DESATURATION = 1f; 45 private static final float DISABLED_BRIGHTNESS = 0.5f; 46 47 public static final int CLICK_FEEDBACK_DURATION = 200; 48 49 private static ColorFilter sDisabledFColorFilter; 50 51 protected final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG); 52 protected Bitmap mBitmap; 53 protected final int mIconColor; 54 55 @Nullable private ColorFilter mColorFilter; 56 57 private boolean mIsPressed; 58 protected boolean mIsDisabled; 59 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 /** 117 * Returns the primary icon color 118 */ getIconColor()119 public int getIconColor() { 120 return mIconColor; 121 } 122 123 /** 124 * Returns if this represents a themed icon 125 */ isThemed()126 public boolean isThemed() { 127 return false; 128 } 129 130 @Override setColorFilter(ColorFilter cf)131 public void setColorFilter(ColorFilter cf) { 132 mColorFilter = cf; 133 updateFilter(); 134 } 135 136 @Override getOpacity()137 public int getOpacity() { 138 return PixelFormat.TRANSLUCENT; 139 } 140 141 @Override setAlpha(int alpha)142 public void setAlpha(int alpha) { 143 if (mAlpha != alpha) { 144 mAlpha = alpha; 145 mPaint.setAlpha(alpha); 146 invalidateSelf(); 147 } 148 } 149 150 @Override setFilterBitmap(boolean filterBitmap)151 public void setFilterBitmap(boolean filterBitmap) { 152 mPaint.setFilterBitmap(filterBitmap); 153 mPaint.setAntiAlias(filterBitmap); 154 } 155 156 @Override getAlpha()157 public int getAlpha() { 158 return mAlpha; 159 } 160 resetScale()161 public void resetScale() { 162 if (mScaleAnimation != null) { 163 mScaleAnimation.cancel(); 164 mScaleAnimation = null; 165 } 166 mScale = 1; 167 invalidateSelf(); 168 } 169 getAnimatedScale()170 public float getAnimatedScale() { 171 return mScaleAnimation == null ? 1 : mScale; 172 } 173 174 @Override getIntrinsicWidth()175 public int getIntrinsicWidth() { 176 return mBitmap.getWidth(); 177 } 178 179 @Override getIntrinsicHeight()180 public int getIntrinsicHeight() { 181 return mBitmap.getHeight(); 182 } 183 184 @Override getMinimumWidth()185 public int getMinimumWidth() { 186 return getBounds().width(); 187 } 188 189 @Override getMinimumHeight()190 public int getMinimumHeight() { 191 return getBounds().height(); 192 } 193 194 @Override isStateful()195 public boolean isStateful() { 196 return true; 197 } 198 199 @Override getColorFilter()200 public ColorFilter getColorFilter() { 201 return mPaint.getColorFilter(); 202 } 203 204 @Override onStateChange(int[] state)205 protected boolean onStateChange(int[] state) { 206 boolean isPressed = false; 207 for (int s : state) { 208 if (s == android.R.attr.state_pressed) { 209 isPressed = true; 210 break; 211 } 212 } 213 if (mIsPressed != isPressed) { 214 mIsPressed = isPressed; 215 216 if (mScaleAnimation != null) { 217 mScaleAnimation.cancel(); 218 mScaleAnimation = null; 219 } 220 221 if (mIsPressed) { 222 // Animate when going to pressed state 223 mScaleAnimation = ObjectAnimator.ofFloat(this, SCALE, PRESSED_SCALE); 224 mScaleAnimation.setDuration(CLICK_FEEDBACK_DURATION); 225 mScaleAnimation.setInterpolator(ACCEL); 226 mScaleAnimation.start(); 227 } else { 228 if (isVisible()) { 229 mScaleAnimation = ObjectAnimator.ofFloat(this, SCALE, 1f); 230 mScaleAnimation.setDuration(CLICK_FEEDBACK_DURATION); 231 mScaleAnimation.setInterpolator(DEACCEL); 232 mScaleAnimation.start(); 233 } else { 234 mScale = 1f; 235 invalidateSelf(); 236 } 237 } 238 return true; 239 } 240 return false; 241 } 242 setIsDisabled(boolean isDisabled)243 public void setIsDisabled(boolean isDisabled) { 244 if (mIsDisabled != isDisabled) { 245 mIsDisabled = isDisabled; 246 updateFilter(); 247 } 248 } 249 isDisabled()250 protected boolean isDisabled() { 251 return mIsDisabled; 252 } 253 getDisabledColorFilter()254 private ColorFilter getDisabledColorFilter() { 255 if (sDisabledFColorFilter == null) { 256 sDisabledFColorFilter = getDisabledFColorFilter(mDisabledAlpha); 257 } 258 return sDisabledFColorFilter; 259 } 260 261 /** 262 * Updates the paint to reflect the current brightness and saturation. 263 */ updateFilter()264 protected void updateFilter() { 265 mPaint.setColorFilter(mIsDisabled ? getDisabledColorFilter() : mColorFilter); 266 invalidateSelf(); 267 } 268 269 @Override getConstantState()270 public ConstantState getConstantState() { 271 return new FastBitmapConstantState(mBitmap, mIconColor, mIsDisabled); 272 } 273 getDisabledFColorFilter(float disabledAlpha)274 public static ColorFilter getDisabledFColorFilter(float disabledAlpha) { 275 ColorMatrix tempBrightnessMatrix = new ColorMatrix(); 276 ColorMatrix tempFilterMatrix = new ColorMatrix(); 277 278 tempFilterMatrix.setSaturation(1f - DISABLED_DESATURATION); 279 float scale = 1 - DISABLED_BRIGHTNESS; 280 int brightnessI = (int) (255 * DISABLED_BRIGHTNESS); 281 float[] mat = tempBrightnessMatrix.getArray(); 282 mat[0] = scale; 283 mat[6] = scale; 284 mat[12] = scale; 285 mat[4] = brightnessI; 286 mat[9] = brightnessI; 287 mat[14] = brightnessI; 288 mat[18] = disabledAlpha; 289 tempFilterMatrix.preConcat(tempBrightnessMatrix); 290 return new ColorMatrixColorFilter(tempBrightnessMatrix); 291 } 292 293 protected static class FastBitmapConstantState extends ConstantState { 294 protected final Bitmap mBitmap; 295 protected final int mIconColor; 296 protected final boolean mIsDisabled; 297 FastBitmapConstantState(Bitmap bitmap, int color, boolean isDisabled)298 public FastBitmapConstantState(Bitmap bitmap, int color, boolean isDisabled) { 299 mBitmap = bitmap; 300 mIconColor = color; 301 mIsDisabled = isDisabled; 302 } 303 304 @Override newDrawable()305 public FastBitmapDrawable newDrawable() { 306 return new FastBitmapDrawable(mBitmap, mIconColor, mIsDisabled); 307 } 308 309 @Override getChangingConfigurations()310 public int getChangingConfigurations() { 311 return 0; 312 } 313 } 314 } 315