LayoutInflater 只负责实例化各个 View 类,不负责调用 measure/layout/draw 等方法
经过 inflate() 后,各个 View 的实例都已实例化完成,并且层级关系也已确定。但其 measure/layout/draw 并没有调用。
因此,onMeasure() 在 onFinishInflate() 后调用。
属性
sConstructorMap:各个 View 的 Constructor 集合。key 值为各个 View 的 name(自定义的为全名,系统的为 xml 中写的名,如 TextView等)
mFilter:过滤器。根据 Class 判断当前 View 是否要过滤。如果在 xml 中使用了要过滤的 View, 则会抛出异常
mFilterMap:存储过滤结果。key 值为各个 View 的 name (自定义的为全名,系统的为 xml 中写的名,如 TextView等)
方法
inflate
负责解析出根结点,并根据根结点不同调用下面方法
当根结点为 merget 时,调用
rInflate()
-
当根结点为普通 View 时:
- 首先调用
createViewFromTag()
创建根节点自身实例,该方法中并不创建其子 view。 - 调用
rInflateChildren()
创建所有子 View,并在创建过程中将子 View 添加到他们各自的父 View 中。
- 首先调用
rInflateChildren
其主要用于递归创建子 view,同时将子 view 添加到其父布局中。
该方法直接调用 rInflate()。
createViewFromTag
创建 view 自身实例,不涉及其子 view。
该方法中会按 mFactory2,mFactory,mPrivateFactory 优先级创建 View 实例。
if (mFactory2 != null) {
view = mFactory2.onCreateView(parent, name, context, attrs);
} else if (mFactory != null) {
view = mFactory.onCreateView(name, context, attrs);
} else {
view = null;
}
if (view == null && mPrivateFactory != null) {
view = mPrivateFactory.onCreateView(parent, name, context, attrs);
}
如果工厂类中有返回值,则本方法结束,将将工厂类的返回值直接返回。
-
如果三个工厂类都没有返回值,就会调用 onCreateView 或 createView —— 系统 view 调用前者,自定义 view 调用后者。
但 onCreateView 对 name 添加
android.view.
包前缀后,又调用到 createView。因此,系统 view 走到 createView 方法时,prefix 不为空,其值为 android.view.;但自定义 view 走到 createView() 时,prefix 为空。
createView
该方法中,经过 mFilter 过滤后,得到对应 view 的 constructor ,然后通过反射得到 view 的实例。
也就是说该方法会得到 view 的实例或者抛出异常(mFilter 没有过滤通过)
rInflate
遍历 xml 文件中所有的节点,并创建这些节点对应的 view 实例,同时将每一个实例添加到其父 view 中。
-
该方法有两个入口:
根结点为 merge 标签时,直接该方法。此时 finishInflate 为 false,且此时 parent 为外界传入的 root。
通过 rInflateChildren 调用该方法,此时 finishInflate 为 true,且参数 parent 的值为 createViewFromTag() 的返回值。
-
该方法内部会通过 while 循环遍历所有的 view 结点,并通过 parser.getName() 获取当前结点的名字。拿到名字后,会根据名字的不同进行不同的操作,如:
普通 view,首先调用 createViewFromTag() 生成该 view 实例,再调用 rInflateChildren 创建子 view。最后通过 ViewGroup#addView 将子 View 添加到当前 View 中。
merge 标签:直接抛异常。因为布局的外层 view 已经解析 ( 在 inflate 方法中完成),所以此时出现 merge 标签说明 merge 并不是最外层。但 merge 只能做为根标签。
-
tag 标签:解析 tag 标签中配置的的 id 与 value,并设置给 parent 。
<Button android:id="@+id/btn" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="选择图片"> <tag android:id="@id/id_tag" android:value="test" /> </Button>
requestFocus:将当前 parent 设置为焦点 view。
include 标签:直接调用 parseInclude 方法
- 该方法最后会调用 view.onFinishInflate()。因此,如果 view 是一个 ViewGroup,该方法回调时表示其子 view 已经添加完毕;如果是一个普通的 View,表示该 View 的实例已创建结束,并且已经 add 到其父 View 中。
parseInclude 方法:
首先读取 layout 属性的值,并尝试转成 layoutId(即读取通过 @layout/layoutId 形式配置的 layout)。如果失败,会尝试读取 theme 中配置的 layout 属性的值(即通过 ?attr/layout 配置的值)。
如果上述两步结束后依旧没有 layout 的值,则抛异常。
-
如果有 layout 的值,则跟普通的通过 inflater() 解析一个 layout 逻辑一样:首先调用 createViewFromTag 得到布局的根结点实例,再调用 rInflateChildren() 填充所有子 view。
但在创建根结点实例后,会读取布局 id 与 visibility 属性:
visibility 控制根结点的显示与隐藏
会调用 View#setId() 方法,将 id 的值设置为根结点的 id。
if (id != View.NO_ID) { view.setId(id); }
blink
在解析过程中,有一个 <blink>
标签,其主要作用是让其子 View 不断闪动:出现与隐藏交替进行。
其对应的实例是 LayoutInflater 中的 BlinkLayout 内部类。
<blink
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="this is test" />
</blink>