前言
LayoutInflater
对于做过Android UI开发的人来说再熟悉不过了。本文结合其源码进行分析。系作者原创,转载需注明出处。
LayoutInflater用法
LayoutInflater主要用于将layout xml实例化成对应的view,通过:
1 | Context#getSystemService(Context.LAYOUT_INFLATER_SERVICE) |
可以获取到绑定了context的LayoutInflater实例,我们用LayoutInflater.from(context)
实际上也是通过getSystemService
获取到的LayoutInflater实例。
LayoutInflater提供了限制inflate哪些View的接口:
1 | /** |
onLoadClass
返回true表示allowed to be inflated,返回false的话该view在inflate的时候会抛出InflateException。
可以看到inflate方法是通过反射创建view实例的,同时会使用一个map缓存constructor。mFilterMap也会缓存每个name对应的view是否允许inflate。然后遇到viewstub的话会使用cloneInContext
创建一个相同context的LayoutInflater传入给viewstub实例。
1 | /** |
同时,inflate的时候我们可以通过给LayoutInflater
设置自己的factory来hook inflate过程,从而改变xml中的view对象或者其属性。例如,我们可以在遇到name为”EditText”的节点的时候new 一个Button实例返回,动态的替换UI组件。
1 | public interface Factory { |
在support v4包下有个LayoutInflaterCompat
类替我们封装好了setFactory2方法,在LayoutInflater直接调用的基础上做了一些旧版本兼容:
1 | // from LayoutInflaterCompat |
在support v7包下的AppCompatActivity
实际上也利用了LayoutInflaterCompat
类。
1 | protected void onCreate(@Nullable Bundle savedInstanceState) { |
在AppCompatActivity
onCreate的时候会调用AppCompatDelegateImpl
的installViewFactory
方法,给当前context的LayoutInflater赋上一个Factory2,也就是自身。
1 | public void installViewFactory() { |
而AppCompatActivity
自身的onCreateView又委托给了AppCompatViewInflater
的createView,将我们的TextView等组件都替换成了对应的AppCompatTextView等组件。
1 |
|
inflate方法解析
inflate我们常用的主要有2个版本:
1 | /** |
可知当使用两个参数的inflate的时候,root为null,attachToRoot就为false,root不为null,attachToRoot就为true,从attachToRoot命名也能知道这是判断是否将inflate得到的view添加为root的子view的依据之一。再来看inflate具体是怎样parse xml的:
1 |
|
首先 <merge>
标签必须要有一个不为null的root和值为true的attachToRoot,否则抛出异常:InflateException。如果满足条件则调用rInflate
进行渲染:
1 | /** |
可以看到rInflate会递归地对<merge>
标签下的节点实例化成view,并根据attrs生成LayoutParams,添加到root上。这也是为什么<merge>
能降低view层级的原因,因为下面的标签是直接add到root上的,不必多一层layout。同时需要注意到<merge>
解析的时候调rInflate
传入的finishInflate
为false,在rInflate
中又调用了rInflateChildren, rInflateChildren又调了一次rInflate,只不过这次传入的finishInflate
为true。这个设计还是很巧妙的,当<merge>
下的节点全部inflate完成之后,每个view都回调了onFinishInflate
,不过由于root还没inflate完成,所以root的onFinishInflate
还没调用。同时也可以注意到,当parse到<requestFocus>
标签时,会调用父容器view的restoreDefaultFocus()
,进而调用requestFocus()
获取焦点。而且不一样的是,<include>
标签必须不在最外层,<merge>
标签必须在最外层。
接着往下看,假如不是<merge>
标签,就会给最外层标签生成对应的viewgroup实例temp,如果root不为null的话,会利用root生成一个layoutparams,然后看看是否attachToRoot, 如果是,那就root.addView(temp, params);
;否,那就temp.setLayoutParams(params);
。假如root为null的话,可以知道生成的temp没有设置任何layoutparams,所以这时候我们最外层的xml属性部分会失效,比如宽高,gravity。
至此,LayoutInflater的源码差不多就分析到这里了。
what about ViewStub
既然提到了ViewStub
那么就顺便也来看看ViewStub
的工作原理:
1 | /** |
可以看到ViewStub
首先拿到了自己在parent view里的index,然后remove自己,将inflate出来的view在原index处插入,layoutparams都是根据view的属性生成的,和viewStub无关。
完结,撒花🎉🎉🎉