1 /* 2 * Copyright (C) 2014 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 package com.android.systemui.recents.views; 17 18 import android.content.res.Resources; 19 import android.graphics.Canvas; 20 import android.graphics.ColorFilter; 21 import android.graphics.LinearGradient; 22 import android.graphics.Paint; 23 import android.graphics.Path; 24 import android.graphics.PixelFormat; 25 import android.graphics.RadialGradient; 26 import android.graphics.Rect; 27 import android.graphics.RectF; 28 import android.graphics.Shader; 29 import android.graphics.drawable.Drawable; 30 import android.util.Log; 31 32 import com.android.systemui.R; 33 import com.android.systemui.recents.RecentsConfiguration; 34 35 /** 36 * A rounded rectangle drawable which also includes a shadow around. This is mostly copied from 37 * frameworks/support/v7/cardview/eclair-mr1/android/support/v7/widget/ 38 * RoundRectDrawableWithShadow.java revision c42ba8c000d1e6ce85e152dfc17089a0a69e739f with a few 39 * modifications to suit our needs in SystemUI. 40 */ 41 class FakeShadowDrawable extends Drawable { 42 // used to calculate content padding 43 final static double COS_45 = Math.cos(Math.toRadians(45)); 44 45 final static float SHADOW_MULTIPLIER = 1.5f; 46 47 final float mInsetShadow; // extra shadow to avoid gaps between card and shadow 48 49 Paint mCornerShadowPaint; 50 51 Paint mEdgeShadowPaint; 52 53 final RectF mCardBounds; 54 55 float mCornerRadius; 56 57 Path mCornerShadowPath; 58 59 // updated value with inset 60 float mMaxShadowSize; 61 62 // actual value set by developer 63 float mRawMaxShadowSize; 64 65 // multiplied value to account for shadow offset 66 float mShadowSize; 67 68 // actual value set by developer 69 float mRawShadowSize; 70 71 private boolean mDirty = true; 72 73 private final int mShadowStartColor; 74 75 private final int mShadowEndColor; 76 77 private boolean mAddPaddingForCorners = true; 78 79 /** 80 * If shadow size is set to a value above max shadow, we print a warning 81 */ 82 private boolean mPrintedShadowClipWarning = false; 83 84 public FakeShadowDrawable(Resources resources, RecentsConfiguration config) { 85 mShadowStartColor = resources.getColor(R.color.fake_shadow_start_color); 86 mShadowEndColor = resources.getColor(R.color.fake_shadow_end_color); 87 mInsetShadow = resources.getDimension(R.dimen.fake_shadow_inset); 88 setShadowSize(resources.getDimensionPixelSize(R.dimen.fake_shadow_size), 89 resources.getDimensionPixelSize(R.dimen.fake_shadow_size)); 90 mCornerShadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG); 91 mCornerShadowPaint.setStyle(Paint.Style.FILL); 92 mCornerShadowPaint.setDither(true); 93 mCornerRadius = resources.getDimensionPixelSize( 94 R.dimen.recents_task_view_rounded_corners_radius); 95 mCardBounds = new RectF(); 96 mEdgeShadowPaint = new Paint(mCornerShadowPaint); 97 } 98 99 @Override 100 public void setAlpha(int alpha) { 101 mCornerShadowPaint.setAlpha(alpha); 102 mEdgeShadowPaint.setAlpha(alpha); 103 } 104 105 @Override 106 protected void onBoundsChange(Rect bounds) { 107 super.onBoundsChange(bounds); 108 mDirty = true; 109 } 110 111 void setShadowSize(float shadowSize, float maxShadowSize) { 112 if (shadowSize < 0 || maxShadowSize < 0) { 113 throw new IllegalArgumentException("invalid shadow size"); 114 } 115 if (shadowSize > maxShadowSize) { 116 shadowSize = maxShadowSize; 117 if (!mPrintedShadowClipWarning) { 118 Log.w("CardView", "Shadow size is being clipped by the max shadow size. See " 119 + "{CardView#setMaxCardElevation}."); 120 mPrintedShadowClipWarning = true; 121 } 122 } 123 if (mRawShadowSize == shadowSize && mRawMaxShadowSize == maxShadowSize) { 124 return; 125 } 126 mRawShadowSize = shadowSize; 127 mRawMaxShadowSize = maxShadowSize; 128 mShadowSize = shadowSize * SHADOW_MULTIPLIER + mInsetShadow; 129 mMaxShadowSize = maxShadowSize + mInsetShadow; 130 mDirty = true; 131 invalidateSelf(); 132 } 133 134 @Override 135 public boolean getPadding(Rect padding) { 136 int vOffset = (int) Math.ceil(calculateVerticalPadding(mRawMaxShadowSize, mCornerRadius, 137 mAddPaddingForCorners)); 138 int hOffset = (int) Math.ceil(calculateHorizontalPadding(mRawMaxShadowSize, mCornerRadius, 139 mAddPaddingForCorners)); 140 padding.set(hOffset, vOffset, hOffset, vOffset); 141 return true; 142 } 143 144 static float calculateVerticalPadding(float maxShadowSize, float cornerRadius, 145 boolean addPaddingForCorners) { 146 if (addPaddingForCorners) { 147 return (float) (maxShadowSize * SHADOW_MULTIPLIER + (1 - COS_45) * cornerRadius); 148 } else { 149 return maxShadowSize * SHADOW_MULTIPLIER; 150 } 151 } 152 153 static float calculateHorizontalPadding(float maxShadowSize, float cornerRadius, 154 boolean addPaddingForCorners) { 155 if (addPaddingForCorners) { 156 return (float) (maxShadowSize + (1 - COS_45) * cornerRadius); 157 } else { 158 return maxShadowSize; 159 } 160 } 161 162 @Override 163 public void setColorFilter(ColorFilter colorFilter) { 164 mCornerShadowPaint.setColorFilter(colorFilter); 165 mEdgeShadowPaint.setColorFilter(colorFilter); 166 } 167 168 @Override 169 public int getOpacity() { 170 return PixelFormat.OPAQUE; 171 } 172 173 @Override 174 public void draw(Canvas canvas) { 175 if (mDirty) { 176 buildComponents(getBounds()); 177 mDirty = false; 178 } 179 canvas.translate(0, mRawShadowSize / 4); 180 drawShadow(canvas); 181 canvas.translate(0, -mRawShadowSize / 4); 182 } 183 184 private void drawShadow(Canvas canvas) { 185 final float edgeShadowTop = -mCornerRadius - mShadowSize; 186 final float inset = mCornerRadius + mInsetShadow + mRawShadowSize / 2; 187 final boolean drawHorizontalEdges = mCardBounds.width() - 2 * inset > 0; 188 final boolean drawVerticalEdges = mCardBounds.height() - 2 * inset > 0; 189 // LT 190 int saved = canvas.save(); 191 canvas.translate(mCardBounds.left + inset, mCardBounds.top + inset); 192 canvas.drawPath(mCornerShadowPath, mCornerShadowPaint); 193 if (drawHorizontalEdges) { 194 canvas.drawRect(0, edgeShadowTop, 195 mCardBounds.width() - 2 * inset, -mCornerRadius, 196 mEdgeShadowPaint); 197 } 198 canvas.restoreToCount(saved); 199 // RB 200 saved = canvas.save(); 201 canvas.translate(mCardBounds.right - inset, mCardBounds.bottom - inset); 202 canvas.rotate(180f); 203 canvas.drawPath(mCornerShadowPath, mCornerShadowPaint); 204 if (drawHorizontalEdges) { 205 canvas.drawRect(0, edgeShadowTop, 206 mCardBounds.width() - 2 * inset, -mCornerRadius + mShadowSize, 207 mEdgeShadowPaint); 208 } 209 canvas.restoreToCount(saved); 210 // LB 211 saved = canvas.save(); 212 canvas.translate(mCardBounds.left + inset, mCardBounds.bottom - inset); 213 canvas.rotate(270f); 214 canvas.drawPath(mCornerShadowPath, mCornerShadowPaint); 215 if (drawVerticalEdges) { 216 canvas.drawRect(0, edgeShadowTop, 217 mCardBounds.height() - 2 * inset, -mCornerRadius, mEdgeShadowPaint); 218 } 219 canvas.restoreToCount(saved); 220 // RT 221 saved = canvas.save(); 222 canvas.translate(mCardBounds.right - inset, mCardBounds.top + inset); 223 canvas.rotate(90f); 224 canvas.drawPath(mCornerShadowPath, mCornerShadowPaint); 225 if (drawVerticalEdges) { 226 canvas.drawRect(0, edgeShadowTop, 227 mCardBounds.height() - 2 * inset, -mCornerRadius, mEdgeShadowPaint); 228 } 229 canvas.restoreToCount(saved); 230 } 231 232 private void buildShadowCorners() { 233 RectF innerBounds = new RectF(-mCornerRadius, -mCornerRadius, mCornerRadius, mCornerRadius); 234 RectF outerBounds = new RectF(innerBounds); 235 outerBounds.inset(-mShadowSize, -mShadowSize); 236 237 if (mCornerShadowPath == null) { 238 mCornerShadowPath = new Path(); 239 } else { 240 mCornerShadowPath.reset(); 241 } 242 mCornerShadowPath.setFillType(Path.FillType.EVEN_ODD); 243 mCornerShadowPath.moveTo(-mCornerRadius, 0); 244 mCornerShadowPath.rLineTo(-mShadowSize, 0); 245 // outer arc 246 mCornerShadowPath.arcTo(outerBounds, 180f, 90f, false); 247 // inner arc 248 mCornerShadowPath.arcTo(innerBounds, 270f, -90f, false); 249 mCornerShadowPath.close(); 250 251 float startRatio = mCornerRadius / (mCornerRadius + mShadowSize); 252 mCornerShadowPaint.setShader(new RadialGradient(0, 0, mCornerRadius + mShadowSize, 253 new int[]{mShadowStartColor, mShadowStartColor, mShadowEndColor}, 254 new float[]{0f, startRatio, 1f} 255 , Shader.TileMode.CLAMP)); 256 257 // we offset the content shadowSize/2 pixels up to make it more realistic. 258 // this is why edge shadow shader has some extra space 259 // When drawing bottom edge shadow, we use that extra space. 260 mEdgeShadowPaint.setShader(new LinearGradient(0, -mCornerRadius + mShadowSize, 0, 261 -mCornerRadius - mShadowSize, 262 new int[]{mShadowStartColor, mShadowStartColor, mShadowEndColor}, 263 new float[]{0f, .5f, 1f}, Shader.TileMode.CLAMP)); 264 } 265 266 private void buildComponents(Rect bounds) { 267 // Card is offset SHADOW_MULTIPLIER * maxShadowSize to account for the shadow shift. 268 // We could have different top-bottom offsets to avoid extra gap above but in that case 269 // center aligning Views inside the CardView would be problematic. 270 final float verticalOffset = mMaxShadowSize * SHADOW_MULTIPLIER; 271 mCardBounds.set(bounds.left + mMaxShadowSize, bounds.top + verticalOffset, 272 bounds.right - mMaxShadowSize, bounds.bottom - verticalOffset); 273 buildShadowCorners(); 274 } 275 276 float getMinWidth() { 277 final float content = 2 * 278 Math.max(mRawMaxShadowSize, mCornerRadius + mInsetShadow + mRawMaxShadowSize / 2); 279 return content + (mRawMaxShadowSize + mInsetShadow) * 2; 280 } 281 282 float getMinHeight() { 283 final float content = 2 * Math.max(mRawMaxShadowSize, mCornerRadius + mInsetShadow 284 + mRawMaxShadowSize * SHADOW_MULTIPLIER / 2); 285 return content + (mRawMaxShadowSize * SHADOW_MULTIPLIER + mInsetShadow) * 2; 286 } 287 }