参考资料:
https://blog.csdn.net/hxl517116279/article/details/96581407(换肤原理)

https://blog.csdn.net/u011418943/article/details/106863899

https://blog.csdn.net/my_csdnboke/article/details/104168864(hook 布局优化)

https://www.jianshu.com/p/8ca35e86d476(布局优化)

https://www.cnblogs.com/Free-Thinker/p/3573391.html (apk通讯换肤)

https://www.sohu.com/a/278212830_739982 (换肤原理解析)

https://zhuanlan.zhihu.com/p/147683628 (非常全的换肤方案)

总结

换肤框架本质是要解决两个问题:找到换肤控件换肤属性绑定皮肤给控件(绑定皮肤资源控件的属性)
- 找换肤控件
第一种方法:通过hook AppcompatActivity的factory2
第二种方法:遍历android.R.id.content布局,找到所有的控件。

换肤控件包括xml里定义的和代码里动态创建的
换肤控件存在于Activity,也存在于Dialog。
  • 绑定属性
    第一种方法:根据属性来绑定View
    第二种方法:根据View来绑定属性

    属性包括系统自带控件的属性,也包括自定义控件的属性。

下面的源码是API29,网络上很多博客都是很旧的源码,所以稍微有些出入。

一、View是如何创建的

AppCompatActivity->setContentView

AppCompatDelegateImpl->setContentView

LayoutInflater->inflate->createViewFromTag
->tryCreateView

 View view;
        if (mFactory2 != null) {
            view = mFactory2.onCreateView(parent, name, context, attrs);
        } else if (mFactory != null) {
            view = mFactory.onCreateView(name, context, attrs);
        } else {
            view = null;
        }

由上面的分析可以知道view是由mFactory2或者mFactory创建的,至于是哪一个,继续往下看
AppCompatActivity->onCreate

 @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        final AppCompatDelegate delegate = getDelegate();
        delegate.installViewFactory();
        delegate.onCreate(savedInstanceState);
        super.onCreate(savedInstanceState);
    }

上面的delegate跟踪源码可知是AppCompatDelegateImpl类

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

打断点,可知上面的代码默认会执行
LayoutInflaterCompat.setFactory2(layoutInflater, this);

而最终AppCompatDelegateImpl会调用AppCompatViewInflater创建View,AppCompatViewInflater创建View有几种方式:
1)基础控件直接new AppCompatXXXView
2)createViewFromTag
一种情况是通过<view>声明的控件,通过获取class属性来获取全类名;另外是系统特殊的控件或者自定义控件。

如果标签没有.号,系统会根据下面三个前缀尝试去拼接创建View

  "android.widget.",
            "android.view.",
            "android.webkit."

如果有全类名,系统会直接去创建View.
创建View都是通过反射的方法:

private View createViewByPrefix(Context context, String name, String prefix)
            throws ClassNotFoundException, InflateException {
        Constructor<? extends View> constructor = sConstructorMap.get(name);

        try {
            if (constructor == null) {
                // Class not found in the cache, see if it's real, and try to add it
                Class<? extends View> clazz = Class.forName(
                        prefix != null ? (prefix + name) : name,
                        false,
                        context.getClassLoader()).asSubclass(View.class);

                constructor = clazz.getConstructor(sConstructorSignature);
                sConstructorMap.put(name, constructor);
            }
            constructor.setAccessible(true);
            return constructor.newInstance(mConstructorArgs);
        } catch (Exception e) {
            // We do not want to catch these, lets return null and let the actual LayoutInflater
            // try
            return null;
        }
    }

关于AppCompatXXXView
分析源码可以知道,AppCompatXXXView的各种属性的绑定,都是通过属性对应的一个Helper类去实现的。

二、自定义创建View的Factory2(Hook View的创建)

通过上面的分析可知,可以通过两种方法实现自定义的Factory2。
1)在AppCompatActivity执行onCreate之前,调用LayoutInflaterCompat.setFactory2(layoutInflater, myFactory2);设置自己的Factory。
2)复写AppCompatActivity的getDelegate方法,自定义AppCompatDelegate的实现类。
为什么要复写----让installFactory方法为空实现。

设置了Factory2,设置的最终对象是context对应的inflater,所以如果activity上创建Dialog,dialog使用activity的inflater去换肤,也会有换肤的效果。

分类: 换肤

0 条评论

发表回复

您的电子邮箱地址不会被公开。