1cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi/*
2cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi * Copyright (C) 2014 The Android Open Source Project
3cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi *
4cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi * Licensed under the Apache License, Version 2.0 (the "License");
5cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi * you may not use this file except in compliance with the License.
6cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi * You may obtain a copy of the License at
7cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi *
8cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi *      http://www.apache.org/licenses/LICENSE-2.0
9cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi *
10cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi * Unless required by applicable law or agreed to in writing, software
11cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi * distributed under the License is distributed on an "AS IS" BASIS,
12cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi * See the License for the specific language governing permissions and
14cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi * limitations under the License
15cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi */
16cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggipackage com.android.systemui.recents.views;
17cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi
18cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggiimport android.content.res.Resources;
19cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggiimport android.graphics.Canvas;
20cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggiimport android.graphics.ColorFilter;
21cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggiimport android.graphics.LinearGradient;
22cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggiimport android.graphics.Paint;
23cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggiimport android.graphics.Path;
24cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggiimport android.graphics.PixelFormat;
25cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggiimport android.graphics.RadialGradient;
26cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggiimport android.graphics.Rect;
27cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggiimport android.graphics.RectF;
28cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggiimport android.graphics.Shader;
29cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggiimport android.graphics.drawable.Drawable;
30cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggiimport android.util.Log;
31cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi
32cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggiimport com.android.systemui.R;
33cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggiimport com.android.systemui.recents.RecentsConfiguration;
34cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi
35cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi/**
36cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi * A rounded rectangle drawable which also includes a shadow around. This is mostly copied from
37cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi * frameworks/support/v7/cardview/eclair-mr1/android/support/v7/widget/
38cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi * RoundRectDrawableWithShadow.java revision c42ba8c000d1e6ce85e152dfc17089a0a69e739f with a few
39cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi * modifications to suit our needs in SystemUI.
40cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi */
41cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggiclass FakeShadowDrawable extends Drawable {
42cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    // used to calculate content padding
43cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    final static double COS_45 = Math.cos(Math.toRadians(45));
44cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi
45cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    final static float SHADOW_MULTIPLIER = 1.5f;
46cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi
47cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    final float mInsetShadow; // extra shadow to avoid gaps between card and shadow
48cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi
49cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    Paint mCornerShadowPaint;
50cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi
51cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    Paint mEdgeShadowPaint;
52cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi
53cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    final RectF mCardBounds;
54cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi
55cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    float mCornerRadius;
56cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi
57cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    Path mCornerShadowPath;
58cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi
59cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    // updated value with inset
60cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    float mMaxShadowSize;
61cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi
62cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    // actual value set by developer
63cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    float mRawMaxShadowSize;
64cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi
65cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    // multiplied value to account for shadow offset
66cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    float mShadowSize;
67cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi
68cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    // actual value set by developer
69cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    float mRawShadowSize;
70cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi
71cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    private boolean mDirty = true;
72cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi
73cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    private final int mShadowStartColor;
74cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi
75cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    private final int mShadowEndColor;
76cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi
77cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    private boolean mAddPaddingForCorners = true;
78cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi
79cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    /**
80cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi     * If shadow size is set to a value above max shadow, we print a warning
81cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi     */
82cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    private boolean mPrintedShadowClipWarning = false;
83cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi
84cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    public FakeShadowDrawable(Resources resources, RecentsConfiguration config) {
85cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        mShadowStartColor = resources.getColor(R.color.fake_shadow_start_color);
86cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        mShadowEndColor = resources.getColor(R.color.fake_shadow_end_color);
87cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        mInsetShadow = resources.getDimension(R.dimen.fake_shadow_inset);
88cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        setShadowSize(resources.getDimensionPixelSize(R.dimen.fake_shadow_size),
89cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi                resources.getDimensionPixelSize(R.dimen.fake_shadow_size));
90cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        mCornerShadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
91cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        mCornerShadowPaint.setStyle(Paint.Style.FILL);
92cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        mCornerShadowPaint.setDither(true);
93cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        mCornerRadius = config.taskViewRoundedCornerRadiusPx;
94cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        mCardBounds = new RectF();
95cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        mEdgeShadowPaint = new Paint(mCornerShadowPaint);
96cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    }
97cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi
98cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    @Override
99cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    public void setAlpha(int alpha) {
100cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        mCornerShadowPaint.setAlpha(alpha);
101cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        mEdgeShadowPaint.setAlpha(alpha);
102cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    }
103cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi
104cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    @Override
105cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    protected void onBoundsChange(Rect bounds) {
106cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        super.onBoundsChange(bounds);
107cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        mDirty = true;
108cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    }
109cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi
110cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    void setShadowSize(float shadowSize, float maxShadowSize) {
111cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        if (shadowSize < 0 || maxShadowSize < 0) {
112cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi            throw new IllegalArgumentException("invalid shadow size");
113cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        }
114cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        if (shadowSize > maxShadowSize) {
115cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi            shadowSize = maxShadowSize;
116cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi            if (!mPrintedShadowClipWarning) {
117cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi                Log.w("CardView", "Shadow size is being clipped by the max shadow size. See "
118cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi                        + "{CardView#setMaxCardElevation}.");
119cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi                mPrintedShadowClipWarning = true;
120cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi            }
121cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        }
122cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        if (mRawShadowSize == shadowSize && mRawMaxShadowSize == maxShadowSize) {
123cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi            return;
124cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        }
125cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        mRawShadowSize = shadowSize;
126cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        mRawMaxShadowSize = maxShadowSize;
127cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        mShadowSize = shadowSize * SHADOW_MULTIPLIER + mInsetShadow;
128cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        mMaxShadowSize = maxShadowSize + mInsetShadow;
129cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        mDirty = true;
130cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        invalidateSelf();
131cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    }
132cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi
133cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    @Override
134cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    public boolean getPadding(Rect padding) {
135cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        int vOffset = (int) Math.ceil(calculateVerticalPadding(mRawMaxShadowSize, mCornerRadius,
136cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi                mAddPaddingForCorners));
137cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        int hOffset = (int) Math.ceil(calculateHorizontalPadding(mRawMaxShadowSize, mCornerRadius,
138cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi                mAddPaddingForCorners));
139cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        padding.set(hOffset, vOffset, hOffset, vOffset);
140cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        return true;
141cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    }
142cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi
143cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    static float calculateVerticalPadding(float maxShadowSize, float cornerRadius,
144cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi            boolean addPaddingForCorners) {
145cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        if (addPaddingForCorners) {
146cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi            return (float) (maxShadowSize * SHADOW_MULTIPLIER + (1 - COS_45) * cornerRadius);
147cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        } else {
148cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi            return maxShadowSize * SHADOW_MULTIPLIER;
149cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        }
150cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    }
151cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi
152cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    static float calculateHorizontalPadding(float maxShadowSize, float cornerRadius,
153cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi            boolean addPaddingForCorners) {
154cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        if (addPaddingForCorners) {
155cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi            return (float) (maxShadowSize + (1 - COS_45) * cornerRadius);
156cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        } else {
157cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi            return maxShadowSize;
158cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        }
159cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    }
160cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi
161cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    @Override
162cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    public void setColorFilter(ColorFilter cf) {
163cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        mCornerShadowPaint.setColorFilter(cf);
164cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        mEdgeShadowPaint.setColorFilter(cf);
165cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    }
166cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi
167cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    @Override
168cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    public int getOpacity() {
169cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        return PixelFormat.OPAQUE;
170cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    }
171cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi
172cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    @Override
173cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    public void draw(Canvas canvas) {
174cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        if (mDirty) {
175cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi            buildComponents(getBounds());
176cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi            mDirty = false;
177cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        }
178cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        canvas.translate(0, mRawShadowSize / 4);
179cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        drawShadow(canvas);
180cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        canvas.translate(0, -mRawShadowSize / 4);
181cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    }
182cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi
183cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    private void drawShadow(Canvas canvas) {
184cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        final float edgeShadowTop = -mCornerRadius - mShadowSize;
185cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        final float inset = mCornerRadius + mInsetShadow + mRawShadowSize / 2;
186cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        final boolean drawHorizontalEdges = mCardBounds.width() - 2 * inset > 0;
187cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        final boolean drawVerticalEdges = mCardBounds.height() - 2 * inset > 0;
188cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        // LT
189cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        int saved = canvas.save();
190cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        canvas.translate(mCardBounds.left + inset, mCardBounds.top + inset);
191cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
192cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        if (drawHorizontalEdges) {
193cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi            canvas.drawRect(0, edgeShadowTop,
194cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi                    mCardBounds.width() - 2 * inset, -mCornerRadius,
195cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi                    mEdgeShadowPaint);
196cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        }
197cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        canvas.restoreToCount(saved);
198cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        // RB
199cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        saved = canvas.save();
200cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        canvas.translate(mCardBounds.right - inset, mCardBounds.bottom - inset);
201cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        canvas.rotate(180f);
202cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
203cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        if (drawHorizontalEdges) {
204cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi            canvas.drawRect(0, edgeShadowTop,
205cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi                    mCardBounds.width() - 2 * inset, -mCornerRadius + mShadowSize,
206cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi                    mEdgeShadowPaint);
207cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        }
208cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        canvas.restoreToCount(saved);
209cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        // LB
210cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        saved = canvas.save();
211cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        canvas.translate(mCardBounds.left + inset, mCardBounds.bottom - inset);
212cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        canvas.rotate(270f);
213cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
214cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        if (drawVerticalEdges) {
215cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi            canvas.drawRect(0, edgeShadowTop,
216cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi                    mCardBounds.height() - 2 * inset, -mCornerRadius, mEdgeShadowPaint);
217cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        }
218cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        canvas.restoreToCount(saved);
219cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        // RT
220cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        saved = canvas.save();
221cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        canvas.translate(mCardBounds.right - inset, mCardBounds.top + inset);
222cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        canvas.rotate(90f);
223cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
224cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        if (drawVerticalEdges) {
225cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi            canvas.drawRect(0, edgeShadowTop,
226cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi                    mCardBounds.height() - 2 * inset, -mCornerRadius, mEdgeShadowPaint);
227cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        }
228cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        canvas.restoreToCount(saved);
229cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    }
230cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi
231cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    private void buildShadowCorners() {
232cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        RectF innerBounds = new RectF(-mCornerRadius, -mCornerRadius, mCornerRadius, mCornerRadius);
233cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        RectF outerBounds = new RectF(innerBounds);
234cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        outerBounds.inset(-mShadowSize, -mShadowSize);
235cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi
236cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        if (mCornerShadowPath == null) {
237cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi            mCornerShadowPath = new Path();
238cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        } else {
239cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi            mCornerShadowPath.reset();
240cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        }
241cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        mCornerShadowPath.setFillType(Path.FillType.EVEN_ODD);
242cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        mCornerShadowPath.moveTo(-mCornerRadius, 0);
243cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        mCornerShadowPath.rLineTo(-mShadowSize, 0);
244cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        // outer arc
245cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        mCornerShadowPath.arcTo(outerBounds, 180f, 90f, false);
246cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        // inner arc
247cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        mCornerShadowPath.arcTo(innerBounds, 270f, -90f, false);
248cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        mCornerShadowPath.close();
249cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi
250cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        float startRatio = mCornerRadius / (mCornerRadius + mShadowSize);
251cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        mCornerShadowPaint.setShader(new RadialGradient(0, 0, mCornerRadius + mShadowSize,
252cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi                new int[]{mShadowStartColor, mShadowStartColor, mShadowEndColor},
253cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi                new float[]{0f, startRatio, 1f}
254cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi                , Shader.TileMode.CLAMP));
255cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi
256cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        // we offset the content shadowSize/2 pixels up to make it more realistic.
257cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        // this is why edge shadow shader has some extra space
258cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        // When drawing bottom edge shadow, we use that extra space.
259cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        mEdgeShadowPaint.setShader(new LinearGradient(0, -mCornerRadius + mShadowSize, 0,
260cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi                -mCornerRadius - mShadowSize,
261cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi                new int[]{mShadowStartColor, mShadowStartColor, mShadowEndColor},
262cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi                new float[]{0f, .5f, 1f}, Shader.TileMode.CLAMP));
263cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    }
264cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi
265cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    private void buildComponents(Rect bounds) {
266cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        // Card is offset SHADOW_MULTIPLIER * maxShadowSize to account for the shadow shift.
267cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        // We could have different top-bottom offsets to avoid extra gap above but in that case
268cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        // center aligning Views inside the CardView would be problematic.
269cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        final float verticalOffset = mMaxShadowSize * SHADOW_MULTIPLIER;
270cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        mCardBounds.set(bounds.left + mMaxShadowSize, bounds.top + verticalOffset,
271cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi                bounds.right - mMaxShadowSize, bounds.bottom - verticalOffset);
272cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        buildShadowCorners();
273cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    }
274cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi
275cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    float getMinWidth() {
276cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        final float content = 2 *
277cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi                Math.max(mRawMaxShadowSize, mCornerRadius + mInsetShadow + mRawMaxShadowSize / 2);
278cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        return content + (mRawMaxShadowSize + mInsetShadow) * 2;
279cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    }
280cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi
281cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    float getMinHeight() {
282cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        final float content = 2 * Math.max(mRawMaxShadowSize, mCornerRadius + mInsetShadow
283cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi                        + mRawMaxShadowSize * SHADOW_MULTIPLIER / 2);
284cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        return content + (mRawMaxShadowSize * SHADOW_MULTIPLIER + mInsetShadow) * 2;
285cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    }
286cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi}