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.icons.BitmapRenderer; 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") ShadowDrawable()53 public ShadowDrawable() { 54 this(new ShadowDrawableState()); 55 } 56 ShadowDrawable(ShadowDrawableState state)57 private ShadowDrawable(ShadowDrawableState state) { 58 mState = state; 59 } 60 61 @Override draw(Canvas canvas)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 setAlpha(int alpha)74 public void setAlpha(int alpha) { 75 mPaint.setAlpha(alpha); 76 invalidateSelf(); 77 } 78 79 @Override setColorFilter(ColorFilter colorFilter)80 public void setColorFilter(ColorFilter colorFilter) { 81 mPaint.setColorFilter(colorFilter); 82 invalidateSelf(); 83 } 84 85 @Override getConstantState()86 public ConstantState getConstantState() { 87 return mState; 88 } 89 90 @Override getOpacity()91 public int getOpacity() { 92 return PixelFormat.TRANSLUCENT; 93 } 94 95 @Override getIntrinsicHeight()96 public int getIntrinsicHeight() { 97 return mState.mIntrinsicHeight; 98 } 99 100 @Override getIntrinsicWidth()101 public int getIntrinsicWidth() { 102 return mState.mIntrinsicWidth; 103 } 104 105 @Override canApplyTheme()106 public boolean canApplyTheme() { 107 return mState.canApplyTheme(); 108 } 109 110 @Override applyTheme(Resources.Theme t)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 regenerateBitmapCache()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 (BitmapRenderer.USE_HARDWARE_BITMAP) { 150 bitmap = bitmap.copy(Bitmap.Config.HARDWARE, false); 151 } 152 mState.mLastDrawnBitmap = bitmap; 153 } 154 155 @Override inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Resources.Theme theme)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 newDrawable()200 public Drawable newDrawable() { 201 return new ShadowDrawable(this); 202 } 203 204 @Override getChangingConfigurations()205 public int getChangingConfigurations() { 206 return mChangingConfigurations; 207 } 208 209 @Override canApplyTheme()210 public boolean canApplyTheme() { 211 return true; 212 } 213 } 214 } 215