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
18d2c60296856244030e9a92ef3065d3884ac67527Aurimas Liutikasimport android.content.res.ColorStateList;
1983b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyarimport android.content.res.Resources;
2083b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyarimport android.graphics.Canvas;
21d2c60296856244030e9a92ef3065d3884ac67527Aurimas Liutikasimport android.graphics.Color;
2283b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyarimport android.graphics.ColorFilter;
2383b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyarimport android.graphics.LinearGradient;
2483b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyarimport android.graphics.Paint;
2583b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyarimport android.graphics.Path;
2683b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyarimport android.graphics.PixelFormat;
2783b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyarimport android.graphics.RadialGradient;
2883b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyarimport android.graphics.Rect;
2983b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyarimport android.graphics.RectF;
3083b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyarimport android.graphics.Shader;
3183b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyarimport android.graphics.drawable.Drawable;
32d2c60296856244030e9a92ef3065d3884ac67527Aurimas Liutikasimport android.support.annotation.Nullable;
3383b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyarimport android.support.v7.cardview.R;
3483b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar
3583b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar/**
3683b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar * A rounded rectangle drawable which also includes a shadow around.
3783b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar */
3883b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyarclass RoundRectDrawableWithShadow extends Drawable {
3918ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar    // used to calculate content padding
4018ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar    final static double COS_45 = Math.cos(Math.toRadians(45));
4118ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar
42bc943f7fa746c149c5e4c3a4eed7febe494d5df5Yigit Boyar    final static float SHADOW_MULTIPLIER = 1.5f;
4318ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar
44db27cfc95b2f12aefacdd4d656910650a7fd4663Yigit Boyar    final int mInsetShadow; // extra shadow to avoid gaps between card and shadow
4518ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar
4683b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar    /*
4783b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar    * This helper is set by CardView implementations.
4883b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar    * <p>
4983b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar    * Prior to API 17, canvas.drawRoundRect is expensive; which is why we need this interface
5083b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar    * to draw efficient rounded rectangles before 17.
5183b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar    * */
5283b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar    static RoundRectHelper sRoundRectHelper;
5383b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar
5483b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar    Paint mPaint;
5583b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar
5683b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar    Paint mCornerShadowPaint;
5783b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar
5883b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar    Paint mEdgeShadowPaint;
5983b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar
6018ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar    final RectF mCardBounds;
6183b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar
6283b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar    float mCornerRadius;
6383b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar
6483b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar    Path mCornerShadowPath;
6583b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar
6618ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar    // updated value with inset
6718ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar    float mMaxShadowSize;
6818ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar
6918ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar    // actual value set by developer
7018ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar    float mRawMaxShadowSize;
7118ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar
7218ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar    // multiplied value to account for shadow offset
73bc943f7fa746c149c5e4c3a4eed7febe494d5df5Yigit Boyar    float mShadowSize;
7483b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar
7518ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar    // actual value set by developer
7618ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar    float mRawShadowSize;
7718ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar
78d2c60296856244030e9a92ef3065d3884ac67527Aurimas Liutikas    private ColorStateList mBackground;
79d2c60296856244030e9a92ef3065d3884ac67527Aurimas Liutikas
8083b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar    private boolean mDirty = true;
8183b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar
8283b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar    private final int mShadowStartColor;
8383b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar
8483b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar    private final int mShadowEndColor;
8583b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar
86c42ba8c000d1e6ce85e152dfc17089a0a69e739fYigit Boyar    private boolean mAddPaddingForCorners = true;
87c42ba8c000d1e6ce85e152dfc17089a0a69e739fYigit Boyar
8818ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar    /**
8918ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar     * If shadow size is set to a value above max shadow, we print a warning
9018ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar     */
9118ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar    private boolean mPrintedShadowClipWarning = false;
9218ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar
93d2c60296856244030e9a92ef3065d3884ac67527Aurimas Liutikas    RoundRectDrawableWithShadow(Resources resources, ColorStateList backgroundColor, float radius,
9418ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar            float shadowSize, float maxShadowSize) {
9583b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar        mShadowStartColor = resources.getColor(R.color.cardview_shadow_start_color);
9683b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar        mShadowEndColor = resources.getColor(R.color.cardview_shadow_end_color);
97db27cfc95b2f12aefacdd4d656910650a7fd4663Yigit Boyar        mInsetShadow = resources.getDimensionPixelSize(R.dimen.cardview_compat_inset_shadow);
9883b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
99d2c60296856244030e9a92ef3065d3884ac67527Aurimas Liutikas        setBackground(backgroundColor);
10083b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar        mCornerShadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
10183b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar        mCornerShadowPaint.setStyle(Paint.Style.FILL);
102db27cfc95b2f12aefacdd4d656910650a7fd4663Yigit Boyar        mCornerRadius = (int) (radius + .5f);
10318ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar        mCardBounds = new RectF();
10483b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar        mEdgeShadowPaint = new Paint(mCornerShadowPaint);
105db27cfc95b2f12aefacdd4d656910650a7fd4663Yigit Boyar        mEdgeShadowPaint.setAntiAlias(false);
106db27cfc95b2f12aefacdd4d656910650a7fd4663Yigit Boyar        setShadowSize(shadowSize, maxShadowSize);
107db27cfc95b2f12aefacdd4d656910650a7fd4663Yigit Boyar    }
108db27cfc95b2f12aefacdd4d656910650a7fd4663Yigit Boyar
109d2c60296856244030e9a92ef3065d3884ac67527Aurimas Liutikas    private void setBackground(ColorStateList color) {
110d2c60296856244030e9a92ef3065d3884ac67527Aurimas Liutikas        mBackground = (color == null) ?  ColorStateList.valueOf(Color.TRANSPARENT) : color;
111d2c60296856244030e9a92ef3065d3884ac67527Aurimas Liutikas        mPaint.setColor(mBackground.getColorForState(getState(), mBackground.getDefaultColor()));
112d2c60296856244030e9a92ef3065d3884ac67527Aurimas Liutikas    }
113d2c60296856244030e9a92ef3065d3884ac67527Aurimas Liutikas
114db27cfc95b2f12aefacdd4d656910650a7fd4663Yigit Boyar    /**
115db27cfc95b2f12aefacdd4d656910650a7fd4663Yigit Boyar     * Casts the value to an even integer.
116db27cfc95b2f12aefacdd4d656910650a7fd4663Yigit Boyar     */
117db27cfc95b2f12aefacdd4d656910650a7fd4663Yigit Boyar    private int toEven(float value) {
118db27cfc95b2f12aefacdd4d656910650a7fd4663Yigit Boyar        int i = (int) (value + .5f);
119db27cfc95b2f12aefacdd4d656910650a7fd4663Yigit Boyar        if (i % 2 == 1) {
120db27cfc95b2f12aefacdd4d656910650a7fd4663Yigit Boyar            return i - 1;
121db27cfc95b2f12aefacdd4d656910650a7fd4663Yigit Boyar        }
122db27cfc95b2f12aefacdd4d656910650a7fd4663Yigit Boyar        return i;
12383b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar    }
12483b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar
125c42ba8c000d1e6ce85e152dfc17089a0a69e739fYigit Boyar    public void setAddPaddingForCorners(boolean addPaddingForCorners) {
126c42ba8c000d1e6ce85e152dfc17089a0a69e739fYigit Boyar        mAddPaddingForCorners = addPaddingForCorners;
127c42ba8c000d1e6ce85e152dfc17089a0a69e739fYigit Boyar        invalidateSelf();
128c42ba8c000d1e6ce85e152dfc17089a0a69e739fYigit Boyar    }
129c42ba8c000d1e6ce85e152dfc17089a0a69e739fYigit Boyar
13083b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar    @Override
13183b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar    public void setAlpha(int alpha) {
13218ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar        mPaint.setAlpha(alpha);
13318ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar        mCornerShadowPaint.setAlpha(alpha);
13418ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar        mEdgeShadowPaint.setAlpha(alpha);
13583b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar    }
13683b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar
13783b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar    @Override
13883b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar    protected void onBoundsChange(Rect bounds) {
13983b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar        super.onBoundsChange(bounds);
14083b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar        mDirty = true;
14183b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar    }
14283b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar
14318ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar    void setShadowSize(float shadowSize, float maxShadowSize) {
144e4548b3ac721f2b85f7b030d7043c94b24288670Kirill Grouchnikov        if (shadowSize < 0f) {
145e4548b3ac721f2b85f7b030d7043c94b24288670Kirill Grouchnikov            throw new IllegalArgumentException("Invalid shadow size " + shadowSize +
146e4548b3ac721f2b85f7b030d7043c94b24288670Kirill Grouchnikov                    ". Must be >= 0");
147e4548b3ac721f2b85f7b030d7043c94b24288670Kirill Grouchnikov        }
148e4548b3ac721f2b85f7b030d7043c94b24288670Kirill Grouchnikov        if (maxShadowSize < 0f) {
149e4548b3ac721f2b85f7b030d7043c94b24288670Kirill Grouchnikov            throw new IllegalArgumentException("Invalid max shadow size " + maxShadowSize +
150e4548b3ac721f2b85f7b030d7043c94b24288670Kirill Grouchnikov                    ". Must be >= 0");
15118ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar        }
152db27cfc95b2f12aefacdd4d656910650a7fd4663Yigit Boyar        shadowSize = toEven(shadowSize);
153db27cfc95b2f12aefacdd4d656910650a7fd4663Yigit Boyar        maxShadowSize = toEven(maxShadowSize);
15418ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar        if (shadowSize > maxShadowSize) {
15518ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar            shadowSize = maxShadowSize;
15618ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar            if (!mPrintedShadowClipWarning) {
15718ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar                mPrintedShadowClipWarning = true;
15818ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar            }
15918ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar        }
16018ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar        if (mRawShadowSize == shadowSize && mRawMaxShadowSize == maxShadowSize) {
16118ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar            return;
16218ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar        }
16318ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar        mRawShadowSize = shadowSize;
16418ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar        mRawMaxShadowSize = maxShadowSize;
165db27cfc95b2f12aefacdd4d656910650a7fd4663Yigit Boyar        mShadowSize = (int)(shadowSize * SHADOW_MULTIPLIER + mInsetShadow + .5f);
16618ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar        mMaxShadowSize = maxShadowSize + mInsetShadow;
16718ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar        mDirty = true;
16818ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar        invalidateSelf();
16918ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar    }
17018ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar
17183b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar    @Override
17283b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar    public boolean getPadding(Rect padding) {
173c42ba8c000d1e6ce85e152dfc17089a0a69e739fYigit Boyar        int vOffset = (int) Math.ceil(calculateVerticalPadding(mRawMaxShadowSize, mCornerRadius,
174c42ba8c000d1e6ce85e152dfc17089a0a69e739fYigit Boyar                mAddPaddingForCorners));
175c42ba8c000d1e6ce85e152dfc17089a0a69e739fYigit Boyar        int hOffset = (int) Math.ceil(calculateHorizontalPadding(mRawMaxShadowSize, mCornerRadius,
176c42ba8c000d1e6ce85e152dfc17089a0a69e739fYigit Boyar                mAddPaddingForCorners));
177bdb07a1802c017efa64a5cfd8ab5a7ff4c4926b0Yigit Boyar        padding.set(hOffset, vOffset, hOffset, vOffset);
17883b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar        return true;
17983b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar    }
18083b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar
181c42ba8c000d1e6ce85e152dfc17089a0a69e739fYigit Boyar    static float calculateVerticalPadding(float maxShadowSize, float cornerRadius,
182c42ba8c000d1e6ce85e152dfc17089a0a69e739fYigit Boyar            boolean addPaddingForCorners) {
183c42ba8c000d1e6ce85e152dfc17089a0a69e739fYigit Boyar        if (addPaddingForCorners) {
184c42ba8c000d1e6ce85e152dfc17089a0a69e739fYigit Boyar            return (float) (maxShadowSize * SHADOW_MULTIPLIER + (1 - COS_45) * cornerRadius);
185c42ba8c000d1e6ce85e152dfc17089a0a69e739fYigit Boyar        } else {
186c42ba8c000d1e6ce85e152dfc17089a0a69e739fYigit Boyar            return maxShadowSize * SHADOW_MULTIPLIER;
187c42ba8c000d1e6ce85e152dfc17089a0a69e739fYigit Boyar        }
188bdb07a1802c017efa64a5cfd8ab5a7ff4c4926b0Yigit Boyar    }
189bdb07a1802c017efa64a5cfd8ab5a7ff4c4926b0Yigit Boyar
190c42ba8c000d1e6ce85e152dfc17089a0a69e739fYigit Boyar    static float calculateHorizontalPadding(float maxShadowSize, float cornerRadius,
191c42ba8c000d1e6ce85e152dfc17089a0a69e739fYigit Boyar            boolean addPaddingForCorners) {
192c42ba8c000d1e6ce85e152dfc17089a0a69e739fYigit Boyar        if (addPaddingForCorners) {
193c42ba8c000d1e6ce85e152dfc17089a0a69e739fYigit Boyar            return (float) (maxShadowSize + (1 - COS_45) * cornerRadius);
194c42ba8c000d1e6ce85e152dfc17089a0a69e739fYigit Boyar        } else {
195c42ba8c000d1e6ce85e152dfc17089a0a69e739fYigit Boyar            return maxShadowSize;
196c42ba8c000d1e6ce85e152dfc17089a0a69e739fYigit Boyar        }
197bdb07a1802c017efa64a5cfd8ab5a7ff4c4926b0Yigit Boyar    }
198bdb07a1802c017efa64a5cfd8ab5a7ff4c4926b0Yigit Boyar
19983b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar    @Override
200d2c60296856244030e9a92ef3065d3884ac67527Aurimas Liutikas    protected boolean onStateChange(int[] stateSet) {
201d2c60296856244030e9a92ef3065d3884ac67527Aurimas Liutikas        final int newColor = mBackground.getColorForState(stateSet, mBackground.getDefaultColor());
202d2c60296856244030e9a92ef3065d3884ac67527Aurimas Liutikas        if (mPaint.getColor() == newColor) {
203d2c60296856244030e9a92ef3065d3884ac67527Aurimas Liutikas            return false;
204d2c60296856244030e9a92ef3065d3884ac67527Aurimas Liutikas        }
205d2c60296856244030e9a92ef3065d3884ac67527Aurimas Liutikas        mPaint.setColor(newColor);
206d2c60296856244030e9a92ef3065d3884ac67527Aurimas Liutikas        mDirty = true;
207d2c60296856244030e9a92ef3065d3884ac67527Aurimas Liutikas        invalidateSelf();
208d2c60296856244030e9a92ef3065d3884ac67527Aurimas Liutikas        return true;
209d2c60296856244030e9a92ef3065d3884ac67527Aurimas Liutikas    }
210d2c60296856244030e9a92ef3065d3884ac67527Aurimas Liutikas
211d2c60296856244030e9a92ef3065d3884ac67527Aurimas Liutikas    @Override
212d2c60296856244030e9a92ef3065d3884ac67527Aurimas Liutikas    public boolean isStateful() {
213d2c60296856244030e9a92ef3065d3884ac67527Aurimas Liutikas        return (mBackground != null && mBackground.isStateful()) || super.isStateful();
214d2c60296856244030e9a92ef3065d3884ac67527Aurimas Liutikas    }
215d2c60296856244030e9a92ef3065d3884ac67527Aurimas Liutikas
216d2c60296856244030e9a92ef3065d3884ac67527Aurimas Liutikas    @Override
21783b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar    public void setColorFilter(ColorFilter cf) {
21818ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar        mPaint.setColorFilter(cf);
21983b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar    }
22083b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar
22183b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar    @Override
22283b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar    public int getOpacity() {
223ad124ea5fa71f1b675c9c42b858adfbc42093d37Yigit Boyar        return PixelFormat.TRANSLUCENT;
22483b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar    }
22583b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar
22683b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar    void setCornerRadius(float radius) {
227e4548b3ac721f2b85f7b030d7043c94b24288670Kirill Grouchnikov        if (radius < 0f) {
228e4548b3ac721f2b85f7b030d7043c94b24288670Kirill Grouchnikov            throw new IllegalArgumentException("Invalid radius " + radius +
229e4548b3ac721f2b85f7b030d7043c94b24288670Kirill Grouchnikov                ". Must be >= 0");
230e4548b3ac721f2b85f7b030d7043c94b24288670Kirill Grouchnikov        }
231db27cfc95b2f12aefacdd4d656910650a7fd4663Yigit Boyar        radius = (int) (radius + .5f);
23283b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar        if (mCornerRadius == radius) {
23383b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar            return;
23483b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar        }
23583b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar        mCornerRadius = radius;
23683b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar        mDirty = true;
23783b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar        invalidateSelf();
23883b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar    }
23983b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar
24083b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar    @Override
24183b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar    public void draw(Canvas canvas) {
24283b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar        if (mDirty) {
24383b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar            buildComponents(getBounds());
24483b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar            mDirty = false;
24583b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar        }
24618ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar        canvas.translate(0, mRawShadowSize / 2);
24783b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar        drawShadow(canvas);
24818ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar        canvas.translate(0, -mRawShadowSize / 2);
24918ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar        sRoundRectHelper.drawRoundRect(canvas, mCardBounds, mCornerRadius, mPaint);
25083b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar    }
25183b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar
25283b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar    private void drawShadow(Canvas canvas) {
25318ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar        final float edgeShadowTop = -mCornerRadius - mShadowSize;
25418ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar        final float inset = mCornerRadius + mInsetShadow + mRawShadowSize / 2;
25518ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar        final boolean drawHorizontalEdges = mCardBounds.width() - 2 * inset > 0;
25618ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar        final boolean drawVerticalEdges = mCardBounds.height() - 2 * inset > 0;
25783b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar        // LT
25818ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar        int saved = canvas.save();
25918ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar        canvas.translate(mCardBounds.left + inset, mCardBounds.top + inset);
26083b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar        canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
26118ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar        if (drawHorizontalEdges) {
26218ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar            canvas.drawRect(0, edgeShadowTop,
26318ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar                    mCardBounds.width() - 2 * inset, -mCornerRadius,
26418ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar                    mEdgeShadowPaint);
26518ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar        }
26618ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar        canvas.restoreToCount(saved);
26783b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar        // RB
26818ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar        saved = canvas.save();
26918ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar        canvas.translate(mCardBounds.right - inset, mCardBounds.bottom - inset);
27083b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar        canvas.rotate(180f);
27183b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar        canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
27218ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar        if (drawHorizontalEdges) {
27318ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar            canvas.drawRect(0, edgeShadowTop,
27418ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar                    mCardBounds.width() - 2 * inset, -mCornerRadius + mShadowSize,
27518ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar                    mEdgeShadowPaint);
27618ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar        }
27718ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar        canvas.restoreToCount(saved);
27883b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar        // LB
27918ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar        saved = canvas.save();
28018ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar        canvas.translate(mCardBounds.left + inset, mCardBounds.bottom - inset);
28118ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar        canvas.rotate(270f);
28283b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar        canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
28318ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar        if (drawVerticalEdges) {
28418ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar            canvas.drawRect(0, edgeShadowTop,
28518ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar                    mCardBounds.height() - 2 * inset, -mCornerRadius, mEdgeShadowPaint);
28618ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar        }
28718ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar        canvas.restoreToCount(saved);
28883b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar        // RT
28918ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar        saved = canvas.save();
29018ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar        canvas.translate(mCardBounds.right - inset, mCardBounds.top + inset);
29118ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar        canvas.rotate(90f);
29283b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar        canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
29318ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar        if (drawVerticalEdges) {
29418ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar            canvas.drawRect(0, edgeShadowTop,
29518ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar                    mCardBounds.height() - 2 * inset, -mCornerRadius, mEdgeShadowPaint);
29618ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar        }
29783b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar        canvas.restoreToCount(saved);
29883b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar    }
29983b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar
30083b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar    private void buildShadowCorners() {
30183b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar        RectF innerBounds = new RectF(-mCornerRadius, -mCornerRadius, mCornerRadius, mCornerRadius);
30283b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar        RectF outerBounds = new RectF(innerBounds);
30383b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar        outerBounds.inset(-mShadowSize, -mShadowSize);
30483b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar
30583b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar        if (mCornerShadowPath == null) {
30683b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar            mCornerShadowPath = new Path();
30783b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar        } else {
30883b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar            mCornerShadowPath.reset();
30983b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar        }
31083b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar        mCornerShadowPath.setFillType(Path.FillType.EVEN_ODD);
31183b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar        mCornerShadowPath.moveTo(-mCornerRadius, 0);
31283b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar        mCornerShadowPath.rLineTo(-mShadowSize, 0);
31383b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar        // outer arc
31483b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar        mCornerShadowPath.arcTo(outerBounds, 180f, 90f, false);
31583b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar        // inner arc
31683b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar        mCornerShadowPath.arcTo(innerBounds, 270f, -90f, false);
31783b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar        mCornerShadowPath.close();
31883b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar        float startRatio = mCornerRadius / (mCornerRadius + mShadowSize);
31983b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar        mCornerShadowPaint.setShader(new RadialGradient(0, 0, mCornerRadius + mShadowSize,
32083b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar                new int[]{mShadowStartColor, mShadowStartColor, mShadowEndColor},
32183b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar                new float[]{0f, startRatio, 1f}
32283b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar                , Shader.TileMode.CLAMP));
32383b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar
32483b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar        // we offset the content shadowSize/2 pixels up to make it more realistic.
32583b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar        // this is why edge shadow shader has some extra space
32683b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar        // When drawing bottom edge shadow, we use that extra space.
32783b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar        mEdgeShadowPaint.setShader(new LinearGradient(0, -mCornerRadius + mShadowSize, 0,
32883b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar                -mCornerRadius - mShadowSize,
32983b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar                new int[]{mShadowStartColor, mShadowStartColor, mShadowEndColor},
33083b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar                new float[]{0f, .5f, 1f}, Shader.TileMode.CLAMP));
331db27cfc95b2f12aefacdd4d656910650a7fd4663Yigit Boyar        mEdgeShadowPaint.setAntiAlias(false);
33283b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar    }
33383b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar
33483b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar    private void buildComponents(Rect bounds) {
33518ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar        // Card is offset SHADOW_MULTIPLIER * maxShadowSize to account for the shadow shift.
33618ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar        // We could have different top-bottom offsets to avoid extra gap above but in that case
33718ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar        // center aligning Views inside the CardView would be problematic.
338ec224ed0e481c64dbd13f740e15040a97b62daefYigit Boyar        final float verticalOffset = mRawMaxShadowSize * SHADOW_MULTIPLIER;
339ec224ed0e481c64dbd13f740e15040a97b62daefYigit Boyar        mCardBounds.set(bounds.left + mRawMaxShadowSize, bounds.top + verticalOffset,
340ec224ed0e481c64dbd13f740e15040a97b62daefYigit Boyar                bounds.right - mRawMaxShadowSize, bounds.bottom - verticalOffset);
34183b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar        buildShadowCorners();
34283b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar    }
34383b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar
34418ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar    float getCornerRadius() {
34583b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar        return mCornerRadius;
34683b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar    }
34783b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar
34818ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar    void getMaxShadowAndCornerPadding(Rect into) {
34918ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar        getPadding(into);
35018ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar    }
35118ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar
35218ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar    void setShadowSize(float size) {
35318ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar        setShadowSize(size, mRawMaxShadowSize);
35418ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar    }
35518ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar
35618ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar    void setMaxShadowSize(float size) {
35718ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar        setShadowSize(mRawShadowSize, size);
35818ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar    }
35918ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar
36018ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar    float getShadowSize() {
36118ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar        return mRawShadowSize;
36218ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar    }
36318ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar
36418ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar    float getMaxShadowSize() {
36518ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar        return mRawMaxShadowSize;
36618ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar    }
36718ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar
36818ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar    float getMinWidth() {
36918ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar        final float content = 2 *
37018ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar                Math.max(mRawMaxShadowSize, mCornerRadius + mInsetShadow + mRawMaxShadowSize / 2);
37118ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar        return content + (mRawMaxShadowSize + mInsetShadow) * 2;
37218ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar    }
37318ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar
37418ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar    float getMinHeight() {
37518ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar        final float content = 2 * Math.max(mRawMaxShadowSize, mCornerRadius + mInsetShadow
37618ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar                        + mRawMaxShadowSize * SHADOW_MULTIPLIER / 2);
37718ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar        return content + (mRawMaxShadowSize * SHADOW_MULTIPLIER + mInsetShadow) * 2;
37818ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar    }
37918ef68d444a1c059041bf5b683eb612ffed22ea9Yigit Boyar
380e7c701f05b4f6afa2913c3743638d7b25b96df83Aurimas Liutikas    void setColor(@Nullable ColorStateList color) {
381d2c60296856244030e9a92ef3065d3884ac67527Aurimas Liutikas        setBackground(color);
3824eb77f0ed24a9d300f7d12959de8cf7efd837e2fYigit Boyar        invalidateSelf();
3834eb77f0ed24a9d300f7d12959de8cf7efd837e2fYigit Boyar    }
3844eb77f0ed24a9d300f7d12959de8cf7efd837e2fYigit Boyar
385e7c701f05b4f6afa2913c3743638d7b25b96df83Aurimas Liutikas    ColorStateList getColor() {
386e7c701f05b4f6afa2913c3743638d7b25b96df83Aurimas Liutikas        return mBackground;
387e7c701f05b4f6afa2913c3743638d7b25b96df83Aurimas Liutikas    }
388e7c701f05b4f6afa2913c3743638d7b25b96df83Aurimas Liutikas
38983b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar    static interface RoundRectHelper {
39083b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar        void drawRoundRect(Canvas canvas, RectF bounds, float cornerRadius, Paint paint);
39183b8526436ba2e564dff99ec4c6cf46fabfdf22eYigit Boyar    }
392767f1e75ad6e96042c6e4466c363b285a8bfda68Yigit Boyar}
393