Home | History | Annotate | Download | only in graphics
      1 /*
      2  * Copyright (C) 2017 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.graphics;
     18 
     19 import android.annotation.TargetApi;
     20 import android.content.res.Resources;
     21 import android.content.res.TypedArray;
     22 import android.graphics.Bitmap;
     23 import android.graphics.BlurMaskFilter;
     24 import android.graphics.Canvas;
     25 import android.graphics.Color;
     26 import android.graphics.ColorFilter;
     27 import android.graphics.Paint;
     28 import android.graphics.PixelFormat;
     29 import android.graphics.Rect;
     30 import android.graphics.drawable.Drawable;
     31 import android.os.Build;
     32 import android.util.AttributeSet;
     33 
     34 import com.android.launcher3.R;
     35 import com.android.launcher3.Utilities;
     36 
     37 import org.xmlpull.v1.XmlPullParser;
     38 import org.xmlpull.v1.XmlPullParserException;
     39 
     40 import java.io.IOException;
     41 
     42 /**
     43  * A drawable which adds shadow around a child drawable.
     44  */
     45 @TargetApi(Build.VERSION_CODES.O)
     46 public class ShadowDrawable extends Drawable {
     47 
     48     private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
     49 
     50     private final ShadowDrawableState mState;
     51 
     52     @SuppressWarnings("unused")
     53     public ShadowDrawable() {
     54         this(new ShadowDrawableState());
     55     }
     56 
     57     private ShadowDrawable(ShadowDrawableState state) {
     58         mState = state;
     59     }
     60 
     61     @Override
     62     public void draw(Canvas canvas) {
     63         Rect bounds = getBounds();
     64         if (bounds.isEmpty()) {
     65             return;
     66         }
     67         if (mState.mLastDrawnBitmap == null) {
     68             regenerateBitmapCache();
     69         }
     70         canvas.drawBitmap(mState.mLastDrawnBitmap, null, bounds, mPaint);
     71     }
     72 
     73     @Override
     74     public void setAlpha(int alpha) {
     75         mPaint.setAlpha(alpha);
     76         invalidateSelf();
     77     }
     78 
     79     @Override
     80     public void setColorFilter(ColorFilter colorFilter) {
     81         mPaint.setColorFilter(colorFilter);
     82         invalidateSelf();
     83     }
     84 
     85     @Override
     86     public ConstantState getConstantState() {
     87         return mState;
     88     }
     89 
     90     @Override
     91     public int getOpacity() {
     92         return PixelFormat.TRANSLUCENT;
     93     }
     94 
     95     @Override
     96     public int getIntrinsicHeight() {
     97         return mState.mIntrinsicHeight;
     98     }
     99 
    100     @Override
    101     public int getIntrinsicWidth() {
    102         return mState.mIntrinsicWidth;
    103     }
    104 
    105     @Override
    106     public boolean canApplyTheme() {
    107         return mState.canApplyTheme();
    108     }
    109 
    110     @Override
    111     public void applyTheme(Resources.Theme t) {
    112         TypedArray ta = t.obtainStyledAttributes(new int[] {R.attr.isWorkspaceDarkText});
    113         boolean isDark = ta.getBoolean(0, false);
    114         ta.recycle();
    115         if (mState.mIsDark != isDark) {
    116             mState.mIsDark = isDark;
    117             mState.mLastDrawnBitmap = null;
    118             invalidateSelf();
    119         }
    120     }
    121 
    122     private void regenerateBitmapCache() {
    123         Bitmap bitmap = Bitmap.createBitmap(mState.mIntrinsicWidth, mState.mIntrinsicHeight,
    124                 Bitmap.Config.ARGB_8888);
    125         Canvas canvas = new Canvas(bitmap);
    126 
    127         // Call mutate, so that the pixel allocation by the underlying vector drawable is cleared.
    128         Drawable d = mState.mChildState.newDrawable().mutate();
    129         d.setBounds(mState.mShadowSize, mState.mShadowSize,
    130                 mState.mIntrinsicWidth - mState.mShadowSize,
    131                 mState.mIntrinsicHeight - mState.mShadowSize);
    132         d.setTint(mState.mIsDark ? mState.mDarkTintColor : Color.WHITE);
    133         d.draw(canvas);
    134 
    135         // Do not draw shadow on dark theme
    136         if (!mState.mIsDark) {
    137             Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
    138             paint.setMaskFilter(new BlurMaskFilter(mState.mShadowSize, BlurMaskFilter.Blur.NORMAL));
    139             int[] offset = new int[2];
    140             Bitmap shadow = bitmap.extractAlpha(paint, offset);
    141 
    142             paint.setMaskFilter(null);
    143             paint.setColor(mState.mShadowColor);
    144             bitmap.eraseColor(Color.TRANSPARENT);
    145             canvas.drawBitmap(shadow, offset[0], offset[1], paint);
    146             d.draw(canvas);
    147         }
    148 
    149         if (Utilities.ATLEAST_OREO) {
    150             bitmap = bitmap.copy(Bitmap.Config.HARDWARE, false);
    151         }
    152         mState.mLastDrawnBitmap = bitmap;
    153     }
    154 
    155     @Override
    156     public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs,
    157             Resources.Theme theme) throws XmlPullParserException, IOException {
    158         super.inflate(r, parser, attrs, theme);
    159 
    160         final TypedArray a = theme == null
    161                 ? r.obtainAttributes(attrs, R.styleable.ShadowDrawable)
    162                 : theme.obtainStyledAttributes(attrs, R.styleable.ShadowDrawable, 0, 0);
    163         try {
    164             Drawable d = a.getDrawable(R.styleable.ShadowDrawable_android_src);
    165             if (d == null) {
    166                 throw new XmlPullParserException("missing src attribute");
    167             }
    168             mState.mShadowColor = a.getColor(
    169                     R.styleable.ShadowDrawable_android_shadowColor, Color.BLACK);
    170             mState.mShadowSize = a.getDimensionPixelSize(
    171                     R.styleable.ShadowDrawable_android_elevation, 0);
    172             mState.mDarkTintColor = a.getColor(
    173                     R.styleable.ShadowDrawable_darkTintColor, Color.BLACK);
    174 
    175             mState.mIntrinsicHeight = d.getIntrinsicHeight() + 2 * mState.mShadowSize;
    176             mState.mIntrinsicWidth = d.getIntrinsicWidth() + 2 * mState.mShadowSize;
    177             mState.mChangingConfigurations = d.getChangingConfigurations();
    178 
    179             mState.mChildState = d.getConstantState();
    180         } finally {
    181             a.recycle();
    182         }
    183     }
    184 
    185     private static class ShadowDrawableState extends ConstantState {
    186 
    187         int mChangingConfigurations;
    188         int mIntrinsicWidth;
    189         int mIntrinsicHeight;
    190 
    191         int mShadowColor;
    192         int mShadowSize;
    193         int mDarkTintColor;
    194 
    195         boolean mIsDark;
    196         Bitmap mLastDrawnBitmap;
    197         ConstantState mChildState;
    198 
    199         @Override
    200         public Drawable newDrawable() {
    201             return new ShadowDrawable(this);
    202         }
    203 
    204         @Override
    205         public int getChangingConfigurations() {
    206             return mChangingConfigurations;
    207         }
    208 
    209         @Override
    210         public boolean canApplyTheme() {
    211             return true;
    212         }
    213     }
    214 }
    215