LayoutInflater代码解读
本文主要解读LayoutInflater加载XML的过程。
我们经常在ListView或者RecyclerView亦或是Fragment的onCreateView中调用到LayoutInflater来加载布局得到一个View对象。(当然其实Activity的setContent方法也是要间接的调用到的)。于是就源码来进行跟踪分析。
比如我们
LayoutInflater.from(mContext).inflate(R.layout.item, null);
如此调用。那么就来看一看它所走的流程吧最终是如何通过加载一个xml布局文件返回一个View对象。
当然如果inflate方法root参数和attachToRoot参数传入会有较大的区别,详见源码
首先from(context)方法返回了LayoutInflater对象本身。然后就是关键的inflate方法。
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
这里是调用了重载了的另一个方法。
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
//debug的日志。忽略
if (DEBUG) { Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" (" + Integer.toHexString(resource) + ")"); }
//通过layout的id返回一个XmlResourceParser
final 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) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
final Context inflaterContext = mContext;
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context) mConstructorArgs[0];
mConstructorArgs[0] = inflaterContext;
//最终返回给外部的View对象
View result = root;
try {
int type;
//遍历真个xml标签,找开始标签,START_TAG和END_TAG具体是什么稍后在看
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty
}
//如果遍历完成都没有START_TAG就抛出异常
if (type != XmlPullParser.START_TAG) {
throw new InflateException(parser.getPositionDescription()
+ ": No start tag found!");
}
//拿到标签的name,具体实现暂不深究,猜测就是TextView等这种名字
final String name = parser.getName();
//debug日志省略
//处理根标签是merge的
if (TAG_MERGE.equals(name)) {
//处理merge标签。如果没有root会抛出异常。
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
//创建跟布局对应的View或ViewGroup
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
//root不为空的时候设置layoutparams,这也是为什么root不传的时候最外层布局 //的一些属性无效的原因比如,padding、margin等
if (root != null) {
if (DEBUG) {
System.out.println("Creating params from root: " +
root);
}
// 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);
}
}
if (DEBUG) {
System.out.println("-----> start inflating children");
}
// Inflate all children under temp against its context.
//根布局处理完成后进行子标签的递归遍历
rInflateChildren(parser, temp, attrs, true);
if (DEBUG) {
System.out.println("-----> done inflating children");
}
// We are supposed to attach all the views we found (int temp)
// to root. Do that now.
//如果rootview存在则。add到父布局。
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;
}
}
} catch (XmlPullParserException e) {
final InflateException ie = new InflateException(e.getMessage(), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} catch (Exception e) {
final InflateException ie = new InflateException(parser.getPositionDescription()
+ ": " + e.getMessage(), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} finally {
// Don't retain static reference on context.
mConstructorArgs[0] = lastContext;
mConstructorArgs[1] = null;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
return result;
}
}
先来继续看一下createViewFromTag。用于创建根布局
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs, boolean ignoreThemeAttr) {
if (name.equals("view")) {
name = attrs.getAttributeValue(null, "class");
}
// Apply a theme wrapper, if allowed and one is specified.
if (!ignoreThemeAttr) {
final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
final int themeResId = ta.getResourceId(0, 0);
if (themeResId != 0) {
context = new ContextThemeWrapper(context, themeResId);
}
ta.recycle();
}
if (name.equals(TAG_1995)) {
// Let's party like it's 1995!
return new BlinkLayout(context, attrs);
}
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) {
final InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class " + name, e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} catch (Exception e) {
final InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class " + name, e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
}
}
然后对子View的递归遍历
void rInflate(XmlPullParser parser, View parent, Context context,
AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
final int depth = parser.getDepth();
int type;
boolean pendingRequestFocus = false;
while (((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
if (type != XmlPullParser.START_TAG) {
continue;
}
final String name = parser.getName();
//如果设置了requestFocus,比如EditText。会让View获取焦点
if (TAG_REQUEST_FOCUS.equals(name)) {
pendingRequestFocus = true;
consumeChildElements(parser);
} else if (TAG_TAG.equals(name)) {
//如果设置了tag属性会解析出来,对View 进行setTag,调用 view.setTag(key, value);key 是int 类型的id,value可以是任何的Object
parseViewTag(parser, parent, attrs);
} else if (TAG_INCLUDE.equals(name)) {
//对include标签的处理
if (parser.getDepth() == 0) {
throw new InflateException(" cannot be the root element");
}
parseInclude(parser, context, parent, attrs);
} else if (TAG_MERGE.equals(name)) {
//这里说明merge标签不能随意使用。只有在根布局。在childview使用是会抛出异常的
throw new InflateException(" must be the root element");
} else {
//如果不是上面的特殊类型,就创建View,设置params,add到对应的ViewGroup中去
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);
}
}
if (pendingRequestFocus) {
parent.restoreDefaultFocus();
}
if (finishInflate) {
parent.onFinishInflate();
}
}
我觉得从中总结到最有用的几点:
没有root最外层的layoutparams是无效的。
如果root为空,mereg标签是不能用在根标签的、另外在使用include的时候,merge必须在include的布局的根标签。
还发现了一个BlinkLayout继承自FramLayout,用于制作闪烁效果。查了下。说是为了纪念1995复活节!