Android 布局填充器

LayoutInflater 的作用是把布局 XML 文件解析成相应的 View 对象。由于性能原因,LayoutInflater 只能处理编译过的 xml 文件,不能处理原始的 xml 文件。

通过 Context#getSystemService(Context.LAYOUT_INFLATER_SERVICE) 系统服务获取 LayoutInflater 对象。

1
2
3
4
5
/**
* Instantiates a layout XML file into its corresponding View objects.
**/
@SystemService(Context.LAYOUT_INFLATER_SERVICE)
public abstract class LayoutInflater {}

主要方法

  • public static LayoutInflater from(context)
    获取 LayoutInflater 实例化对象。
  • public View inflate(int resource, ViewGroup root)
    把指定的布局资源填充成 View 对象。如果 root 不为空则把填充的 View 添加到 root 上,返回 root;否则返回填充的 View。
  • public View inflate(int resource, ViewGroup root, boolean attachToRoot)
    把指定的布局资源填充成 View。如果 root != null && attachToRoot == true,则把填充的 View 添加到 root 上,返回 root;否则直接返回填充的 View。
  • public void setFactory(Factory factory);
    设置当前 LayoutInflater 的自定义实例化工厂。通过设置自定义工厂,可以在系统实例化 View 的时候进行一些拦截操作,比如可以把本来的 TextView 拦截成 Button、给 TextView 统一指定字体等。
  • public void setFactory2(Factory2 factory)
    Factory2 多了对实例化 View 的时候 Parent 的支持。
  • public void setFilter(Filter filter)
    给当前 LayoutInflater 设置过滤器,如果要被填充的 View 不被这个过滤器允许,则会抛出 InflateException。

View 填充流程

大体流程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// 把xml布局资源或者通过资源解析器实例化View
inflate{
if(merge标签){
// 递归实例化根节点的子View,返回父 View
rInflate();
}else{
// 实例化根节点的View
createViewFromTag();
// 递归实例化跟节点的子View
rInflateChildren()
// 这个需要注意
if(父View是空或者不把填充的View添加到父View){
返回根节点View
}else{
返回父View
}
}
}

// 通过View的名称实例化View
createViewFromTag{
if(mFactory2){
mFactory2.onCreateView()
}else if(mFactory){
mFactory.onCreateView()
}

if(view && mPrivateFactory){
mPrivateFactory.onCreateView()
}

if(view){
if(系统View){
//添加前缀"android.view.",调用createView()
onCreateView()
}else{//自定义View
//通过constructor.newInstance() 实例化 View
createView()
}
}
}

// 递归实例化Parent的子View
rInflate{
// 解析请求焦点
parseRequestFocus()
// 解析include标签
parseInclude()
// 通过View的名称实例化View
createViewFromTag()
rInflate
}

源码分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
//LayoutInflater.java

private static final String TAG_MERGE = "merge";
private static final String TAG_INCLUDE = "include";
private static final String TAG_TAG = "tag";

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
final XmlResourceParser parser = res.getLayout(resource);
return inflate(parser, root, attachToRoot);
}

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
//返回指定的 root View 或 xml 的跟节点 View
View result = root;
//获取标签名称,如 LinearLayout
final String name = parser.getName();
//处理 merge 标签
if (TAG_MERGE.equals(name)) {
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
//递归实例化 xml 中所有的View
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// Temp is the root view that was found in the xml
final View temp = createViewFromTag(root, name, inflaterContext, attrs);

ViewGroup.LayoutParams params = null;
//设置布局参数
if (root != null) {
// Create layout params that match root, if supplied
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
temp.setLayoutParams(params);
}
}
// 实例化根节点 View 下面的所有子 View
// Inflate all children under temp against its context.
rInflateChildren(parser, temp, attrs, true);

// We are supposed to attach all the views we found (int temp)
// to root. Do that now.
if (root != null && attachToRoot) {
root.addView(temp, params);
}

// Decide whether to return the root that was passed in or the
// top view found in xml.
if (root == null || !attachToRoot) {
result = temp;
}
}

return result;
}

/**
* 递归处理标签下的子标签
* Recursive method used to descend down the xml hierarchy and instantiate
* views, instantiate their children, and then call onFinishInflate().
*/
void rInflate(XmlPullParser parser...) {
while (((type = parser.next()) != XmlPullParser.END_TAGZ...) {
final String name = parser.getName();
if (TAG_REQUEST_FOCUS.equals(name)) {//处理 requestFocus 标签
consumeChildElements(parser);
} else if (TAG_TAG.equals(name)) {
//处理 tag 标签,设置 view.setTag(key, value);
parseViewTag(parser, parent, attrs);
} else if (TAG_INCLUDE.equals(name)) {
parseInclude(parser, context, parent, attrs);//处理 include 标签
} else if (TAG_MERGE.equals(name)) {
throw new InflateException("<merge /> must be the root element");
} else {
final View view = createViewFromTag(parent, name, context, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
rInflateChildren(parser, view, attrs, true);
viewGroup.addView(view, params);
}
}
//回调 onFinishInflate() 方法
if (finishInflate) {
parent.onFinishInflate();
}
}

//根据 xml 标签名称(eg: LinearLayout)创建 View 对象
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs, boolean ignoreThemeAttr) {
View view;
//通过 Factory 创建 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
if (view == null && mPrivateFactory != null) {
view = mPrivateFactory.onCreateView(parent, name, context, attrs);
}

if (-1 == name.indexOf('.')) {//系统 View,添加前缀 "android.view."
view = onCreateView(parent, name, attrs);
} else {//自定义 View,通过完整类名,反射实例化 View
view = createView(name, null, attrs);
}

return view;
}

//View 名称和其构造器的 Map 集合
private static final HashMap<String, Constructor<? extends View>> sConstructorMap =
new HashMap<String, Constructor<? extends View>>();

/**
* Low-level function for instantiating a view by name. This attempts to
* instantiate a view class of the given <var>name</var> found in this
* LayoutInflater's ClassLoader.
**/
public final View createView(String name, String prefix, AttributeSet attrs){
//从缓存中获取该 View 的构造器
Constructor<? extends View> constructor = sConstructorMap.get(name);
if (constructor == null) {
//通过类加载器加载该类
Class<? extends View> clazz = mContext.getClassLoader().loadClass(...);
//判断是否允许填充此类
boolean allowed = mFilter.onLoadClass(clazz);
if (!allowed) {
failNotAllowed(name, prefix, attrs);
}
//获取该类的构造器
constructor = clazz.getConstructor(mConstructorSignature);
//保存在缓存中
sConstructorMap.put(name, constructor);
} else {
//同上,执行 Filter
}
//args 是二维数组,也是自定义 View 时必须重写两个参数的构造方法的原因。
//否则会抛出 InflateException: Caused by: java.lang.NoSuchMethodException:
// <init> [class android.content.Context, interface android.util.AttributeSet]
args[1] = attrs;
//通过反射创建该 View 的实例
final View view = constructor.newInstance(args);
//设置 ViewStub 的 LayoutInflater,在需要时实例化 View
if (view instanceof ViewStub) {
// Use the same context when inflating ViewStub later.
final ViewStub viewStub = (ViewStub) view;
viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
}
return view;
}