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