最近项目中来了新的需求,一大堆卡片式布局,还有不同的阴影颜色,甚至不同的状态下颜色还不一样,UI 给的切图各种错位,而 Google 的 CardView 是无法设置阴影颜色的,我能怎么办,我也很绝望啊_(:з」∠)_
没办法,百度了一堆都没有找到解决方案,最后借鉴了各位大神的思路,才有了这篇文章。
怎么实现?当然是参照 Google 的 CardView 啦,毕竟是亲生的,各种实现优化都是非常棒的,然后就是在 CardView 的基础上,给他添加上设置阴影颜色的功能,很简单的是吧。
<color name="cardview_shadow_end_color">#03000000color>
<color name="cardview_shadow_start_color">#37000000color>
public void setCardElevation(float elevation) {
IMPL.setElevation(mCardViewDelegate, elevation);
}
// CardViewBaseImpl
@Override
public void setElevation(CardViewDelegate cardView, float elevation) {
getShadowBackground(cardView).setShadowSize(elevation);
}
// CardViewApi21Impl
@Override
public void setElevation(CardViewDelegate cardView, float elevation) {
cardView.getCardView().setElevation(elevation);
}
<attr name="cardShadowColorStart" format="color" />
<attr name="cardShadowColorEnd" format="color" />
ColorStateList shadowColorStart = a.getColorStateList(R.styleable.CardView_cardShadowColorStart);
ColorStateList shaodwColorEnd = a.getColorStateList(R.styleable.CardView_cardShadowColorEnd);
void initialize(CardViewDelegate cardView, Context context, ColorStateList backgroundColor,
float radius, float elevation, float maxElevation, ColorStateList shadowColorStart, ColorStateList shadowColorEnd);
// 阴影颜色默认是 int 类型的颜色值,要修改成 ColorStateList
private final ColorStateList mShadowStartColor;
private final ColorStateList mShadowEndColor;
RoundRectDrawableWithShadow(Resources resources, ColorStateList backgroundColor, float radius,
float shadowSize, float maxShadowSize, ColorStateList shadowColorStart, ColorStateList shadowColorEnd) {
// 如果没有设置阴影颜色,使用默认颜色
if (shadowColorStart == null) {
mShadowStartColor = ColorStateList.valueOf(resources.getColor(R.color.cardview_shadow_start_color));
} else {
mShadowStartColor = shadowColorStart;
}
if (shadowColorEnd == null) {
mShadowEndColor = ColorStateList.valueOf(resources.getColor(R.color.cardview_shadow_end_color));
} else {
mShadowEndColor = shadowColorEnd;
}
mInsetShadow = resources.getDimensionPixelSize(R.dimen.cardview_compat_inset_shadow);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
setBackground(backgroundColor);
mCornerShadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
mCornerShadowPaint.setStyle(Paint.Style.FILL);
mCornerRadius = (int) (radius + .5f);
mCardBounds = new RectF();
mEdgeShadowPaint = new Paint(mCornerShadowPaint);
mEdgeShadowPaint.setAntiAlias(false);
setShadowSize(shadowSize, maxShadowSize);
}
private void buildShadowCorners() {
RectF innerBounds = new RectF(-mCornerRadius, -mCornerRadius, mCornerRadius, mCornerRadius);
RectF outerBounds = new RectF(innerBounds);
outerBounds.inset(-mShadowSize, -mShadowSize);
if (mCornerShadowPath == null) {
mCornerShadowPath = new Path();
} else {
mCornerShadowPath.reset();
}
mCornerShadowPath.setFillType(Path.FillType.EVEN_ODD);
mCornerShadowPath.moveTo(-mCornerRadius, 0);
mCornerShadowPath.rLineTo(-mShadowSize, 0);
// outer arc
mCornerShadowPath.arcTo(outerBounds, 180f, 90f, false);
// inner arc
mCornerShadowPath.arcTo(innerBounds, 270f, -90f, false);
mCornerShadowPath.close();
float startRatio = mCornerRadius / (mCornerRadius + mShadowSize);
// 获取当前状态下的阴影颜色
int starColor = mShadowStartColor.getColorForState(getState(), mShadowStartColor.getDefaultColor());
int endColor = mShadowEndColor.getColorForState(getState(), mShadowEndColor.getDefaultColor());
// 设置阴影颜色
mCornerShadowPaint.setShader(new RadialGradient(0, 0, mCornerRadius + mShadowSize,
new int[]{starColor, starColor, endColor},
new float[]{0f, startRatio, 1f},
Shader.TileMode.CLAMP));
// we offset the content shadowSize/2 pixels up to make it more realistic.
// this is why edge shadow shader has some extra space
// When drawing bottom edge shadow, we use that extra space.
mEdgeShadowPaint.setShader(new LinearGradient(0, -mCornerRadius + mShadowSize, 0,
-mCornerRadius - mShadowSize,
new int[]{starColor, starColor, endColor},
new float[]{0f, .5f, 1f}, Shader.TileMode.CLAMP));
mEdgeShadowPaint.setAntiAlias(false);
}
<cn.wj.android.colorcardview.CardView
android:layout_width="200dp"
android:layout_height="50dp"
app:cardPreventCornerOverlap="true"
app:cardBackgroundColor="#069ff1"
app:cardShadowColorStart="#2dfd0000"
app:cardShadowColorEnd="#03fd0000"
app:cardUseCompatPadding="true"
app:cardElevation="8dp" />
@RequiresApi(21)
class CardViewApi21Impl extends CardViewApi17Impl {
// 标记 - 是否使用低版本实现
private boolean useLower = false;
@Override
public void initialize(CardViewDelegate cardView, Context context,
ColorStateList backgroundColor, float radius, float elevation, float maxElevation,
ColorStateList shadowColorStart, ColorStateList shadowColorEnd) {
// 没有自定义阴影颜色,不使用低版本实现
if (shadowColorStart == null && shadowColorEnd == null) {
useLower = false;
final RoundRectDrawable background = new RoundRectDrawable(backgroundColor, radius);
cardView.setCardBackground(background);
View view = cardView.getCardView();
view.setClipToOutline(true);
view.setElevation(elevation);
setMaxElevation(cardView, maxElevation);
} else {
// 配置了自定义颜色,使用低版本实现
useLower = true;
super.initialize(cardView, context, backgroundColor, radius, elevation, maxElevation, shadowColorStart, shadowColorEnd);
}
}
@Override
public void setRadius(CardViewDelegate cardView, float radius) {
if (useLower) {
super.setRadius(cardView, radius);
} else {
getCardBackground(cardView).setRadius(radius);
}
}
@Override
public void setMaxElevation(CardViewDelegate cardView, float maxElevation) {
if (useLower) {
super.setMaxElevation(cardView, maxElevation);
} else {
getCardBackground(cardView).setPadding(maxElevation,
cardView.getUseCompatPadding(), cardView.getPreventCornerOverlap());
updatePadding(cardView);
}
}
@Override
public float getMaxElevation(CardViewDelegate cardView) {
if (useLower) {
return super.getMaxElevation(cardView);
} else {
return getCardBackground(cardView).getPadding();
}
}
@Override
public float getMinWidth(CardViewDelegate cardView) {
if (useLower) {
return super.getMinWidth(cardView);
} else {
return getRadius(cardView) * 2;
}
}
@Override
public float getMinHeight(CardViewDelegate cardView) {
if (useLower) {
return super.getMinHeight(cardView);
} else {
return getRadius(cardView) * 2;
}
}
@Override
public float getRadius(CardViewDelegate cardView) {
if (useLower) {
return super.getRadius(cardView);
} else {
return getCardBackground(cardView).getRadius();
}
}
@Override
public void setElevation(CardViewDelegate cardView, float elevation) {
if (useLower) {
super.setElevation(cardView, elevation);
} else {
cardView.getCardView().setElevation(elevation);
}
}
@Override
public float getElevation(CardViewDelegate cardView) {
if (useLower) {
return super.getElevation(cardView);
} else {
return cardView.getCardView().getElevation();
}
}
@Override
public void updatePadding(CardViewDelegate cardView) {
if (useLower) {
super.updatePadding(cardView);
} else {
if (!cardView.getUseCompatPadding()) {
cardView.setShadowPadding(0, 0, 0, 0);
return;
}
float elevation = getMaxElevation(cardView);
final float radius = getRadius(cardView);
int hPadding = (int) Math.ceil(RoundRectDrawableWithShadow
.calculateHorizontalPadding(elevation, radius, cardView.getPreventCornerOverlap()));
int vPadding = (int) Math.ceil(RoundRectDrawableWithShadow
.calculateVerticalPadding(elevation, radius, cardView.getPreventCornerOverlap()));
cardView.setShadowPadding(hPadding, vPadding, hPadding, vPadding);
}
}
@Override
public void onCompatPaddingChanged(CardViewDelegate cardView) {
if (useLower) {
super.onCompatPaddingChanged(cardView);
} else {
setMaxElevation(cardView, getMaxElevation(cardView));
}
}
@Override
public void onPreventCornerOverlapChanged(CardViewDelegate cardView) {
if (useLower) {
super.onPreventCornerOverlapChanged(cardView);
} else {
setMaxElevation(cardView, getMaxElevation(cardView));
}
}
@Override
public void setBackgroundColor(CardViewDelegate cardView, @Nullable ColorStateList color) {
if (useLower) {
super.setBackgroundColor(cardView, color);
} else {
getCardBackground(cardView).setColor(color);
}
}
@Override
public ColorStateList getBackgroundColor(CardViewDelegate cardView) {
if (useLower) {
return super.getBackgroundColor(cardView);
} else {
return getCardBackground(cardView).getColor();
}
}
private RoundRectDrawable getCardBackground(CardViewDelegate cardView) {
return ((RoundRectDrawable) cardView.getCardBackground());
}
}
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
tools:context=".MainActivity">
<cn.wj.android.colorcardview.CardView
android:id="@+id/cv1"
android:layout_width="200dp"
android:layout_height="100dp"
app:cardBackgroundColor="@color/app_selector_card"
app:cardElevation="8dp"
app:cardPreventCornerOverlap="true"
app:cardShadowColorEnd="@color/app_selector_shadow_end"
app:cardShadowColorStart="@color/app_selector_shadow_start"
app:cardUseCompatPadding="true" />
<android.support.v7.widget.CardView
android:id="@+id/cv2"
android:layout_width="200dp"
android:layout_height="100dp"
app:cardBackgroundColor="@color/app_selector_card"
app:cardElevation="8dp"
app:cardPreventCornerOverlap="true"
app:cardUseCompatPadding="true" />
LinearLayout>
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
cv1.setOnClickListener { onClick(it) }
cv2.setOnClickListener { onClick(it) }
}
fun onClick(v: View) {
v.isSelected = !v.isSelected
}
}