LayoutInflater
我们经常使用LayoutInflater将布局文件渲染成View层级视图,那么具体是怎么使用的呢?目前有四种方式:
- context.getSystemService()
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View rootView = inflater.inflate(R.layout.view_layout, null);
- LayoutInflater.from(context)
LayoutInflater inflater = LayoutInflater.from(context);
View rootView = inflater.inflate(R.layout.view_layout, null);
- Activity下调用getLayoutInflater()
LayoutInflater inflater = getLayoutInflater();
View rootView = inflater.inflate(R.layout.view_layout, null);
- View的静态方法,View.inflate()
rootView = View.inflate(context, R.layout.view_layout, null);
通过源码可以得知,第二、第三、第四种方式其实就是第一种方式的封装,最终获取View实例的是通过inflater实例的inflate()方法。
我们再看一下Activity的onCreate()方法中的setContentView方法:
public void setContentView(int layoutResID) {
...省略
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
...省略
}
可以看到同样也是使用了inflater的inflate()方法。所以我们可以得到这样的结论:
无论是我们自己主动调用inflater的inflate()方法渲染View,还是Activity通过setContentView来渲染View,都是通过inflater的inflate()方法来完成的。
我们继续跟踪inflate()方法,发现最终调用的是inflate(XmlPullParser,ViewGroup, boolean attachToRoot) 方法:
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
...省略
try {
...省略
final String name = parser.getName();
if (TAG_MERGE.equals(name)) {
if (root == null || !attachToRoot) {
throw new InflateException(" can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
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);
}
}
// Inflate all children under temp against its context.
rInflateChildren(parser, temp, attrs, true);
...省略
}
} catch (XmlPullParserException e) {
..省略
} catch (Exception e) {
...省略
} finally {
...省略
}
return result;
}
}
我们省略掉了一些代码,直接从try代码块开始看,不考虑merge标签(其实merge标签只是正常渲染的一种特殊情况),我们就走到了else语句下,关键代码createViewFromTag()生成了View。
那我们继续跟踪,看一下createViewFromTag执行了什么操作:
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
if (name.equals("view")) {
name = attrs.getAttributeValue(null, "class");
}
...省略
try {
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;
}
if (view == null && mPrivateFactory != null) {
view = mPrivateFactory.onCreateView(parent, name, context, attrs);
}
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
if (-1 == name.indexOf('.')) {
view = onCreateView(parent, name, attrs);
} else {
view = createView(name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
} catch (InflateException e) {
throw e;
} catch (ClassNotFoundException e) {
...省略
} catch (Exception e) {
...省略
}
}
我们还是讲部分代码省略了,从try代码块开始看,当mFactory2或者mFactory不为null时,View是由它们的onCreateView方法生成的,否则使用系统默认创建View的流程。系统默认创建View的流程是通过判断标签名称有没有包含".",如果没有则将前缀"android.view."添加到标签名前面,最终调用LayoutInflater的createView()方法,然后返回View。
到这里我们就知道了LayoutInflater根据布局文件来渲染View的主要流程:先通过布局文件的资源ID创建XmlResourceParser解析器对象,然后利用该对象递归解析布局文件,根据解析出来的标签名生成View,最终返回层级视图View。而如果LayoutInflater设置了Factory2或者Factory,那么在创建View时都会调用Factory2或者Factory的onCreateView方法,所以我们可以在View创建之前在onCreateView中做一些相关逻辑,比如说换肤。
LayoutInflater之Factory接口
未完待续...