我们知道默认的CardView是不能设置阴影颜色的,许多时候却又有这种需求,然后百度上解决方案很少,基本就是把官方的CardView的源码改了再拷进工程。
看看效果:
反射修改的缺点和上面改源码的缺点一样,都是没有Android5.0以上的View自带的阴影绘制那么平滑好看,且有半径限制,如图,TextView 是Api21以上自带阴影,CardView是反射修改的阴影。

先看看CardView源码:

可以发现,
和CardView属性有关(setCardBackgroundColor,setCardElevation等)的基本都在静态常量IMPL上了,
CardView里也没有绘制阴影的相关方法,所以阴影绘制很可能在IMPL里,
IMPL的类型CardViewImpl本身是个接口,所以要找到它的子类,看他在哪里被赋值。

可以看到,
IMPL根据android版本的不同被赋于了不同的 子类,从名字也可以看出,其实这个就是为了实现不同Android版本的兼容和优化。
然后需要进入
CardViewApi21Impl.javaCardViewApi17Impl.javaCardViewBaseImpl.java三个地方分别看看他们的区别,
可以看出,
Android 5.0(
CardViewApi21Impl.java)以上是调用View里面的阴影绘制的。Android 4.2(
CardViewBaseImpl.java)以下是利用Drawable来绘制阴影的Android 4.2-5.0(
CardViewApi17Impl.java)只在CardViewBaseImpl.java的基础上替换了画圆角矩形的方法。
Android 5.0(CardViewApi21Impl.java)的 View里面的阴影绘制过于复杂(可能调用native方法),用反射也不一定能完成,就没有深入了,
所以这里考虑用修改Drawable的属性来实现修改阴影颜色。
带阴影的圆角矩形 的Drawable 它已经写好了,

进去RoundRectDrawableWithShadow类可以看到阴影的起始颜色和结束颜色两个属性,但是是private的,所以我们要利用反射修改他的属性值就行了。
修改完mShadowStartColor,mShadowEndColor发现:
什么都没发生??
当然,因为IPML仍然是Api21以上的实现CardViewApi21Impl,用的是View的阴影绘制
所以需要先把它改成Api17的实现CardViewApi17Impl,才是用Drawable实现阴影。
所以要在之前加一步替换IPML和调用初始化IPML的方法。
import android.os.Build;
import android.support.v7.widget.CardView;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class CardUtils {
private static boolean inited=false;
//设置obj的成员变量
private static void setMember(Object obj, String memberName, Object value) {
try {
if (obj instanceof Class) {
//静态变量
Field declaredField = ((Class) obj).getDeclaredField(memberName);
declaredField.setAccessible(true);
declaredField.set(null, value);
} else {
Field declaredField = obj.getClass().getDeclaredField(memberName);
declaredField.setAccessible(true);
declaredField.set(obj, value);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void init() {
if (Build.VERSION.SDK_INT >= 21&&!inited) {
inited=true;
try {
//new 一个CardViewApi17Impl
Constructor<?> constructor = Class.forName("android.support.v7.widget.CardViewApi17Impl").getDeclaredConstructor();
constructor.setAccessible(true);
Object impl = constructor.newInstance();
//用新的代替掉原来的
setMember(CardView.class, "IMPL", impl);
//执行方法IMPL.initStatic()
Method initStatic = impl.getClass().getDeclaredMethod("initStatic");
initStatic.setAccessible(true);
initStatic.invoke(impl);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void setCardShadowColor(CardView cardView, int startColor, int endColor) {
try {
//获取背景
Object background = cardView.getBackground();
//设置颜色
setMember(background, "mShadowStartColor", startColor);
setMember(background, "mShadowEndColor", endColor);
} catch (Exception e) {
e.printStackTrace();
}
}
}
CardUtils.init()
最好是在Application启动时调用。