Android专题

利用反射设置CardView阴影颜色

2019-02-16  本文已影响26人  luowenbin

我们知道默认的CardView是不能设置阴影颜色的,许多时候却又有这种需求,然后百度上解决方案很少,基本就是把官方的CardView的源码改了再拷进工程。

看看效果:
反射修改的缺点和上面改源码的缺点一样,都是没有Android5.0以上的View自带的阴影绘制那么平滑好看,且有半径限制,如图,TextViewApi21以上自带阴影,CardView是反射修改的阴影。

先看看CardView源码:

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


可以看到,IMPL根据android版本的不同被赋于了不同的 子类,
从名字也可以看出,其实这个就是为了实现不同Android版本的兼容和优化。
然后需要进入
CardViewApi21Impl.java
CardViewApi17Impl.java
CardViewBaseImpl.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启动时调用。

上一篇下一篇

猜你喜欢

热点阅读