探究 LayoutInflater setFactory
对于LayoutInflater setFactory,平时我们很少用到这个API,但是这个API我觉得还是有学习的必要的,能够很多意象不到的问题,准备围绕这方面编写一系列的文章。
本篇包含:
setFactory 相关API介绍
可能存在的问题
具体的解决方案及一些实际的用途
LayoutInflater大家肯定都不陌生,用的最多的就是其inflate()方法了,今天介绍的就是它的另外的两个方法:
setFactory
setFactory2
这两个方法的功能基本是一致的,setFactory2是在SDK>=11以后引入的,所以我们要根据SDK的版本去选择调用上述方法。
值得高兴的是,v4包下有个类LayoutInflaterCompat帮我们完成了兼容性的操作,提供的方法为:
LayoutInflaterCompat
- setFactory(LayoutInflater inflater,
LayoutInflaterFactory factory)
好了,下面我们就来写段代码看看该方法如何使用:
我们新建一个Activity,在其onCreate中调用setFactory
然后,我们运行项目,你会发现打印的log为(部分log):
MainActivity﹕ name = TextView
MainActivity﹕ layout_width , -2
MainActivity﹕ layout_height , -2
MainActivity﹕ text , @2131099670
这里针对布局文件中的一个TextView,可以看到打印了该TextView的name以及所有的attr相关信息。这些信息有什么用,我们后面的文章会介绍。
那么这个方法能干什么呢?
从字面上理解onCreateView是创建View,那么我们修改部分代码,添加如下代码:
if (name.equals("TextView"))
{
Button button = new Button(context, attrs);
return button;
}
运行后你会发现,界面上的TextView变成了Button
是不是很惊奇,但是我们已经能够确定这个方法的确能够根据布局文件中的信息去创建对应的View了。
你可以会问,谁没事干把TextView换成Button哇,就没什么靠谱的作用吗?
还真有,假设你的项目编写了一半,忽然有个需求:
需要自定义一个TextView(称为:MyTextView)来替换系统的TextView。
那么现在你就不必去打开以前的布局文件把TextView全部进行修改,直接在BaseActivity里面进行下面的操作就可以了:
if (name.equals("TextView"))
{
MyTextView view = new com.zhy.MyTextView(context,attrs);
return view;
}
对于自定义的View,你也可以通过比对name(随便设置个name都可以,不需要去完整的编写全路径了),然后直接去new出该对象。这么做有一个好处,相比系统去帮你创建,效率会高一点,因为系统有一些逻辑需要走,并且最终是通过反射的方式帮你创建View。
不过,对于setFactory的使用,可能会面临下面的问题,也不要担心,文章后面会给出解决方案。
现在开发App的时候,我们一般Activity都继承于AppCompatActivity,而在AppCompatActivity中,实际上也调用了setFactory方法。
如果你自己还调用了setFactory就可能带来一些问题,因为setFactory并不能重复调用。
打开AppCompatActivity的源码,找到onCreate,你会发现如下代码:
installViewFactory的具体实现为
这里你会发现,AppCompatActivity中也尝试去setFactory,如果我们再其之前调用了setFactory,就会打印上面的info信息,并且造成其setFactroy不会生效,其实从当前运行的Log信息中也能看出:
AppCompatDelegate﹕ The Activity's LayoutInflater
already has a Factory installed
so we can not install AppCompat's
这么来看,如果我们使用AppCompatActivity,我们是不应该按照我们前面的方式,直接设置setFactory,否则我们可能会带来一些影响。
我们会带来什么影响呢?
其实AppCompatActivity的setFactory也是想根据name去生成一些类,大家还记得,更新v7包的时候,忽然我们的TextView就支持了一些属性,比如tint属性,以前是不支持的,怎么能够做到使用v7包,然后就能支持且向下兼容的呢?
哈哈,原理就在这里,看下面的代码片段:
可以看到系统其实是利用setFactory,瞒着我们把TextView等类早就换成AppCompatTextView等类了。
如果你使用AppCompatActivty你可以通过打印textview.getClass()来验证。
ok,看到这里,我们上面的一个问题也就知道答案了:
我们按照前面的方式直接设置setFactory,会带来什么影响呢?
会造成没有办法使用一些新的特性,比如tint等。
我们具体看下appcompat(下面统将v7包中的使用称为为appcompat)中设置的factory对应的onCreateView的全部代码:
可以看到其最终是调用:createView方法完成view的创建,并且值得高兴的是该方法是public的。
也就是说,我们可以自己设置factory中,依然可以保证appcompat中创建View的代码的执行。
这样就保证了既执行了我们希望的代码,有保证了appcompat中相关代码的执行。
下面看一个我们常见的场景。
很多时候我们为了app更加个性,可能会整体采用外部引入的字体。
很多开发者的实现是这样的,在BaseActivity的onCreate中去从跟布局去递归遍历所有的View,类似的代码如下:
这种方式虽然方便,但是肯定会带来一定性能问题。
说到这,我估计你心理已经有全新的解决方案了,没错,那就是利用setFactory,相关代码如下(在BaseActivity中):
仅仅几行代码就能完成字体的全局统一设置了,而且非常高效。
我在布局文件中放了3个控件,看下效果图:
我们这里是恰好appcompat中对于TextView、Button、EditText都进行了AppCompat化。
假设还有继承自TextView的自定义View如何处理呢?
很简单,调用LayoutInflater的createView方法就可以了,或者数量并不多,可以自己手动new也没有问题,参考代码如下:
ok,具体的细节自己再琢磨琢磨。
setFactory还非常适合一个场景,就是换肤,换肤需要解决的核心问题有两个:
外部资源的加载
定位到需要换肤的View
第一个资源加载的问题可以通过构造AssetManager,反射调用其addAssetPath就可以完成。
第二个问题,就可以利用在onCreateView中,根据view的属性来定位,例如你可以让需要换肤的view添加一个自定义的属性skin_enabled=true(最开始有打印属性),并且利用一些手段拿到构造到的view,就能在View构造阶段定位的需要换肤的View。
关于这种方式换肤,会在后面的文章进行介绍。
那么本文就是setFactory相关系列的第一篇了。通过本文的阅读,我相信你或多或少收获了一些东西:
appcompat如何对一些View增加特性且向下兼容;
我们如何提升自定义View的构建速度(自行new);
如何使用自定义View替换系统中的View;
如何高效的引入外部字体的为app中统一所有字体。
终于开通赞赏了~~
--欢迎长按或者扫码关注--
-本公众号支持投稿,直接投递md文件或者链接至我邮箱-