一般来说,加载渲染试图view所用方法无非两种:
第一种是View类中的静态inflate()方法;
第二种就是LayoutInflater.from(context).inflate()方法
那么这两种方法有什么区别呢?
首先我们看一下View.inflate()方法,点进去看源码:
public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) {
LayoutInflater factory = LayoutInflater.from(context); //还是使用的LayoutInflater.from
return factory.inflate(resource, root);
}
那么这个方法和LayoutInflate方法到底有什么区别呢?我们继续点击factory.inflate(resource, root)进去看一下
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);------注意处。
}
这块很明显了,View.inflate(contex,resource,root)最后其实还是调用的LayoutInflater.from(context).inflate(resource, root, attachToRoot),只是这个布尔值attachToRoot直接为了root != null。attachToRoot很清楚的翻译:是否绑定到root。用View.inflater渲染时,如果传入了有意义的root,那么最后的attachToRoot=true。
LayoutInflater.from(context).inflate(resource, root, attachToRoot)这个方法大家肯定不陌生,在listview或者recyclerView对viewholder绑定时都要用到这个方法来渲染出item进行绑定。那么我们一直在adapter中是这样写的:LayoutInflater.from(context).inflate(resource, root, false)。那么最后这个参数有什么影响呢?我们不妨将自己项目中的参数进行修改,改为传递true进去运行,结果如下:
java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.
错误信息提示我们这个childview已经有了自己的父集view,你必须先去remove掉child的第一个父集view。
什么意思?我们什么时候去将我们渲染出来的child添加到其他view中了呢?深入下去:
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
final Context inflaterContext = mContext;
//解析xml布局文件获取属性
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context) mConstructorArgs[0];
mConstructorArgs[0] = inflaterContext;
View result = root;
try {
// Look for the root node.
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty
}
if (type != XmlPullParser.START_TAG) {
throw new InflateException(parser.getPositionDescription()
+ ": No start tag found!");
}
final String name = parser.getName();
if (DEBUG) {
System.out.println("**************************");
System.out.println("Creating root view: "
+ name);
System.out.println("**************************");
}
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");
}
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) {
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);
//-参数的设置,如果root为null,就不会设置view的参数、
}
}
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.
//第三处
if (root != null && attachToRoot) {
root.addView(temp, params);
//listview或者recyclerview中传false是因为不需要自己去addchild,最后listview等都会自己去加载自己渲染出来的child。
}
// 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;
}
}
分开来讲第一处:
//第一处
// Temp is the root view that was found in the xml
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
很简单的一个英语翻译:这个temp就是从你传入的xml文件中找到的根节点,什么意思?这个temp就是每一个渲染出来的view,在recyclerview和listview中就是每一个item。
继续第二处分析:
//第二处
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);
//-参数的设置,如果root为null,就不会设置view的参数、
}
}
如果root不为null,它会去给你获取到属性值,如果attachToRoot是false,他才会给你渲染出来的temp设置进去。所以如果单独去渲染一个view出来,想要获取到他的LayoutParams,那么在调方法时,就要注意root和attachToRoot的参数的传入了,不然获取LayoutParams只会返回null。
OK,最后看一下第三处:
//第三处
if (root != null && attachToRoot) {
root.addView(temp, params);
//listview或者recyclerview中传false是因为不需要自己去addchild,最后listview等都会自己去加载自己渲染出来的child。
// Decide whether to return the root that was passed in or the
// top view found in xml.
if (root == null || !attachToRoot) {
result = temp;
}
}
这地方就是The specified child already has a parent. You must call removeView() on the child's parent first.的真正原因了。如果root不为null且attachToRoot=true,他会直接将你渲染出来的view(temp)添加到root中,但是listview和recyclerview他的内部都会将渲染出来的item添加到其内部,所以如果这里就直接给temp添加了一个parent,那么在listview等添加时就会直接报错。
那么root能否提供null呢?很显然整个方法块下来,root是否传null对view的渲染没有影响,仅仅是在给view设置参数和添加parent是会进行判断。