RoundRectDrawableWithShadow.java revision 18ef68d444a1c059041bf5b683eb612ffed22ea9
183b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar/* 283b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar * Copyright (C) 2014 The Android Open Source Project 383b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar * 483b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar * Licensed under the Apache License, Version 2.0 (the "License"); 583b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar * you may not use this file except in compliance with the License. 683b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar * You may obtain a copy of the License at 783b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar * 883b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar * http://www.apache.org/licenses/LICENSE-2.0 983b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar * 1083b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar * Unless required by applicable law or agreed to in writing, software 1183b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar * distributed under the License is distributed on an "AS IS" BASIS, 1283b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1383b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar * See the License for the specific language governing permissions and 1483b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar * limitations under the License. 1583b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar */ 1683b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyarpackage android.support.v7.widget; 1783b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar 1883b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyarimport android.content.res.Resources; 1983b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyarimport android.graphics.Canvas; 2083b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyarimport android.graphics.ColorFilter; 2183b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyarimport android.graphics.LinearGradient; 2283b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyarimport android.graphics.Paint; 2383b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyarimport android.graphics.Path; 2483b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyarimport android.graphics.PixelFormat; 2583b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyarimport android.graphics.RadialGradient; 2683b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyarimport android.graphics.Rect; 2783b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyarimport android.graphics.RectF; 2883b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyarimport android.graphics.Shader; 2983b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyarimport android.graphics.drawable.Drawable; 3083b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyarimport android.support.v7.cardview.R; 3118ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyarimport android.util.Log; 3283b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar 3383b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar/** 3483b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar * A rounded rectangle drawable which also includes a shadow around. 3583b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar */ 3683b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyarclass RoundRectDrawableWithShadow extends Drawable { 3718ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar // used to calculate content padding 3818ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar final static double COS_45 = Math.cos(Math.toRadians(45)); 3918ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar 40bc943f7fa746c149c5e4c3a4eed7febe494d5df5Yigit Boyar final static float SHADOW_MULTIPLIER = 1.5f; 4118ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar 4218ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar final float mInsetShadow; // extra shadow to avoid gaps between card and shadow 4318ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar 4483b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar /* 4583b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar * This helper is set by CardView implementations. 4683b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar * <p> 4783b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar * Prior to API 17, canvas.drawRoundRect is expensive; which is why we need this interface 4883b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar * to draw efficient rounded rectangles before 17. 4983b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar * */ 5083b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar static RoundRectHelper sRoundRectHelper; 5183b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar 5283b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar Paint mPaint; 5383b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar 5483b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar Paint mCornerShadowPaint; 5583b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar 5683b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar Paint mEdgeShadowPaint; 5783b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar 5818ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar final RectF mCardBounds; 5983b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar 6083b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar float mCornerRadius; 6183b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar 6283b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar Path mCornerShadowPath; 6383b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar 6418ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar // updated value with inset 6518ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar float mMaxShadowSize; 6618ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar 6718ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar // actual value set by developer 6818ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar float mRawMaxShadowSize; 6918ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar 7018ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar // multiplied value to account for shadow offset 71bc943f7fa746c149c5e4c3a4eed7febe494d5df5Yigit Boyar float mShadowSize; 7283b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar 7318ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar // actual value set by developer 7418ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar float mRawShadowSize; 7518ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar 7683b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar private boolean mDirty = true; 7783b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar 7883b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar private final int mShadowStartColor; 7983b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar 8083b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar private final int mShadowEndColor; 8183b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar 8218ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar /** 8318ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar * If shadow size is set to a value above max shadow, we print a warning 8418ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar */ 8518ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar private boolean mPrintedShadowClipWarning = false; 8618ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar 8718ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar RoundRectDrawableWithShadow(Resources resources, int backgroundColor, float radius, 8818ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar float shadowSize, float maxShadowSize) { 8983b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar mShadowStartColor = resources.getColor(R.color.cardview_shadow_start_color); 9083b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar mShadowEndColor = resources.getColor(R.color.cardview_shadow_end_color); 9118ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar mInsetShadow = resources.getDimension(R.dimen.cardview_compat_inset_shadow); 9218ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar setShadowSize(shadowSize, maxShadowSize); 9383b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG); 9483b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar mPaint.setColor(backgroundColor); 9583b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar mCornerShadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG); 9683b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar mCornerShadowPaint.setStyle(Paint.Style.FILL); 9783b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar mCornerShadowPaint.setDither(true); 9883b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar mCornerRadius = radius; 9918ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar mCardBounds = new RectF(); 10083b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar mEdgeShadowPaint = new Paint(mCornerShadowPaint); 10183b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar } 10283b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar 10383b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar @Override 10483b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar public void setAlpha(int alpha) { 10518ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar mPaint.setAlpha(alpha); 10618ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar mCornerShadowPaint.setAlpha(alpha); 10718ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar mEdgeShadowPaint.setAlpha(alpha); 10883b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar } 10983b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar 11083b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar @Override 11183b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar protected void onBoundsChange(Rect bounds) { 11283b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar super.onBoundsChange(bounds); 11383b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar mDirty = true; 11483b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar } 11583b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar 11618ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar void setShadowSize(float shadowSize, float maxShadowSize) { 11718ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar if (shadowSize < 0 || maxShadowSize < 0) { 11818ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar throw new IllegalArgumentException("invalid shadow size"); 11918ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar } 12018ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar if (shadowSize > maxShadowSize) { 12118ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar shadowSize = maxShadowSize; 12218ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar if (!mPrintedShadowClipWarning) { 12318ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar Log.w("CardView", "Shadow size is being clipped by the max shadow size. See " 12418ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar + "{CardView#setMaxCardElevation}."); 12518ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar mPrintedShadowClipWarning = true; 12618ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar } 12718ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar } 12818ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar if (mRawShadowSize == shadowSize && mRawMaxShadowSize == maxShadowSize) { 12918ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar return; 13018ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar } 13118ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar mRawShadowSize = shadowSize; 13218ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar mRawMaxShadowSize = maxShadowSize; 13318ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar mShadowSize = shadowSize * SHADOW_MULTIPLIER + mInsetShadow; 13418ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar mMaxShadowSize = maxShadowSize + mInsetShadow; 13518ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar mDirty = true; 13618ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar invalidateSelf(); 13718ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar } 13818ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar 13983b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar @Override 14083b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar public boolean getPadding(Rect padding) { 14118ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar int verticalOffset = (int) Math 14218ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar .round(mRawMaxShadowSize * SHADOW_MULTIPLIER + (1 - COS_45) * mCornerRadius); 14318ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar int horizontalOffset = (int) Math.round(mRawMaxShadowSize + (1 - COS_45) * mCornerRadius); 14418ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar padding.set(horizontalOffset, verticalOffset, horizontalOffset, verticalOffset); 14583b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar return true; 14683b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar } 14783b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar 14883b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar @Override 14983b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar public void setColorFilter(ColorFilter cf) { 15018ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar mPaint.setColorFilter(cf); 15118ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar mCornerShadowPaint.setColorFilter(cf); 15218ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar mEdgeShadowPaint.setColorFilter(cf); 15383b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar } 15483b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar 15583b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar @Override 15683b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar public int getOpacity() { 15783b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar return PixelFormat.OPAQUE; 15883b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar } 15983b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar 16083b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar void setCornerRadius(float radius) { 16183b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar if (mCornerRadius == radius) { 16283b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar return; 16383b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar } 16483b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar mCornerRadius = radius; 16583b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar mDirty = true; 16683b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar invalidateSelf(); 16783b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar } 16883b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar 16983b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar @Override 17083b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar public void draw(Canvas canvas) { 17183b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar if (mDirty) { 17283b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar buildComponents(getBounds()); 17383b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar mDirty = false; 17483b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar } 17518ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar canvas.translate(0, mRawShadowSize / 2); 17683b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar drawShadow(canvas); 17718ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar canvas.translate(0, -mRawShadowSize / 2); 17818ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar sRoundRectHelper.drawRoundRect(canvas, mCardBounds, mCornerRadius, mPaint); 17983b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar } 18083b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar 18183b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar private void drawShadow(Canvas canvas) { 18218ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar final float edgeShadowTop = -mCornerRadius - mShadowSize; 18318ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar final float inset = mCornerRadius + mInsetShadow + mRawShadowSize / 2; 18418ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar final boolean drawHorizontalEdges = mCardBounds.width() - 2 * inset > 0; 18518ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar final boolean drawVerticalEdges = mCardBounds.height() - 2 * inset > 0; 18683b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar // LT 18718ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar int saved = canvas.save(); 18818ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar canvas.translate(mCardBounds.left + inset, mCardBounds.top + inset); 18983b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar canvas.drawPath(mCornerShadowPath, mCornerShadowPaint); 19018ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar if (drawHorizontalEdges) { 19118ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar canvas.drawRect(0, edgeShadowTop, 19218ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar mCardBounds.width() - 2 * inset, -mCornerRadius, 19318ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar mEdgeShadowPaint); 19418ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar } 19518ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar canvas.restoreToCount(saved); 19683b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar // RB 19718ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar saved = canvas.save(); 19818ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar canvas.translate(mCardBounds.right - inset, mCardBounds.bottom - inset); 19983b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar canvas.rotate(180f); 20083b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar canvas.drawPath(mCornerShadowPath, mCornerShadowPaint); 20118ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar if (drawHorizontalEdges) { 20218ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar canvas.drawRect(0, edgeShadowTop, 20318ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar mCardBounds.width() - 2 * inset, -mCornerRadius + mShadowSize, 20418ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar mEdgeShadowPaint); 20518ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar } 20618ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar canvas.restoreToCount(saved); 20783b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar // LB 20818ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar saved = canvas.save(); 20918ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar canvas.translate(mCardBounds.left + inset, mCardBounds.bottom - inset); 21018ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar canvas.rotate(270f); 21183b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar canvas.drawPath(mCornerShadowPath, mCornerShadowPaint); 21218ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar if (drawVerticalEdges) { 21318ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar canvas.drawRect(0, edgeShadowTop, 21418ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar mCardBounds.height() - 2 * inset, -mCornerRadius, mEdgeShadowPaint); 21518ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar } 21618ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar canvas.restoreToCount(saved); 21783b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar // RT 21818ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar saved = canvas.save(); 21918ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar canvas.translate(mCardBounds.right - inset, mCardBounds.top + inset); 22018ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar canvas.rotate(90f); 22183b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar canvas.drawPath(mCornerShadowPath, mCornerShadowPaint); 22218ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar if (drawVerticalEdges) { 22318ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar canvas.drawRect(0, edgeShadowTop, 22418ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar mCardBounds.height() - 2 * inset, -mCornerRadius, mEdgeShadowPaint); 22518ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar } 22683b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar canvas.restoreToCount(saved); 22783b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar } 22883b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar 22983b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar private void buildShadowCorners() { 23083b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar RectF innerBounds = new RectF(-mCornerRadius, -mCornerRadius, mCornerRadius, mCornerRadius); 23183b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar RectF outerBounds = new RectF(innerBounds); 23283b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar outerBounds.inset(-mShadowSize, -mShadowSize); 23383b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar 23483b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar if (mCornerShadowPath == null) { 23583b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar mCornerShadowPath = new Path(); 23683b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar } else { 23783b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar mCornerShadowPath.reset(); 23883b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar } 23983b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar mCornerShadowPath.setFillType(Path.FillType.EVEN_ODD); 24083b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar mCornerShadowPath.moveTo(-mCornerRadius, 0); 24183b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar mCornerShadowPath.rLineTo(-mShadowSize, 0); 24283b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar // outer arc 24383b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar mCornerShadowPath.arcTo(outerBounds, 180f, 90f, false); 24483b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar // inner arc 24583b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar mCornerShadowPath.arcTo(innerBounds, 270f, -90f, false); 24683b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar mCornerShadowPath.close(); 24783b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar 24883b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar float startRatio = mCornerRadius / (mCornerRadius + mShadowSize); 24983b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar mCornerShadowPaint.setShader(new RadialGradient(0, 0, mCornerRadius + mShadowSize, 25083b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar new int[]{mShadowStartColor, mShadowStartColor, mShadowEndColor}, 25183b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar new float[]{0f, startRatio, 1f} 25283b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar , Shader.TileMode.CLAMP)); 25383b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar 25483b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar // we offset the content shadowSize/2 pixels up to make it more realistic. 25583b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar // this is why edge shadow shader has some extra space 25683b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar // When drawing bottom edge shadow, we use that extra space. 25783b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar mEdgeShadowPaint.setShader(new LinearGradient(0, -mCornerRadius + mShadowSize, 0, 25883b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar -mCornerRadius - mShadowSize, 25983b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar new int[]{mShadowStartColor, mShadowStartColor, mShadowEndColor}, 26083b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar new float[]{0f, .5f, 1f}, Shader.TileMode.CLAMP)); 26183b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar } 26283b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar 26383b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar private void buildComponents(Rect bounds) { 26418ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar // Card is offset SHADOW_MULTIPLIER * maxShadowSize to account for the shadow shift. 26518ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar // We could have different top-bottom offsets to avoid extra gap above but in that case 26618ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar // center aligning Views inside the CardView would be problematic. 26718ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar final float verticalOffset = mMaxShadowSize * SHADOW_MULTIPLIER; 26818ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar mCardBounds.set(bounds.left + mMaxShadowSize, bounds.top + verticalOffset, 26918ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar bounds.right - mMaxShadowSize, bounds.bottom - verticalOffset); 27083b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar buildShadowCorners(); 27183b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar } 27283b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar 27318ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar float getCornerRadius() { 27483b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar return mCornerRadius; 27583b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar } 27683b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar 27718ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar void getMaxShadowAndCornerPadding(Rect into) { 27818ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar getPadding(into); 27918ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar } 28018ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar 28118ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar void setShadowSize(float size) { 28218ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar setShadowSize(size, mRawMaxShadowSize); 28318ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar } 28418ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar 28518ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar void setMaxShadowSize(float size) { 28618ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar setShadowSize(mRawShadowSize, size); 28718ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar } 28818ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar 28918ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar float getShadowSize() { 29018ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar return mRawShadowSize; 29118ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar } 29218ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar 29318ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar float getMaxShadowSize() { 29418ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar return mRawMaxShadowSize; 29518ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar } 29618ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar 29718ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar float getMinWidth() { 29818ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar final float content = 2 * 29918ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar Math.max(mRawMaxShadowSize, mCornerRadius + mInsetShadow + mRawMaxShadowSize / 2); 30018ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar return content + (mRawMaxShadowSize + mInsetShadow) * 2; 30118ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar } 30218ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar 30318ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar float getMinHeight() { 30418ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar final float content = 2 * Math.max(mRawMaxShadowSize, mCornerRadius + mInsetShadow 30518ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar + mRawMaxShadowSize * SHADOW_MULTIPLIER / 2); 30618ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar return content + (mRawMaxShadowSize * SHADOW_MULTIPLIER + mInsetShadow) * 2; 30718ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar } 30818ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar 30983b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar static interface RoundRectHelper { 31083b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar void drawRoundRect(Canvas canvas, RectF bounds, float cornerRadius, Paint paint); 31183b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar } 31218ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar}