简介
LayoutInflater在日常的Android开发中是经常使用的类,常常用于XML中View的加载相关流程。本文主要总结一些其常见api的源码流程。
获取LayoutInflater
我们一般会在Activity的onCreate方法中会通过setContentView方法设置自己的布局layoutId,Activity会将加载View委托给Window。
java
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
public LayoutInflater getLayoutInflater() {
return getWindow().getLayoutInflater();
}
而Window对应的实现是PhoneWindow类
java
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
*****省略部分代码
mLayoutInflater.inflate(layoutResID, mContentParent);
}
public PhoneWindow(@UiContext Context context) {
super(context);
mLayoutInflater = LayoutInflater.from(context);
****省略部分代码
}
从上述可知activity中的相关inflater来自PhoneWindow,其本质来自 LayoutInflater.from(context)
Factory和Factory2
Factory和Factory2是两个接口,用户创建具体的View对象,Factory2继承自Factory,新增了onCreateView 重载API,该api新增了parent参数。
java
public interface Factory {
@Nullable
View onCreateView(@NonNull String name, @NonNull Context context,
@NonNull AttributeSet attrs);
}
public interface Factory2 extends Factory {
@Nullable
View onCreateView(@Nullable View parent, @NonNull String name,
@NonNull Context context, @NonNull AttributeSet attrs);
}
上述两个接口主要用在LayoutInflater类inflater()流程中,其中解析xml获取到对应的view描述信息后,调用tryCreateView去创建View。
java
public abstract class LayoutInflater {
@UnsupportedAppUsage
private Factory mFactory;
@UnsupportedAppUsage
private Factory2 mFactory2;
@UnsupportedAppUsage
private Factory2 mPrivateFactory;
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
//*****省略
try {
View view = tryCreateView(parent, name, context, attrs);
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
if (-1 == name.indexOf('.')) {
view = onCreateView(context, parent, name, attrs);
} else {
view = createView(context, name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
//****
}
public final View tryCreateView(@Nullable View parent, @NonNull String name,
@NonNull Context context,
@NonNull AttributeSet attrs) {
//*****
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);
}
return view;
}
可以看到,在tryCreateView中会按优先级去调用对应Factory的onCreateView方法去获取View实例,如果该方法返回空,则createViewFromTag方法会调用layoutInflater的onCreateView方法去创建View。该方法会反射创建View对象。
LayoutInflater中设计的Factory和Factory2相关接口和类,给了开发者自定义View创建和适配的机会。比如应用换肤、framework的View替换等。比如包含TextView的xml设置给AppCompatActivity时,其TextView会被替换成AppCompatTextView。
inflate流程
我们一般使用layoutInflater对象的inflater方法来加载xml中的View。该方法需要关注root和attachToRoot参数,root表示当前待加载的View的父容器。该参数对加载的影响主要是父容器的尺寸和主题对当前View的效果影响。而attachToRoot表示是否将当前待加载的View添加到父容器中,这个还会影响inflate方法返回值,当attachToRoot=true,返回父容器,attachToRoot=false返回当前加载到View。
java
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
public View inflate(XmlPullParser parser, @Nullable ViewGroup root) {
return inflate(parser, root, root != null);
}
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
//如果是预编译的View,则反射创建该View。这里的预编译是指在打包时将xml描述的布局编译成.CompiledView文件。这样创建时不用解析xml。默认预编译为关闭状态
View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
if (view != null) {
return view;
}
XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
try {
advanceToRootNode(parser);
final String name = parser.getName();
if (TAG_MERGE.equals(name)) {
//merge标签时,root必须非NULL,并且attachToRoot必须为true。因为meger标签本身没有parent,无法正确加载。
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// 根据name创建对应的View,前文我们有提到这个方法
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
//生成params
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
rInflateChildren(parser, temp, attrs, true);
// 如果root不为空并且attachToRoot=true,则把当前加载View添加到root中
if (root != null && attachToRoot) {
root.addView(temp, params);
}
//result为返回值,这里root == null || !attachToRoot时返回值时当前View,否则是root
if (root == null || !attachToRoot) {
result = temp;
}
}
}
//***省略***
return result;
}
}
LayoutInflater创建View
前文我们分析createViewFromTag这个方法时提到,如果factory和factory2创建View时都返回了Null,那么会兜底走LayoutInflater的onCreateView方法,该方法最终会调用createView创建View。我们来看看View是是不是通过反射创建的。
java
public final View createView(@NonNull Context viewContext, @NonNull String name,
@Nullable String prefix, @Nullable AttributeSet attrs)
throws ClassNotFoundException, InflateException {
//*******
Class<? extends View> clazz = null;
try {
if (constructor == null) {
// Class not found in the cache, see if it's real, and try to add it
//****
//获取View的构造函数并缓存,这样不用每次都去查
constructor = clazz.getConstructor(mConstructorSignature);
constructor.setAccessible(true);
sConstructorMap.put(name, constructor);
}
//****省略部分代码
Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = viewContext;
Object[] args = mConstructorArgs;
args[1] = attrs;
try {
//反射创建View
final View view = constructor.newInstance(args);
if (view instanceof ViewStub) {
//如果是ViewStub,则设置LayoutInflater给它
final ViewStub viewStub = (ViewStub) view;
viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
}
return view;
} finally {
mConstructorArgs[0] = lastContext;
}
} //***
}