网易换肤第二篇:本地换肤实现!

在这里插入图片描述
完整脑图:https://note.youdao.com/s/V2csJmYS

Demo源码:点击下载

技术分析


我们在换肤的第一篇介绍了换肤的核心思想。就是在setContentView()之前调用setFactory2()

第一篇的Demo利用的是AOP切面方法registerActivityLifecycleCallbacks(xxx)回调在setContentView()之前,从而在registerActivityLifecycleCallbacks的onActivityCreated()方法中设置Factory。如此就能拦截到控件的属性,根据拦截到的控件的属性,重新赋值控件的textColor、background等属性,从而实现换肤的。

本Demo的实现,主要基于以下两个狙击点。

1、super.onCreate(savedInstanceState)方法
2、Activity实现了Factory接口

前面说过,只要在setContentView()之前setFactory2()就行。super.onCreate(savedInstanceState)方法就是在setContentView()方法之前执行的。

一直跟踪super.onCreate(savedInstanceState)方法,最终会发现setFactory的逻辑,如下:

AppCompatDelegateImpl.java(1008)

public void installViewFactory() {
    LayoutInflater layoutInflater = LayoutInflater.from(this.mContext);
    if (layoutInflater.getFactory() == null) {
        LayoutInflaterCompat.setFactory2(layoutInflater, this);
    } else if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImpl)) {
        Log.i("AppCompatDelegate", "The Activity's LayoutInflater already has a Factory installed so we can not install AppCompat's");
    }
}

它这里传了this,可以预见AppCompatDelegateImpl是实现了Factory接口的,最后会通过AppCompatDelegateImpl自身的onCreateView()方法创建的View。

onCreateView()中如何创建的View的,下面再看源码,先知道是通过AppCompatViewInflater来做控件的具体初始化的。

第一个狙击点可以抽出下图内容:

在这里插入图片描述
细心地同学肯定注意到了AppCompatDelegateImpl的installViewFactory()方法中,只有当layoutInflater.getFactory() == null的时候,才会去setFactory。

也就是说我在super.onCreate(savedInstanceState)之前,先给它setFactory就能走自己Factory的onCreateView()回调。

换肤第一篇中我们是自己去实现Factory2接口,在本例中,就用到了我们第二个狙击点。

Activity实现了Factory接口!!!

在这里插入图片描述

也就是说,只要我们在super.onCreate(savedInstanceState)之前,setFactory的时候,传this,就能走ActivityonCreateView()回调,来对控件属性做操作。

用归纳法,见下图:

在这里插入图片描述
最后,也就剩下Activity的onCreateView()中的回调怎么实现了。

直接模拟super.onCreate(savedInstanceState)中AppCompatViewInflater类中的实现就好了。

在这里插入图片描述
参考代码:

/**
 * 自定义控件加载器(可以考虑该类不被继承)
 */
public final class CustomAppCompatViewInflater extends AppCompatViewInflater {

    private String name; // 控件名
    private Context context; // 上下文
    private AttributeSet attrs; // 某控件对应所有属性

    public CustomAppCompatViewInflater(@NonNull Context context) {
        this.context = context;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setAttrs(AttributeSet attrs) {
        this.attrs = attrs;
    }

    /**
     * @return 自动匹配控件名,并初始化控件对象
     */
    public View autoMatch() {
        View view = null;
        switch (name) {
            case "LinearLayout":
                // view = super.createTextView(context, attrs); // 源码写法
                view = new SkinnableLinearLayout(context, attrs);
                this.verifyNotNull(view, name);
                break;
            case "RelativeLayout":
                view = new SkinnableRelativeLayout(context, attrs);
                this.verifyNotNull(view, name);
                break;
            case "TextView":
                view = new SkinnableTextView(context, attrs);
                this.verifyNotNull(view, name);
                break;
            case "ImageView":
                view = new SkinnableImageView(context, attrs);
                this.verifyNotNull(view, name);
                break;
            case "Button":
                view = new SkinnableButton(context, attrs);
                this.verifyNotNull(view, name);
                break;
        }

        return view;
    }

    /**
     * 校验控件不为空(源码方法,由于private修饰,只能复制过来了。为了代码健壮,可有可无)
     *
     * @param view 被校验控件,如:AppCompatTextView extends TextView(v7兼容包,兼容是重点!!!)
     * @param name 控件名,如:"ImageView"
     */
    private void verifyNotNull(View view, String name) {
        if (view == null) {
            throw new IllegalStateException(this.getClass().getName() + " asked to inflate view for <" + name + ">, but returned null");
        }
    }
}

详细实现就参考Demo吧,思路其实很简单,只是会有对setFactory这块逻辑的流程不了解的。建议跟踪着点几遍源码。


About

网易换肤第一篇:换肤技术解密!
网易换肤第二篇:本地换肤实现!
网易换肤第三篇:动态换肤实现!

架构师系列文章一览

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IT小瓯

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值