Home | History | Annotate | Download | only in launcher3
      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 
     21 import android.animation.ObjectAnimator;
     22 import android.graphics.Bitmap;
     23 import android.graphics.Canvas;
     24 import android.graphics.Color;
     25 import android.graphics.ColorFilter;
     26 import android.graphics.ColorMatrix;
     27 import android.graphics.ColorMatrixColorFilter;
     28 import android.graphics.Paint;
     29 import android.graphics.PixelFormat;
     30 import android.graphics.PorterDuff;
     31 import android.graphics.PorterDuffColorFilter;
     32 import android.graphics.Rect;
     33 import android.graphics.drawable.Drawable;
     34 import android.util.Property;
     35 import android.util.SparseArray;
     36 
     37 import com.android.launcher3.graphics.BitmapInfo;
     38 
     39 public class FastBitmapDrawable extends Drawable {
     40 
     41     private static final float PRESSED_SCALE = 1.1f;
     42 
     43     private static final float DISABLED_DESATURATION = 1f;
     44     private static final float DISABLED_BRIGHTNESS = 0.5f;
     45 
     46     public static final int CLICK_FEEDBACK_DURATION = 200;
     47 
     48     // Since we don't need 256^2 values for combinations of both the brightness and saturation, we
     49     // reduce the value space to a smaller value V, which reduces the number of cached
     50     // ColorMatrixColorFilters that we need to keep to V^2
     51     private static final int REDUCED_FILTER_VALUE_SPACE = 48;
     52 
     53     // A cache of ColorFilters for optimizing brightness and saturation animations
     54     private static final SparseArray<ColorFilter> sCachedFilter = new SparseArray<>();
     55 
     56     // Temporary matrices used for calculation
     57     private static final ColorMatrix sTempBrightnessMatrix = new ColorMatrix();
     58     private static final ColorMatrix sTempFilterMatrix = new ColorMatrix();
     59 
     60     protected final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG);
     61     protected Bitmap mBitmap;
     62     protected final int mIconColor;
     63 
     64     private boolean mIsPressed;
     65     private boolean mIsDisabled;
     66 
     67     // Animator and properties for the fast bitmap drawable's scale
     68     private static final Property<FastBitmapDrawable, Float> SCALE
     69             = new Property<FastBitmapDrawable, Float>(Float.TYPE, "scale") {
     70         @Override
     71         public Float get(FastBitmapDrawable fastBitmapDrawable) {
     72             return fastBitmapDrawable.mScale;
     73         }
     74 
     75         @Override
     76         public void set(FastBitmapDrawable fastBitmapDrawable, Float value) {
     77             fastBitmapDrawable.mScale = value;
     78             fastBitmapDrawable.invalidateSelf();
     79         }
     80     };
     81     private ObjectAnimator mScaleAnimation;
     82     private float mScale = 1;
     83 
     84 
     85     // The saturation and brightness are values that are mapped to REDUCED_FILTER_VALUE_SPACE and
     86     // as a result, can be used to compose the key for the cached ColorMatrixColorFilters
     87     private int mDesaturation = 0;
     88     private int mBrightness = 0;
     89     private int mAlpha = 255;
     90     private int mPrevUpdateKey = Integer.MAX_VALUE;
     91 
     92     public FastBitmapDrawable(Bitmap b) {
     93         this(b, Color.TRANSPARENT);
     94     }
     95 
     96     public FastBitmapDrawable(BitmapInfo info) {
     97         this(info.icon, info.color);
     98     }
     99 
    100     public FastBitmapDrawable(ItemInfoWithIcon info) {
    101         this(info.iconBitmap, info.iconColor);
    102     }
    103 
    104     protected FastBitmapDrawable(Bitmap b, int iconColor) {
    105         mBitmap = b;
    106         mIconColor = iconColor;
    107         setFilterBitmap(true);
    108     }
    109 
    110     @Override
    111     public final void draw(Canvas canvas) {
    112         if (mScaleAnimation != null) {
    113             int count = canvas.save();
    114             Rect bounds = getBounds();
    115             canvas.scale(mScale, mScale, bounds.exactCenterX(), bounds.exactCenterY());
    116             drawInternal(canvas, bounds);
    117             canvas.restoreToCount(count);
    118         } else {
    119             drawInternal(canvas, getBounds());
    120         }
    121     }
    122 
    123     protected void drawInternal(Canvas canvas, Rect bounds) {
    124         canvas.drawBitmap(mBitmap, null, bounds, mPaint);
    125     }
    126 
    127     @Override
    128     public void setColorFilter(ColorFilter cf) {
    129         // No op
    130     }
    131 
    132     @Override
    133     public int getOpacity() {
    134         return PixelFormat.TRANSLUCENT;
    135     }
    136 
    137     @Override
    138     public void setAlpha(int alpha) {
    139         mAlpha = alpha;
    140         mPaint.setAlpha(alpha);
    141     }
    142 
    143     @Override
    144     public void setFilterBitmap(boolean filterBitmap) {
    145         mPaint.setFilterBitmap(filterBitmap);
    146         mPaint.setAntiAlias(filterBitmap);
    147     }
    148 
    149     public int getAlpha() {
    150         return mAlpha;
    151     }
    152 
    153     public float getAnimatedScale() {
    154         return mScaleAnimation == null ? 1 : mScale;
    155     }
    156 
    157     @Override
    158     public int getIntrinsicWidth() {
    159         return mBitmap.getWidth();
    160     }
    161 
    162     @Override
    163     public int getIntrinsicHeight() {
    164         return mBitmap.getHeight();
    165     }
    166 
    167     @Override
    168     public int getMinimumWidth() {
    169         return getBounds().width();
    170     }
    171 
    172     @Override
    173     public int getMinimumHeight() {
    174         return getBounds().height();
    175     }
    176 
    177     @Override
    178     public boolean isStateful() {
    179         return true;
    180     }
    181 
    182     @Override
    183     public ColorFilter getColorFilter() {
    184         return mPaint.getColorFilter();
    185     }
    186 
    187     @Override
    188     protected boolean onStateChange(int[] state) {
    189         boolean isPressed = false;
    190         for (int s : state) {
    191             if (s == android.R.attr.state_pressed) {
    192                 isPressed = true;
    193                 break;
    194             }
    195         }
    196         if (mIsPressed != isPressed) {
    197             mIsPressed = isPressed;
    198 
    199             if (mScaleAnimation != null) {
    200                 mScaleAnimation.cancel();
    201                 mScaleAnimation = null;
    202             }
    203 
    204             if (mIsPressed) {
    205                 // Animate when going to pressed state
    206                 mScaleAnimation = ObjectAnimator.ofFloat(this, SCALE, PRESSED_SCALE);
    207                 mScaleAnimation.setDuration(CLICK_FEEDBACK_DURATION);
    208                 mScaleAnimation.setInterpolator(ACCEL);
    209                 mScaleAnimation.start();
    210             } else {
    211                 mScale = 1f;
    212                 invalidateSelf();
    213             }
    214             return true;
    215         }
    216         return false;
    217     }
    218 
    219     private void invalidateDesaturationAndBrightness() {
    220         setDesaturation(mIsDisabled ? DISABLED_DESATURATION : 0);
    221         setBrightness(mIsDisabled ? DISABLED_BRIGHTNESS : 0);
    222     }
    223 
    224     public void setIsDisabled(boolean isDisabled) {
    225         if (mIsDisabled != isDisabled) {
    226             mIsDisabled = isDisabled;
    227             invalidateDesaturationAndBrightness();
    228         }
    229     }
    230 
    231     /**
    232      * Sets the saturation of this icon, 0 [full color] -> 1 [desaturated]
    233      */
    234     private void setDesaturation(float desaturation) {
    235         int newDesaturation = (int) Math.floor(desaturation * REDUCED_FILTER_VALUE_SPACE);
    236         if (mDesaturation != newDesaturation) {
    237             mDesaturation = newDesaturation;
    238             updateFilter();
    239         }
    240     }
    241 
    242     public float getDesaturation() {
    243         return (float) mDesaturation / REDUCED_FILTER_VALUE_SPACE;
    244     }
    245 
    246     /**
    247      * Sets the brightness of this icon, 0 [no add. brightness] -> 1 [2bright2furious]
    248      */
    249     private void setBrightness(float brightness) {
    250         int newBrightness = (int) Math.floor(brightness * REDUCED_FILTER_VALUE_SPACE);
    251         if (mBrightness != newBrightness) {
    252             mBrightness = newBrightness;
    253             updateFilter();
    254         }
    255     }
    256 
    257     private float getBrightness() {
    258         return (float) mBrightness / REDUCED_FILTER_VALUE_SPACE;
    259     }
    260 
    261     /**
    262      * Updates the paint to reflect the current brightness and saturation.
    263      */
    264     private void updateFilter() {
    265         boolean usePorterDuffFilter = false;
    266         int key = -1;
    267         if (mDesaturation > 0) {
    268             key = (mDesaturation << 16) | mBrightness;
    269         } else if (mBrightness > 0) {
    270             // Compose a key with a fully saturated icon if we are just animating brightness
    271             key = (1 << 16) | mBrightness;
    272 
    273             // We found that in L, ColorFilters cause drawing artifacts with shadows baked into
    274             // icons, so just use a PorterDuff filter when we aren't animating saturation
    275             usePorterDuffFilter = true;
    276         }
    277 
    278         // Debounce multiple updates on the same frame
    279         if (key == mPrevUpdateKey) {
    280             return;
    281         }
    282         mPrevUpdateKey = key;
    283 
    284         if (key != -1) {
    285             ColorFilter filter = sCachedFilter.get(key);
    286             if (filter == null) {
    287                 float brightnessF = getBrightness();
    288                 int brightnessI = (int) (255 * brightnessF);
    289                 if (usePorterDuffFilter) {
    290                     filter = new PorterDuffColorFilter(Color.argb(brightnessI, 255, 255, 255),
    291                             PorterDuff.Mode.SRC_ATOP);
    292                 } else {
    293                     float saturationF = 1f - getDesaturation();
    294                     sTempFilterMatrix.setSaturation(saturationF);
    295                     if (mBrightness > 0) {
    296                         // Brightness: C-new = C-old*(1-amount) + amount
    297                         float scale = 1f - brightnessF;
    298                         float[] mat = sTempBrightnessMatrix.getArray();
    299                         mat[0] = scale;
    300                         mat[6] = scale;
    301                         mat[12] = scale;
    302                         mat[4] = brightnessI;
    303                         mat[9] = brightnessI;
    304                         mat[14] = brightnessI;
    305                         sTempFilterMatrix.preConcat(sTempBrightnessMatrix);
    306                     }
    307                     filter = new ColorMatrixColorFilter(sTempFilterMatrix);
    308                 }
    309                 sCachedFilter.append(key, filter);
    310             }
    311             mPaint.setColorFilter(filter);
    312         } else {
    313             mPaint.setColorFilter(null);
    314         }
    315         invalidateSelf();
    316     }
    317 
    318     @Override
    319     public ConstantState getConstantState() {
    320         return new MyConstantState(mBitmap, mIconColor);
    321     }
    322 
    323     protected static class MyConstantState extends ConstantState {
    324         protected final Bitmap mBitmap;
    325         protected final int mIconColor;
    326 
    327         public MyConstantState(Bitmap bitmap, int color) {
    328             mBitmap = bitmap;
    329             mIconColor = color;
    330         }
    331 
    332         @Override
    333         public Drawable newDrawable() {
    334             return new FastBitmapDrawable(mBitmap, mIconColor);
    335         }
    336 
    337         @Override
    338         public int getChangingConfigurations() {
    339             return 0;
    340         }
    341     }
    342 }
    343