参考资料:
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 条评论