Android插件化换肤思路实现

Andorid的插件化换肤的思路,首先我们要弄清楚换肤的原理,从源码上下手,弄清原理,不过在看源码之前,我还是先说说换肤的思路,然后我们带着疑问去看看源码;思路如下:

换肤,换肤,换的是View对应的资源,如TextView更换字体的颜色,换的是color这个资源,我们要拿到这个资源ID;那插件化换肤的两个很重要的思路,

  1. 我们先拿到对应资源的string名称,我们的皮肤包中也同样存在这样一个同名的资源,我们使用这个string资源名称去拿皮皮肤包中的同名资源的资源ID,然后根据皮肤包的资源ID加载相应的资源;
  2. 在我们找了相应的资源ID,这个时候需要把资源加载到相应的View上面去,我们如何拿到所有的view,并且记录所需要更换的View的属性,如TextView我们可能需要更换的属性就有textColor,ImageView有src(更换显示的图片 ),关于如何拿到所有的View,我们可以使用LayoutInflater.Factory2这个接口,我们需要侵入,系统中创建View的这个过程,记录view,及其相应的View所需要更换的资源属性;(这个地方可以考虑是否有更好的实现方式);

 

先看看这个第二个思路,关于view的创建,view是如何创建view的;

上面的这个函数最终会进入下面这个函数:

在这里提一嘴,我们平时调用上面这个函数的时候,一般都会attachToRoot这个参数设置为false,为什么要设置为false,如果我们设置为true,会发现我们要创建的布局,根TAG有些属性不会生效,先看如下代码:

先获取root的LayoutParams,如果attachToRoot为false的话,那么就将这个LayoutParams设置到view上面去,否则的话,不设置LayoutParams,从而view设置的一些布局参数才会生效,view被创建出来的时候,并不会设置相应的属性,从而我们attachToRoot为true的时候,view的布局参数不会生效,再回到问题中来;

 

上面的这个函数是解析layout.xml文件的关键函数,会创建View,并为view设置相应的参数,里面核心的函数如下:

可以看到createViewFromTag函数中,首先调用的是tryCreateView这个函数,从函数的字面意义上理解,先尝试创建view,然后调用系统的onCreateView或者createView方法;

而在tryCreateView这个方法中,我们可以看到这样的一个逻辑如果mFactory2不为空,那么先使用mFactory2中的onCreateView方法创建view,而这个mFactory2是个什么呢?秘诀就在这里,先看看mFactory2的结构,如下:

这是一个接口,我们可以实现这个接口然后介入到创建view的这个一个过程中去,记录所有的view,同时拿到view所有需要换肤的属性,记录下来;

接下来我们先实现这个接口,直接上代码了:

    @Nullable

    @Override

    public View onCreateView(@Nullable View parent,
                             @NonNull String name,
                             @NonNull Context context,
                             @NonNull AttributeSet attrs) {
        //创建系统的
        View view = createSDKView(name, context, attrs);
        if (view == null) {
            view = createView(name, context, attrs);
        }
        if (view != null) {
            //将这个View中的那些属性可以替换做一个记录
            skinAttribute.look(view, attrs);
        }
        return view;
    }


    /**
     * 根据系统view的名字创建系统提供的 View实例
     * 创建系统的View的时候 需要添加View的前缀,因为一般系统的View都是只有View的名字
     *
     * @param name
     * @param context
     * @param attrs
     * @return
     */
    private View createSDKView(String name, Context context, AttributeSet attrs) {
        //TODO 如果包含点(.)的话  那么这个view标签就是jar包或者自定义View的
        if (name.indexOf('.') != -1) {
            //这个时候可以我们自己去创建
            return null;
        }
        for (String viewName : mClassPrefixList) {
            View view = createView(viewName + name, context, attrs);
            if (view != null) {
                return view;
            }
        }
        return null;
    }


    public Constructor<? extends View> findConstructor(String name, Context context) {
        Constructor<? extends View> constructor = sConstructorMap.get(name);
        if (constructor == null) {
            Class<? extends View> clazz = null;
            try {
                clazz = context.getClassLoader().loadClass(name).asSubclass(View.class);
                constructor = clazz.getConstructor(mConstructorSignature);
                constructor.setAccessible(true);
                sConstructorMap.put(name, constructor);
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
        return constructor;
    }


    /**
     * TODO 根据view的名字创建View对象
     *
     * @param name         view的名字
     * @param context      上下文
     * @param attributeSet view的构造函数的第二个函数
     * @return view 实例
     */
    public View createView(String name, Context context, AttributeSet attributeSet) {
        Constructor<? extends View> constructor = findConstructor(name, context);
        try {
            return constructor.newInstance(context, attributeSet);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

以上的其中我们在创建view的时候就是使用反射来创建对应view的实例,其中注意的是,view的名字如果带“.”符号的话,那么就是全限定名,一般就是Jar包中或者自定义的view会使用全限定名,这个时候可以直接使用反射获取view的构造方法,如果没有全限定名,那么就需要我们自己加上前缀,然后组合成全限定名,然后获取构造方法创建view的实例;

 

然后look方法中,代码如下:

 

我们需要记录view对应的属性名和对应的属性资源ID,这个资源ID取@符号后面的字符串,在转化为数字,然后创建SkinPair类记录下来;这里的思路是这样的,我们记录下来,我们需要置换资源view的属性名,和对应的资源ID,在我们需要进行更换的时候,循环这个列表,然后找到皮肤包中的资源ID替换我们宿主App的ID;

 

然后最后一个难点,关于如何拿到这个皮肤包中的资源ID呢?我们首先需要拿到皮肤包中对应的Resources对象,代码如下所示:

以上使用了反射构建了AssetManager对象;然后使用这个皮肤包中的Resources对象,我们可以找到对应的皮肤包中的同名资源ID,代码如下:

以上的代码根据宿主APP中的资源name,获取皮肤包中的资源ID;然后我们需要换肤的时候,可以根据我们之前拿到的所有的view的属性和其资源ID来更换为皮肤包中的资源ID;达到换肤的效果;代码如下:

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值