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;
31c0d7058b14c24cd07912f5629c26b39b7b4673d5Winson
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);
9335f3050959e43bf378f9a0adcaef13729206c7e4Winson        mCornerRadius = resources.getDimensionPixelSize(
9435f3050959e43bf378f9a0adcaef13729206c7e4Winson                R.dimen.recents_task_view_rounded_corners_radius);
95cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        mCardBounds = new RectF();
96cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        mEdgeShadowPaint = new Paint(mCornerShadowPaint);
97cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    }
98cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi
99cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    @Override
100cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    public void setAlpha(int alpha) {
101cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        mCornerShadowPaint.setAlpha(alpha);
102cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        mEdgeShadowPaint.setAlpha(alpha);
103cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    }
104cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi
105cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    @Override
106cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    protected void onBoundsChange(Rect bounds) {
107cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        super.onBoundsChange(bounds);
108cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        mDirty = true;
109cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    }
110cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi
111cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    void setShadowSize(float shadowSize, float maxShadowSize) {
112cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        if (shadowSize < 0 || maxShadowSize < 0) {
113cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi            throw new IllegalArgumentException("invalid shadow size");
114cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        }
115cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        if (shadowSize > maxShadowSize) {
116cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi            shadowSize = maxShadowSize;
117cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi            if (!mPrintedShadowClipWarning) {
118cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi                Log.w("CardView", "Shadow size is being clipped by the max shadow size. See "
119cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi                        + "{CardView#setMaxCardElevation}.");
120cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi                mPrintedShadowClipWarning = true;
121cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi            }
122cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        }
123cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        if (mRawShadowSize == shadowSize && mRawMaxShadowSize == maxShadowSize) {
124cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi            return;
125cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        }
126cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        mRawShadowSize = shadowSize;
127cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        mRawMaxShadowSize = maxShadowSize;
128cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        mShadowSize = shadowSize * SHADOW_MULTIPLIER + mInsetShadow;
129cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        mMaxShadowSize = maxShadowSize + mInsetShadow;
130cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        mDirty = true;
131cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        invalidateSelf();
132cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    }
133cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi
134cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    @Override
135cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    public boolean getPadding(Rect padding) {
136cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        int vOffset = (int) Math.ceil(calculateVerticalPadding(mRawMaxShadowSize, mCornerRadius,
137cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi                mAddPaddingForCorners));
138cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        int hOffset = (int) Math.ceil(calculateHorizontalPadding(mRawMaxShadowSize, mCornerRadius,
139cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi                mAddPaddingForCorners));
140cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        padding.set(hOffset, vOffset, hOffset, vOffset);
141cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        return true;
142cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    }
143cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi
144cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    static float calculateVerticalPadding(float maxShadowSize, float cornerRadius,
145cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi            boolean addPaddingForCorners) {
146cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        if (addPaddingForCorners) {
147cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi            return (float) (maxShadowSize * SHADOW_MULTIPLIER + (1 - COS_45) * cornerRadius);
148cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        } else {
149cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi            return maxShadowSize * SHADOW_MULTIPLIER;
150cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        }
151cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    }
152cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi
153cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    static float calculateHorizontalPadding(float maxShadowSize, float cornerRadius,
154cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi            boolean addPaddingForCorners) {
155cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        if (addPaddingForCorners) {
156cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi            return (float) (maxShadowSize + (1 - COS_45) * cornerRadius);
157cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        } else {
158cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi            return maxShadowSize;
159cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        }
160cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    }
161cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi
162cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    @Override
163bd3bfc5285dcacff0a69fecf3baeeeb90d887a58Chris Craik    public void setColorFilter(ColorFilter colorFilter) {
164bd3bfc5285dcacff0a69fecf3baeeeb90d887a58Chris Craik        mCornerShadowPaint.setColorFilter(colorFilter);
165bd3bfc5285dcacff0a69fecf3baeeeb90d887a58Chris Craik        mEdgeShadowPaint.setColorFilter(colorFilter);
166cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    }
167cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi
168cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    @Override
169cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    public int getOpacity() {
170cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        return PixelFormat.OPAQUE;
171cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    }
172cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi
173cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    @Override
174cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    public void draw(Canvas canvas) {
175cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        if (mDirty) {
176cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi            buildComponents(getBounds());
177cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi            mDirty = false;
178cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        }
179cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        canvas.translate(0, mRawShadowSize / 4);
180cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        drawShadow(canvas);
181cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        canvas.translate(0, -mRawShadowSize / 4);
182cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    }
183cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi
184cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    private void drawShadow(Canvas canvas) {
185cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        final float edgeShadowTop = -mCornerRadius - mShadowSize;
186cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        final float inset = mCornerRadius + mInsetShadow + mRawShadowSize / 2;
187cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        final boolean drawHorizontalEdges = mCardBounds.width() - 2 * inset > 0;
188cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        final boolean drawVerticalEdges = mCardBounds.height() - 2 * inset > 0;
189cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        // LT
190cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        int saved = canvas.save();
191cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        canvas.translate(mCardBounds.left + inset, mCardBounds.top + inset);
192cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
193cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        if (drawHorizontalEdges) {
194cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi            canvas.drawRect(0, edgeShadowTop,
195cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi                    mCardBounds.width() - 2 * inset, -mCornerRadius,
196cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi                    mEdgeShadowPaint);
197cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        }
198cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        canvas.restoreToCount(saved);
199cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        // RB
200cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        saved = canvas.save();
201cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        canvas.translate(mCardBounds.right - inset, mCardBounds.bottom - inset);
202cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        canvas.rotate(180f);
203cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
204cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        if (drawHorizontalEdges) {
205cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi            canvas.drawRect(0, edgeShadowTop,
206cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi                    mCardBounds.width() - 2 * inset, -mCornerRadius + mShadowSize,
207cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi                    mEdgeShadowPaint);
208cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        }
209cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        canvas.restoreToCount(saved);
210cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        // LB
211cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        saved = canvas.save();
212cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        canvas.translate(mCardBounds.left + inset, mCardBounds.bottom - inset);
213cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        canvas.rotate(270f);
214cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
215cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        if (drawVerticalEdges) {
216cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi            canvas.drawRect(0, edgeShadowTop,
217cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi                    mCardBounds.height() - 2 * inset, -mCornerRadius, mEdgeShadowPaint);
218cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        }
219cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        canvas.restoreToCount(saved);
220cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        // RT
221cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        saved = canvas.save();
222cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        canvas.translate(mCardBounds.right - inset, mCardBounds.top + inset);
223cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        canvas.rotate(90f);
224cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
225cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        if (drawVerticalEdges) {
226cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi            canvas.drawRect(0, edgeShadowTop,
227cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi                    mCardBounds.height() - 2 * inset, -mCornerRadius, mEdgeShadowPaint);
228cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        }
229cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        canvas.restoreToCount(saved);
230cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    }
231cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi
232cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    private void buildShadowCorners() {
233cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        RectF innerBounds = new RectF(-mCornerRadius, -mCornerRadius, mCornerRadius, mCornerRadius);
234cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        RectF outerBounds = new RectF(innerBounds);
235cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        outerBounds.inset(-mShadowSize, -mShadowSize);
236cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi
237cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        if (mCornerShadowPath == null) {
238cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi            mCornerShadowPath = new Path();
239cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        } else {
240cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi            mCornerShadowPath.reset();
241cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        }
242cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        mCornerShadowPath.setFillType(Path.FillType.EVEN_ODD);
243cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        mCornerShadowPath.moveTo(-mCornerRadius, 0);
244cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        mCornerShadowPath.rLineTo(-mShadowSize, 0);
245cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        // outer arc
246cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        mCornerShadowPath.arcTo(outerBounds, 180f, 90f, false);
247cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        // inner arc
248cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        mCornerShadowPath.arcTo(innerBounds, 270f, -90f, false);
249cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        mCornerShadowPath.close();
250cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi
251cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        float startRatio = mCornerRadius / (mCornerRadius + mShadowSize);
252cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        mCornerShadowPaint.setShader(new RadialGradient(0, 0, mCornerRadius + mShadowSize,
253cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi                new int[]{mShadowStartColor, mShadowStartColor, mShadowEndColor},
254cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi                new float[]{0f, startRatio, 1f}
255cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi                , Shader.TileMode.CLAMP));
256cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi
257cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        // we offset the content shadowSize/2 pixels up to make it more realistic.
258cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        // this is why edge shadow shader has some extra space
259cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        // When drawing bottom edge shadow, we use that extra space.
260cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        mEdgeShadowPaint.setShader(new LinearGradient(0, -mCornerRadius + mShadowSize, 0,
261cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi                -mCornerRadius - mShadowSize,
262cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi                new int[]{mShadowStartColor, mShadowStartColor, mShadowEndColor},
263cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi                new float[]{0f, .5f, 1f}, Shader.TileMode.CLAMP));
264cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    }
265cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi
266cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    private void buildComponents(Rect bounds) {
267cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        // Card is offset SHADOW_MULTIPLIER * maxShadowSize to account for the shadow shift.
268cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        // We could have different top-bottom offsets to avoid extra gap above but in that case
269cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        // center aligning Views inside the CardView would be problematic.
270cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        final float verticalOffset = mMaxShadowSize * SHADOW_MULTIPLIER;
271cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        mCardBounds.set(bounds.left + mMaxShadowSize, bounds.top + verticalOffset,
272cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi                bounds.right - mMaxShadowSize, bounds.bottom - verticalOffset);
273cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        buildShadowCorners();
274cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    }
275cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi
276cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    float getMinWidth() {
277cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        final float content = 2 *
278cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi                Math.max(mRawMaxShadowSize, mCornerRadius + mInsetShadow + mRawMaxShadowSize / 2);
279cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        return content + (mRawMaxShadowSize + mInsetShadow) * 2;
280cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    }
281cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi
282cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    float getMinHeight() {
283cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        final float content = 2 * Math.max(mRawMaxShadowSize, mCornerRadius + mInsetShadow
284cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi                        + mRawMaxShadowSize * SHADOW_MULTIPLIER / 2);
285cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi        return content + (mRawMaxShadowSize * SHADOW_MULTIPLIER + mInsetShadow) * 2;
286cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi    }
287cb5570316d55c6fe2ff717fa6b94b14d13980263Jorim Jaggi}