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