19840efe3dbdc7026521da8576574c55120782f6cChris Banes/* 29840efe3dbdc7026521da8576574c55120782f6cChris Banes * Copyright (C) 2014 The Android Open Source Project 39840efe3dbdc7026521da8576574c55120782f6cChris Banes * 49840efe3dbdc7026521da8576574c55120782f6cChris Banes * Licensed under the Apache License, Version 2.0 (the "License"); 59840efe3dbdc7026521da8576574c55120782f6cChris Banes * you may not use this file except in compliance with the License. 69840efe3dbdc7026521da8576574c55120782f6cChris Banes * You may obtain a copy of the License at 79840efe3dbdc7026521da8576574c55120782f6cChris Banes * 89840efe3dbdc7026521da8576574c55120782f6cChris Banes * http://www.apache.org/licenses/LICENSE-2.0 99840efe3dbdc7026521da8576574c55120782f6cChris Banes * 109840efe3dbdc7026521da8576574c55120782f6cChris Banes * Unless required by applicable law or agreed to in writing, software 119840efe3dbdc7026521da8576574c55120782f6cChris Banes * distributed under the License is distributed on an "AS IS" BASIS, 129840efe3dbdc7026521da8576574c55120782f6cChris Banes * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 139840efe3dbdc7026521da8576574c55120782f6cChris Banes * See the License for the specific language governing permissions and 149840efe3dbdc7026521da8576574c55120782f6cChris Banes * limitations under the License. 159840efe3dbdc7026521da8576574c55120782f6cChris Banes */ 169840efe3dbdc7026521da8576574c55120782f6cChris Banes 179840efe3dbdc7026521da8576574c55120782f6cChris Banespackage android.support.design.widget; 189840efe3dbdc7026521da8576574c55120782f6cChris Banes 197b75d53b8e5584895595f801256a3d63bb68148aAurimas Liutikasimport android.content.Context; 209840efe3dbdc7026521da8576574c55120782f6cChris Banesimport android.graphics.Canvas; 219840efe3dbdc7026521da8576574c55120782f6cChris Banesimport android.graphics.LinearGradient; 229840efe3dbdc7026521da8576574c55120782f6cChris Banesimport android.graphics.Paint; 239840efe3dbdc7026521da8576574c55120782f6cChris Banesimport android.graphics.Path; 249840efe3dbdc7026521da8576574c55120782f6cChris Banesimport android.graphics.PixelFormat; 259840efe3dbdc7026521da8576574c55120782f6cChris Banesimport android.graphics.RadialGradient; 269840efe3dbdc7026521da8576574c55120782f6cChris Banesimport android.graphics.Rect; 279840efe3dbdc7026521da8576574c55120782f6cChris Banesimport android.graphics.RectF; 289840efe3dbdc7026521da8576574c55120782f6cChris Banesimport android.graphics.Shader; 299840efe3dbdc7026521da8576574c55120782f6cChris Banesimport android.graphics.drawable.Drawable; 309840efe3dbdc7026521da8576574c55120782f6cChris Banesimport android.support.design.R; 317b75d53b8e5584895595f801256a3d63bb68148aAurimas Liutikasimport android.support.v4.content.ContextCompat; 329840efe3dbdc7026521da8576574c55120782f6cChris Banesimport android.support.v7.graphics.drawable.DrawableWrapper; 339840efe3dbdc7026521da8576574c55120782f6cChris Banes 349840efe3dbdc7026521da8576574c55120782f6cChris Banes/** 359840efe3dbdc7026521da8576574c55120782f6cChris Banes * A {@link android.graphics.drawable.Drawable} which wraps another drawable and 369840efe3dbdc7026521da8576574c55120782f6cChris Banes * draws a shadow around it. 379840efe3dbdc7026521da8576574c55120782f6cChris Banes */ 389840efe3dbdc7026521da8576574c55120782f6cChris Banesclass ShadowDrawableWrapper extends DrawableWrapper { 399840efe3dbdc7026521da8576574c55120782f6cChris Banes // used to calculate content padding 409840efe3dbdc7026521da8576574c55120782f6cChris Banes static final double COS_45 = Math.cos(Math.toRadians(45)); 419840efe3dbdc7026521da8576574c55120782f6cChris Banes 429840efe3dbdc7026521da8576574c55120782f6cChris Banes static final float SHADOW_MULTIPLIER = 1.5f; 439840efe3dbdc7026521da8576574c55120782f6cChris Banes 449840efe3dbdc7026521da8576574c55120782f6cChris Banes static final float SHADOW_TOP_SCALE = 0.25f; 459840efe3dbdc7026521da8576574c55120782f6cChris Banes static final float SHADOW_HORIZ_SCALE = 0.5f; 469840efe3dbdc7026521da8576574c55120782f6cChris Banes static final float SHADOW_BOTTOM_SCALE = 1f; 479840efe3dbdc7026521da8576574c55120782f6cChris Banes 489840efe3dbdc7026521da8576574c55120782f6cChris Banes final Paint mCornerShadowPaint; 499840efe3dbdc7026521da8576574c55120782f6cChris Banes final Paint mEdgeShadowPaint; 509840efe3dbdc7026521da8576574c55120782f6cChris Banes 519840efe3dbdc7026521da8576574c55120782f6cChris Banes final RectF mContentBounds; 529840efe3dbdc7026521da8576574c55120782f6cChris Banes 539840efe3dbdc7026521da8576574c55120782f6cChris Banes float mCornerRadius; 549840efe3dbdc7026521da8576574c55120782f6cChris Banes 559840efe3dbdc7026521da8576574c55120782f6cChris Banes Path mCornerShadowPath; 569840efe3dbdc7026521da8576574c55120782f6cChris Banes 579840efe3dbdc7026521da8576574c55120782f6cChris Banes // updated value with inset 589840efe3dbdc7026521da8576574c55120782f6cChris Banes float mMaxShadowSize; 599840efe3dbdc7026521da8576574c55120782f6cChris Banes // actual value set by developer 609840efe3dbdc7026521da8576574c55120782f6cChris Banes float mRawMaxShadowSize; 619840efe3dbdc7026521da8576574c55120782f6cChris Banes 629840efe3dbdc7026521da8576574c55120782f6cChris Banes // multiplied value to account for shadow offset 639840efe3dbdc7026521da8576574c55120782f6cChris Banes float mShadowSize; 649840efe3dbdc7026521da8576574c55120782f6cChris Banes // actual value set by developer 659840efe3dbdc7026521da8576574c55120782f6cChris Banes float mRawShadowSize; 669840efe3dbdc7026521da8576574c55120782f6cChris Banes 679840efe3dbdc7026521da8576574c55120782f6cChris Banes private boolean mDirty = true; 689840efe3dbdc7026521da8576574c55120782f6cChris Banes 699840efe3dbdc7026521da8576574c55120782f6cChris Banes private final int mShadowStartColor; 709840efe3dbdc7026521da8576574c55120782f6cChris Banes private final int mShadowMiddleColor; 719840efe3dbdc7026521da8576574c55120782f6cChris Banes private final int mShadowEndColor; 729840efe3dbdc7026521da8576574c55120782f6cChris Banes 739840efe3dbdc7026521da8576574c55120782f6cChris Banes private boolean mAddPaddingForCorners = true; 749840efe3dbdc7026521da8576574c55120782f6cChris Banes 75d9770e12c8ff2d4417700492c6616572be897e93Chris Banes private float mRotation; 76d9770e12c8ff2d4417700492c6616572be897e93Chris Banes 779840efe3dbdc7026521da8576574c55120782f6cChris Banes /** 789840efe3dbdc7026521da8576574c55120782f6cChris Banes * If shadow size is set to a value above max shadow, we print a warning 799840efe3dbdc7026521da8576574c55120782f6cChris Banes */ 809840efe3dbdc7026521da8576574c55120782f6cChris Banes private boolean mPrintedShadowClipWarning = false; 819840efe3dbdc7026521da8576574c55120782f6cChris Banes 827b75d53b8e5584895595f801256a3d63bb68148aAurimas Liutikas public ShadowDrawableWrapper(Context context, Drawable content, float radius, 839840efe3dbdc7026521da8576574c55120782f6cChris Banes float shadowSize, float maxShadowSize) { 849840efe3dbdc7026521da8576574c55120782f6cChris Banes super(content); 859840efe3dbdc7026521da8576574c55120782f6cChris Banes 867b75d53b8e5584895595f801256a3d63bb68148aAurimas Liutikas mShadowStartColor = ContextCompat.getColor(context, R.color.design_fab_shadow_start_color); 877b75d53b8e5584895595f801256a3d63bb68148aAurimas Liutikas mShadowMiddleColor = ContextCompat.getColor(context, R.color.design_fab_shadow_mid_color); 887b75d53b8e5584895595f801256a3d63bb68148aAurimas Liutikas mShadowEndColor = ContextCompat.getColor(context, R.color.design_fab_shadow_end_color); 899840efe3dbdc7026521da8576574c55120782f6cChris Banes 909840efe3dbdc7026521da8576574c55120782f6cChris Banes mCornerShadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG); 919840efe3dbdc7026521da8576574c55120782f6cChris Banes mCornerShadowPaint.setStyle(Paint.Style.FILL); 929840efe3dbdc7026521da8576574c55120782f6cChris Banes mCornerRadius = Math.round(radius); 939840efe3dbdc7026521da8576574c55120782f6cChris Banes mContentBounds = new RectF(); 949840efe3dbdc7026521da8576574c55120782f6cChris Banes mEdgeShadowPaint = new Paint(mCornerShadowPaint); 959840efe3dbdc7026521da8576574c55120782f6cChris Banes mEdgeShadowPaint.setAntiAlias(false); 969840efe3dbdc7026521da8576574c55120782f6cChris Banes setShadowSize(shadowSize, maxShadowSize); 979840efe3dbdc7026521da8576574c55120782f6cChris Banes } 989840efe3dbdc7026521da8576574c55120782f6cChris Banes 999840efe3dbdc7026521da8576574c55120782f6cChris Banes /** 1009840efe3dbdc7026521da8576574c55120782f6cChris Banes * Casts the value to an even integer. 1019840efe3dbdc7026521da8576574c55120782f6cChris Banes */ 1029840efe3dbdc7026521da8576574c55120782f6cChris Banes private static int toEven(float value) { 1039840efe3dbdc7026521da8576574c55120782f6cChris Banes int i = Math.round(value); 1049840efe3dbdc7026521da8576574c55120782f6cChris Banes return (i % 2 == 1) ? i - 1 : i; 1059840efe3dbdc7026521da8576574c55120782f6cChris Banes } 1069840efe3dbdc7026521da8576574c55120782f6cChris Banes 1079840efe3dbdc7026521da8576574c55120782f6cChris Banes public void setAddPaddingForCorners(boolean addPaddingForCorners) { 1089840efe3dbdc7026521da8576574c55120782f6cChris Banes mAddPaddingForCorners = addPaddingForCorners; 1099840efe3dbdc7026521da8576574c55120782f6cChris Banes invalidateSelf(); 1109840efe3dbdc7026521da8576574c55120782f6cChris Banes } 1119840efe3dbdc7026521da8576574c55120782f6cChris Banes 1129840efe3dbdc7026521da8576574c55120782f6cChris Banes @Override 1139840efe3dbdc7026521da8576574c55120782f6cChris Banes public void setAlpha(int alpha) { 1149840efe3dbdc7026521da8576574c55120782f6cChris Banes super.setAlpha(alpha); 1159840efe3dbdc7026521da8576574c55120782f6cChris Banes mCornerShadowPaint.setAlpha(alpha); 1169840efe3dbdc7026521da8576574c55120782f6cChris Banes mEdgeShadowPaint.setAlpha(alpha); 1179840efe3dbdc7026521da8576574c55120782f6cChris Banes } 1189840efe3dbdc7026521da8576574c55120782f6cChris Banes 1199840efe3dbdc7026521da8576574c55120782f6cChris Banes @Override 1209840efe3dbdc7026521da8576574c55120782f6cChris Banes protected void onBoundsChange(Rect bounds) { 1219840efe3dbdc7026521da8576574c55120782f6cChris Banes mDirty = true; 1229840efe3dbdc7026521da8576574c55120782f6cChris Banes } 1239840efe3dbdc7026521da8576574c55120782f6cChris Banes 1249840efe3dbdc7026521da8576574c55120782f6cChris Banes void setShadowSize(float shadowSize, float maxShadowSize) { 1259840efe3dbdc7026521da8576574c55120782f6cChris Banes if (shadowSize < 0 || maxShadowSize < 0) { 1269840efe3dbdc7026521da8576574c55120782f6cChris Banes throw new IllegalArgumentException("invalid shadow size"); 1279840efe3dbdc7026521da8576574c55120782f6cChris Banes } 1289840efe3dbdc7026521da8576574c55120782f6cChris Banes shadowSize = toEven(shadowSize); 1299840efe3dbdc7026521da8576574c55120782f6cChris Banes maxShadowSize = toEven(maxShadowSize); 1309840efe3dbdc7026521da8576574c55120782f6cChris Banes if (shadowSize > maxShadowSize) { 1319840efe3dbdc7026521da8576574c55120782f6cChris Banes shadowSize = maxShadowSize; 1329840efe3dbdc7026521da8576574c55120782f6cChris Banes if (!mPrintedShadowClipWarning) { 1339840efe3dbdc7026521da8576574c55120782f6cChris Banes mPrintedShadowClipWarning = true; 1349840efe3dbdc7026521da8576574c55120782f6cChris Banes } 1359840efe3dbdc7026521da8576574c55120782f6cChris Banes } 1369840efe3dbdc7026521da8576574c55120782f6cChris Banes if (mRawShadowSize == shadowSize && mRawMaxShadowSize == maxShadowSize) { 1379840efe3dbdc7026521da8576574c55120782f6cChris Banes return; 1389840efe3dbdc7026521da8576574c55120782f6cChris Banes } 1399840efe3dbdc7026521da8576574c55120782f6cChris Banes mRawShadowSize = shadowSize; 1409840efe3dbdc7026521da8576574c55120782f6cChris Banes mRawMaxShadowSize = maxShadowSize; 1419840efe3dbdc7026521da8576574c55120782f6cChris Banes mShadowSize = Math.round(shadowSize * SHADOW_MULTIPLIER); 1429840efe3dbdc7026521da8576574c55120782f6cChris Banes mMaxShadowSize = maxShadowSize; 1439840efe3dbdc7026521da8576574c55120782f6cChris Banes mDirty = true; 1449840efe3dbdc7026521da8576574c55120782f6cChris Banes invalidateSelf(); 1459840efe3dbdc7026521da8576574c55120782f6cChris Banes } 1469840efe3dbdc7026521da8576574c55120782f6cChris Banes 1479840efe3dbdc7026521da8576574c55120782f6cChris Banes @Override 1489840efe3dbdc7026521da8576574c55120782f6cChris Banes public boolean getPadding(Rect padding) { 1499840efe3dbdc7026521da8576574c55120782f6cChris Banes int vOffset = (int) Math.ceil(calculateVerticalPadding(mRawMaxShadowSize, mCornerRadius, 1509840efe3dbdc7026521da8576574c55120782f6cChris Banes mAddPaddingForCorners)); 1519840efe3dbdc7026521da8576574c55120782f6cChris Banes int hOffset = (int) Math.ceil(calculateHorizontalPadding(mRawMaxShadowSize, mCornerRadius, 1529840efe3dbdc7026521da8576574c55120782f6cChris Banes mAddPaddingForCorners)); 1539840efe3dbdc7026521da8576574c55120782f6cChris Banes padding.set(hOffset, vOffset, hOffset, vOffset); 1549840efe3dbdc7026521da8576574c55120782f6cChris Banes return true; 1559840efe3dbdc7026521da8576574c55120782f6cChris Banes } 1569840efe3dbdc7026521da8576574c55120782f6cChris Banes 1579840efe3dbdc7026521da8576574c55120782f6cChris Banes public static float calculateVerticalPadding(float maxShadowSize, float cornerRadius, 1589840efe3dbdc7026521da8576574c55120782f6cChris Banes boolean addPaddingForCorners) { 1599840efe3dbdc7026521da8576574c55120782f6cChris Banes if (addPaddingForCorners) { 1609840efe3dbdc7026521da8576574c55120782f6cChris Banes return (float) (maxShadowSize * SHADOW_MULTIPLIER + (1 - COS_45) * cornerRadius); 1619840efe3dbdc7026521da8576574c55120782f6cChris Banes } else { 1629840efe3dbdc7026521da8576574c55120782f6cChris Banes return maxShadowSize * SHADOW_MULTIPLIER; 1639840efe3dbdc7026521da8576574c55120782f6cChris Banes } 1649840efe3dbdc7026521da8576574c55120782f6cChris Banes } 1659840efe3dbdc7026521da8576574c55120782f6cChris Banes 1669840efe3dbdc7026521da8576574c55120782f6cChris Banes public static float calculateHorizontalPadding(float maxShadowSize, float cornerRadius, 1679840efe3dbdc7026521da8576574c55120782f6cChris Banes boolean addPaddingForCorners) { 1689840efe3dbdc7026521da8576574c55120782f6cChris Banes if (addPaddingForCorners) { 1699840efe3dbdc7026521da8576574c55120782f6cChris Banes return (float) (maxShadowSize + (1 - COS_45) * cornerRadius); 1709840efe3dbdc7026521da8576574c55120782f6cChris Banes } else { 1719840efe3dbdc7026521da8576574c55120782f6cChris Banes return maxShadowSize; 1729840efe3dbdc7026521da8576574c55120782f6cChris Banes } 1739840efe3dbdc7026521da8576574c55120782f6cChris Banes } 1749840efe3dbdc7026521da8576574c55120782f6cChris Banes 1759840efe3dbdc7026521da8576574c55120782f6cChris Banes @Override 1769840efe3dbdc7026521da8576574c55120782f6cChris Banes public int getOpacity() { 1779840efe3dbdc7026521da8576574c55120782f6cChris Banes return PixelFormat.TRANSLUCENT; 1789840efe3dbdc7026521da8576574c55120782f6cChris Banes } 1799840efe3dbdc7026521da8576574c55120782f6cChris Banes 1809840efe3dbdc7026521da8576574c55120782f6cChris Banes public void setCornerRadius(float radius) { 1819840efe3dbdc7026521da8576574c55120782f6cChris Banes radius = Math.round(radius); 1829840efe3dbdc7026521da8576574c55120782f6cChris Banes if (mCornerRadius == radius) { 1839840efe3dbdc7026521da8576574c55120782f6cChris Banes return; 1849840efe3dbdc7026521da8576574c55120782f6cChris Banes } 1859840efe3dbdc7026521da8576574c55120782f6cChris Banes mCornerRadius = radius; 1869840efe3dbdc7026521da8576574c55120782f6cChris Banes mDirty = true; 1879840efe3dbdc7026521da8576574c55120782f6cChris Banes invalidateSelf(); 1889840efe3dbdc7026521da8576574c55120782f6cChris Banes } 1899840efe3dbdc7026521da8576574c55120782f6cChris Banes 1909840efe3dbdc7026521da8576574c55120782f6cChris Banes @Override 1919840efe3dbdc7026521da8576574c55120782f6cChris Banes public void draw(Canvas canvas) { 1929840efe3dbdc7026521da8576574c55120782f6cChris Banes if (mDirty) { 1939840efe3dbdc7026521da8576574c55120782f6cChris Banes buildComponents(getBounds()); 1949840efe3dbdc7026521da8576574c55120782f6cChris Banes mDirty = false; 1959840efe3dbdc7026521da8576574c55120782f6cChris Banes } 1969840efe3dbdc7026521da8576574c55120782f6cChris Banes drawShadow(canvas); 1979840efe3dbdc7026521da8576574c55120782f6cChris Banes 1989840efe3dbdc7026521da8576574c55120782f6cChris Banes super.draw(canvas); 1999840efe3dbdc7026521da8576574c55120782f6cChris Banes } 2009840efe3dbdc7026521da8576574c55120782f6cChris Banes 201d9770e12c8ff2d4417700492c6616572be897e93Chris Banes final void setRotation(float rotation) { 202d9770e12c8ff2d4417700492c6616572be897e93Chris Banes if (mRotation != rotation) { 203d9770e12c8ff2d4417700492c6616572be897e93Chris Banes mRotation = rotation; 204d9770e12c8ff2d4417700492c6616572be897e93Chris Banes invalidateSelf(); 205d9770e12c8ff2d4417700492c6616572be897e93Chris Banes } 206d9770e12c8ff2d4417700492c6616572be897e93Chris Banes } 207d9770e12c8ff2d4417700492c6616572be897e93Chris Banes 2089840efe3dbdc7026521da8576574c55120782f6cChris Banes private void drawShadow(Canvas canvas) { 209d9770e12c8ff2d4417700492c6616572be897e93Chris Banes final int rotateSaved = canvas.save(); 210d9770e12c8ff2d4417700492c6616572be897e93Chris Banes canvas.rotate(mRotation, mContentBounds.centerX(), mContentBounds.centerY()); 211d9770e12c8ff2d4417700492c6616572be897e93Chris Banes 2129840efe3dbdc7026521da8576574c55120782f6cChris Banes final float edgeShadowTop = -mCornerRadius - mShadowSize; 2139840efe3dbdc7026521da8576574c55120782f6cChris Banes final float shadowOffset = mCornerRadius; 2149840efe3dbdc7026521da8576574c55120782f6cChris Banes final boolean drawHorizontalEdges = mContentBounds.width() - 2 * shadowOffset > 0; 2159840efe3dbdc7026521da8576574c55120782f6cChris Banes final boolean drawVerticalEdges = mContentBounds.height() - 2 * shadowOffset > 0; 2169840efe3dbdc7026521da8576574c55120782f6cChris Banes 2179840efe3dbdc7026521da8576574c55120782f6cChris Banes final float shadowOffsetTop = mRawShadowSize - (mRawShadowSize * SHADOW_TOP_SCALE); 2189840efe3dbdc7026521da8576574c55120782f6cChris Banes final float shadowOffsetHorizontal = mRawShadowSize - (mRawShadowSize * SHADOW_HORIZ_SCALE); 2199840efe3dbdc7026521da8576574c55120782f6cChris Banes final float shadowOffsetBottom = mRawShadowSize - (mRawShadowSize * SHADOW_BOTTOM_SCALE); 2209840efe3dbdc7026521da8576574c55120782f6cChris Banes 2219840efe3dbdc7026521da8576574c55120782f6cChris Banes final float shadowScaleHorizontal = shadowOffset / (shadowOffset + shadowOffsetHorizontal); 2229840efe3dbdc7026521da8576574c55120782f6cChris Banes final float shadowScaleTop = shadowOffset / (shadowOffset + shadowOffsetTop); 2239840efe3dbdc7026521da8576574c55120782f6cChris Banes final float shadowScaleBottom = shadowOffset / (shadowOffset + shadowOffsetBottom); 2249840efe3dbdc7026521da8576574c55120782f6cChris Banes 2259840efe3dbdc7026521da8576574c55120782f6cChris Banes // LT 2269840efe3dbdc7026521da8576574c55120782f6cChris Banes int saved = canvas.save(); 2279840efe3dbdc7026521da8576574c55120782f6cChris Banes canvas.translate(mContentBounds.left + shadowOffset, mContentBounds.top + shadowOffset); 2289840efe3dbdc7026521da8576574c55120782f6cChris Banes canvas.scale(shadowScaleHorizontal, shadowScaleTop); 2299840efe3dbdc7026521da8576574c55120782f6cChris Banes canvas.drawPath(mCornerShadowPath, mCornerShadowPaint); 2309840efe3dbdc7026521da8576574c55120782f6cChris Banes if (drawHorizontalEdges) { 2319840efe3dbdc7026521da8576574c55120782f6cChris Banes // TE 2329840efe3dbdc7026521da8576574c55120782f6cChris Banes canvas.scale(1f / shadowScaleHorizontal, 1f); 2339840efe3dbdc7026521da8576574c55120782f6cChris Banes canvas.drawRect(0, edgeShadowTop, 2349840efe3dbdc7026521da8576574c55120782f6cChris Banes mContentBounds.width() - 2 * shadowOffset, -mCornerRadius, 2359840efe3dbdc7026521da8576574c55120782f6cChris Banes mEdgeShadowPaint); 2369840efe3dbdc7026521da8576574c55120782f6cChris Banes } 2379840efe3dbdc7026521da8576574c55120782f6cChris Banes canvas.restoreToCount(saved); 2389840efe3dbdc7026521da8576574c55120782f6cChris Banes // RB 2399840efe3dbdc7026521da8576574c55120782f6cChris Banes saved = canvas.save(); 2409840efe3dbdc7026521da8576574c55120782f6cChris Banes canvas.translate(mContentBounds.right - shadowOffset, mContentBounds.bottom - shadowOffset); 2419840efe3dbdc7026521da8576574c55120782f6cChris Banes canvas.scale(shadowScaleHorizontal, shadowScaleBottom); 2429840efe3dbdc7026521da8576574c55120782f6cChris Banes canvas.rotate(180f); 2439840efe3dbdc7026521da8576574c55120782f6cChris Banes canvas.drawPath(mCornerShadowPath, mCornerShadowPaint); 2449840efe3dbdc7026521da8576574c55120782f6cChris Banes if (drawHorizontalEdges) { 2459840efe3dbdc7026521da8576574c55120782f6cChris Banes // BE 2469840efe3dbdc7026521da8576574c55120782f6cChris Banes canvas.scale(1f / shadowScaleHorizontal, 1f); 2479840efe3dbdc7026521da8576574c55120782f6cChris Banes canvas.drawRect(0, edgeShadowTop, 2489840efe3dbdc7026521da8576574c55120782f6cChris Banes mContentBounds.width() - 2 * shadowOffset, -mCornerRadius + mShadowSize, 2499840efe3dbdc7026521da8576574c55120782f6cChris Banes mEdgeShadowPaint); 2509840efe3dbdc7026521da8576574c55120782f6cChris Banes } 2519840efe3dbdc7026521da8576574c55120782f6cChris Banes canvas.restoreToCount(saved); 2529840efe3dbdc7026521da8576574c55120782f6cChris Banes // LB 2539840efe3dbdc7026521da8576574c55120782f6cChris Banes saved = canvas.save(); 2549840efe3dbdc7026521da8576574c55120782f6cChris Banes canvas.translate(mContentBounds.left + shadowOffset, mContentBounds.bottom - shadowOffset); 2559840efe3dbdc7026521da8576574c55120782f6cChris Banes canvas.scale(shadowScaleHorizontal, shadowScaleBottom); 2569840efe3dbdc7026521da8576574c55120782f6cChris Banes canvas.rotate(270f); 2579840efe3dbdc7026521da8576574c55120782f6cChris Banes canvas.drawPath(mCornerShadowPath, mCornerShadowPaint); 2589840efe3dbdc7026521da8576574c55120782f6cChris Banes if (drawVerticalEdges) { 2599840efe3dbdc7026521da8576574c55120782f6cChris Banes // LE 2609840efe3dbdc7026521da8576574c55120782f6cChris Banes canvas.scale(1f / shadowScaleBottom, 1f); 2619840efe3dbdc7026521da8576574c55120782f6cChris Banes canvas.drawRect(0, edgeShadowTop, 2629840efe3dbdc7026521da8576574c55120782f6cChris Banes mContentBounds.height() - 2 * shadowOffset, -mCornerRadius, mEdgeShadowPaint); 2639840efe3dbdc7026521da8576574c55120782f6cChris Banes } 2649840efe3dbdc7026521da8576574c55120782f6cChris Banes canvas.restoreToCount(saved); 2659840efe3dbdc7026521da8576574c55120782f6cChris Banes // RT 2669840efe3dbdc7026521da8576574c55120782f6cChris Banes saved = canvas.save(); 2679840efe3dbdc7026521da8576574c55120782f6cChris Banes canvas.translate(mContentBounds.right - shadowOffset, mContentBounds.top + shadowOffset); 2689840efe3dbdc7026521da8576574c55120782f6cChris Banes canvas.scale(shadowScaleHorizontal, shadowScaleTop); 2699840efe3dbdc7026521da8576574c55120782f6cChris Banes canvas.rotate(90f); 2709840efe3dbdc7026521da8576574c55120782f6cChris Banes canvas.drawPath(mCornerShadowPath, mCornerShadowPaint); 2719840efe3dbdc7026521da8576574c55120782f6cChris Banes if (drawVerticalEdges) { 2729840efe3dbdc7026521da8576574c55120782f6cChris Banes // RE 2739840efe3dbdc7026521da8576574c55120782f6cChris Banes canvas.scale(1f / shadowScaleTop, 1f); 2749840efe3dbdc7026521da8576574c55120782f6cChris Banes canvas.drawRect(0, edgeShadowTop, 2759840efe3dbdc7026521da8576574c55120782f6cChris Banes mContentBounds.height() - 2 * shadowOffset, -mCornerRadius, mEdgeShadowPaint); 2769840efe3dbdc7026521da8576574c55120782f6cChris Banes } 2779840efe3dbdc7026521da8576574c55120782f6cChris Banes canvas.restoreToCount(saved); 278d9770e12c8ff2d4417700492c6616572be897e93Chris Banes 279d9770e12c8ff2d4417700492c6616572be897e93Chris Banes canvas.restoreToCount(rotateSaved); 2809840efe3dbdc7026521da8576574c55120782f6cChris Banes } 2819840efe3dbdc7026521da8576574c55120782f6cChris Banes 2829840efe3dbdc7026521da8576574c55120782f6cChris Banes private void buildShadowCorners() { 2839840efe3dbdc7026521da8576574c55120782f6cChris Banes RectF innerBounds = new RectF(-mCornerRadius, -mCornerRadius, mCornerRadius, mCornerRadius); 2849840efe3dbdc7026521da8576574c55120782f6cChris Banes RectF outerBounds = new RectF(innerBounds); 2859840efe3dbdc7026521da8576574c55120782f6cChris Banes outerBounds.inset(-mShadowSize, -mShadowSize); 2869840efe3dbdc7026521da8576574c55120782f6cChris Banes 2879840efe3dbdc7026521da8576574c55120782f6cChris Banes if (mCornerShadowPath == null) { 2889840efe3dbdc7026521da8576574c55120782f6cChris Banes mCornerShadowPath = new Path(); 2899840efe3dbdc7026521da8576574c55120782f6cChris Banes } else { 2909840efe3dbdc7026521da8576574c55120782f6cChris Banes mCornerShadowPath.reset(); 2919840efe3dbdc7026521da8576574c55120782f6cChris Banes } 2929840efe3dbdc7026521da8576574c55120782f6cChris Banes mCornerShadowPath.setFillType(Path.FillType.EVEN_ODD); 2939840efe3dbdc7026521da8576574c55120782f6cChris Banes mCornerShadowPath.moveTo(-mCornerRadius, 0); 2949840efe3dbdc7026521da8576574c55120782f6cChris Banes mCornerShadowPath.rLineTo(-mShadowSize, 0); 2959840efe3dbdc7026521da8576574c55120782f6cChris Banes // outer arc 2969840efe3dbdc7026521da8576574c55120782f6cChris Banes mCornerShadowPath.arcTo(outerBounds, 180f, 90f, false); 2979840efe3dbdc7026521da8576574c55120782f6cChris Banes // inner arc 2989840efe3dbdc7026521da8576574c55120782f6cChris Banes mCornerShadowPath.arcTo(innerBounds, 270f, -90f, false); 2999840efe3dbdc7026521da8576574c55120782f6cChris Banes mCornerShadowPath.close(); 3009840efe3dbdc7026521da8576574c55120782f6cChris Banes 3019840efe3dbdc7026521da8576574c55120782f6cChris Banes float shadowRadius = -outerBounds.top; 3029840efe3dbdc7026521da8576574c55120782f6cChris Banes if (shadowRadius > 0f) { 3039840efe3dbdc7026521da8576574c55120782f6cChris Banes float startRatio = mCornerRadius / shadowRadius; 3049840efe3dbdc7026521da8576574c55120782f6cChris Banes float midRatio = startRatio + ((1f - startRatio) / 2f); 3059840efe3dbdc7026521da8576574c55120782f6cChris Banes mCornerShadowPaint.setShader(new RadialGradient(0, 0, shadowRadius, 3069840efe3dbdc7026521da8576574c55120782f6cChris Banes new int[]{0, mShadowStartColor, mShadowMiddleColor, mShadowEndColor}, 3079840efe3dbdc7026521da8576574c55120782f6cChris Banes new float[]{0f, startRatio, midRatio, 1f}, 3089840efe3dbdc7026521da8576574c55120782f6cChris Banes Shader.TileMode.CLAMP)); 3099840efe3dbdc7026521da8576574c55120782f6cChris Banes } 3109840efe3dbdc7026521da8576574c55120782f6cChris Banes 3119840efe3dbdc7026521da8576574c55120782f6cChris Banes // we offset the content shadowSize/2 pixels up to make it more realistic. 3129840efe3dbdc7026521da8576574c55120782f6cChris Banes // this is why edge shadow shader has some extra space 3139840efe3dbdc7026521da8576574c55120782f6cChris Banes // When drawing bottom edge shadow, we use that extra space. 3149840efe3dbdc7026521da8576574c55120782f6cChris Banes mEdgeShadowPaint.setShader(new LinearGradient(0, innerBounds.top, 0, outerBounds.top, 3159840efe3dbdc7026521da8576574c55120782f6cChris Banes new int[]{mShadowStartColor, mShadowMiddleColor, mShadowEndColor}, 3169840efe3dbdc7026521da8576574c55120782f6cChris Banes new float[]{0f, .5f, 1f}, Shader.TileMode.CLAMP)); 3179840efe3dbdc7026521da8576574c55120782f6cChris Banes mEdgeShadowPaint.setAntiAlias(false); 3189840efe3dbdc7026521da8576574c55120782f6cChris Banes } 3199840efe3dbdc7026521da8576574c55120782f6cChris Banes 3209840efe3dbdc7026521da8576574c55120782f6cChris Banes private void buildComponents(Rect bounds) { 3219840efe3dbdc7026521da8576574c55120782f6cChris Banes // Card is offset SHADOW_MULTIPLIER * maxShadowSize to account for the shadow shift. 3229840efe3dbdc7026521da8576574c55120782f6cChris Banes // We could have different top-bottom offsets to avoid extra gap above but in that case 3239840efe3dbdc7026521da8576574c55120782f6cChris Banes // center aligning Views inside the CardView would be problematic. 3249840efe3dbdc7026521da8576574c55120782f6cChris Banes final float verticalOffset = mRawMaxShadowSize * SHADOW_MULTIPLIER; 3259840efe3dbdc7026521da8576574c55120782f6cChris Banes mContentBounds.set(bounds.left + mRawMaxShadowSize, bounds.top + verticalOffset, 3269840efe3dbdc7026521da8576574c55120782f6cChris Banes bounds.right - mRawMaxShadowSize, bounds.bottom - verticalOffset); 3279840efe3dbdc7026521da8576574c55120782f6cChris Banes 3289840efe3dbdc7026521da8576574c55120782f6cChris Banes getWrappedDrawable().setBounds((int) mContentBounds.left, (int) mContentBounds.top, 3299840efe3dbdc7026521da8576574c55120782f6cChris Banes (int) mContentBounds.right, (int) mContentBounds.bottom); 3309840efe3dbdc7026521da8576574c55120782f6cChris Banes 3319840efe3dbdc7026521da8576574c55120782f6cChris Banes buildShadowCorners(); 3329840efe3dbdc7026521da8576574c55120782f6cChris Banes } 3339840efe3dbdc7026521da8576574c55120782f6cChris Banes 3349840efe3dbdc7026521da8576574c55120782f6cChris Banes public float getCornerRadius() { 3359840efe3dbdc7026521da8576574c55120782f6cChris Banes return mCornerRadius; 3369840efe3dbdc7026521da8576574c55120782f6cChris Banes } 3379840efe3dbdc7026521da8576574c55120782f6cChris Banes 3389840efe3dbdc7026521da8576574c55120782f6cChris Banes public void setShadowSize(float size) { 3399840efe3dbdc7026521da8576574c55120782f6cChris Banes setShadowSize(size, mRawMaxShadowSize); 3409840efe3dbdc7026521da8576574c55120782f6cChris Banes } 3419840efe3dbdc7026521da8576574c55120782f6cChris Banes 3429840efe3dbdc7026521da8576574c55120782f6cChris Banes public void setMaxShadowSize(float size) { 3439840efe3dbdc7026521da8576574c55120782f6cChris Banes setShadowSize(mRawShadowSize, size); 3449840efe3dbdc7026521da8576574c55120782f6cChris Banes } 3459840efe3dbdc7026521da8576574c55120782f6cChris Banes 3469840efe3dbdc7026521da8576574c55120782f6cChris Banes public float getShadowSize() { 3479840efe3dbdc7026521da8576574c55120782f6cChris Banes return mRawShadowSize; 3489840efe3dbdc7026521da8576574c55120782f6cChris Banes } 3499840efe3dbdc7026521da8576574c55120782f6cChris Banes 3509840efe3dbdc7026521da8576574c55120782f6cChris Banes public float getMaxShadowSize() { 3519840efe3dbdc7026521da8576574c55120782f6cChris Banes return mRawMaxShadowSize; 3529840efe3dbdc7026521da8576574c55120782f6cChris Banes } 3539840efe3dbdc7026521da8576574c55120782f6cChris Banes 3549840efe3dbdc7026521da8576574c55120782f6cChris Banes public float getMinWidth() { 3559840efe3dbdc7026521da8576574c55120782f6cChris Banes final float content = 2 * 3569840efe3dbdc7026521da8576574c55120782f6cChris Banes Math.max(mRawMaxShadowSize, mCornerRadius + mRawMaxShadowSize / 2); 3579840efe3dbdc7026521da8576574c55120782f6cChris Banes return content + mRawMaxShadowSize * 2; 3589840efe3dbdc7026521da8576574c55120782f6cChris Banes } 3599840efe3dbdc7026521da8576574c55120782f6cChris Banes 3609840efe3dbdc7026521da8576574c55120782f6cChris Banes public float getMinHeight() { 3619840efe3dbdc7026521da8576574c55120782f6cChris Banes final float content = 2 * Math.max(mRawMaxShadowSize, mCornerRadius 3629840efe3dbdc7026521da8576574c55120782f6cChris Banes + mRawMaxShadowSize * SHADOW_MULTIPLIER / 2); 3639840efe3dbdc7026521da8576574c55120782f6cChris Banes return content + (mRawMaxShadowSize * SHADOW_MULTIPLIER) * 2; 3649840efe3dbdc7026521da8576574c55120782f6cChris Banes } 3659840efe3dbdc7026521da8576574c55120782f6cChris Banes} 366