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}