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 // Call mutate, so that the pixel allocation by the underlying vector drawable is cleared. 124 Drawable d = mState.mChildState.newDrawable().mutate(); 125 d.setBounds(mState.mShadowSize, mState.mShadowSize, 126 mState.mIntrinsicWidth - mState.mShadowSize, 127 mState.mIntrinsicHeight - mState.mShadowSize); 128 d.setTint(mState.mIsDark ? mState.mDarkTintColor : Color.WHITE); 129 130 if (mState.mIsDark) { 131 // Dark text do not have any shadow, but just the bitmap 132 mState.mLastDrawnBitmap = BitmapRenderer.createHardwareBitmap( 133 mState.mIntrinsicWidth, mState.mIntrinsicHeight, d::draw); 134 } else { 135 Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); 136 paint.setMaskFilter(new BlurMaskFilter(mState.mShadowSize, BlurMaskFilter.Blur.NORMAL)); 137 138 // Generate the shadow bitmap 139 int[] offset = new int[2]; 140 Bitmap shadow = BitmapRenderer.createSoftwareBitmap( 141 mState.mIntrinsicWidth, mState.mIntrinsicHeight, d::draw) 142 .extractAlpha(paint, offset); 143 144 paint.setMaskFilter(null); 145 paint.setColor(mState.mShadowColor); 146 mState.mLastDrawnBitmap = BitmapRenderer.createHardwareBitmap( 147 mState.mIntrinsicWidth, mState.mIntrinsicHeight, c -> { 148 c.drawBitmap(shadow, offset[0], offset[1], paint); 149 d.draw(c); 150 }); 151 } 152 } 153 154 @Override inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Resources.Theme theme)155 public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, 156 Resources.Theme theme) throws XmlPullParserException, IOException { 157 super.inflate(r, parser, attrs, theme); 158 159 final TypedArray a = theme == null 160 ? r.obtainAttributes(attrs, R.styleable.ShadowDrawable) 161 : theme.obtainStyledAttributes(attrs, R.styleable.ShadowDrawable, 0, 0); 162 try { 163 Drawable d = a.getDrawable(R.styleable.ShadowDrawable_android_src); 164 if (d == null) { 165 throw new XmlPullParserException("missing src attribute"); 166 } 167 mState.mShadowColor = a.getColor( 168 R.styleable.ShadowDrawable_android_shadowColor, Color.BLACK); 169 mState.mShadowSize = a.getDimensionPixelSize( 170 R.styleable.ShadowDrawable_android_elevation, 0); 171 mState.mDarkTintColor = a.getColor( 172 R.styleable.ShadowDrawable_darkTintColor, Color.BLACK); 173 174 mState.mIntrinsicHeight = d.getIntrinsicHeight() + 2 * mState.mShadowSize; 175 mState.mIntrinsicWidth = d.getIntrinsicWidth() + 2 * mState.mShadowSize; 176 mState.mChangingConfigurations = d.getChangingConfigurations(); 177 178 mState.mChildState = d.getConstantState(); 179 } finally { 180 a.recycle(); 181 } 182 } 183 184 private static class ShadowDrawableState extends ConstantState { 185 186 int mChangingConfigurations; 187 int mIntrinsicWidth; 188 int mIntrinsicHeight; 189 190 int mShadowColor; 191 int mShadowSize; 192 int mDarkTintColor; 193 194 boolean mIsDark; 195 Bitmap mLastDrawnBitmap; 196 ConstantState mChildState; 197 198 @Override newDrawable()199 public Drawable newDrawable() { 200 return new ShadowDrawable(this); 201 } 202 203 @Override getChangingConfigurations()204 public int getChangingConfigurations() { 205 return mChangingConfigurations; 206 } 207 208 @Override canApplyTheme()209 public boolean canApplyTheme() { 210 return true; 211 } 212 } 213 } 214