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