美文网首页Android专题
利用反射设置CardView阴影颜色

利用反射设置CardView阴影颜色

作者: luowenbin | 来源:发表于2019-02-16 23:54 被阅读26次

    我们知道默认的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启动时调用。

    相关文章

      网友评论

        本文标题:利用反射设置CardView阴影颜色

        本文链接:https://www.haomeiwen.com/subject/rwxweqtx.html