一、前言
以前用adapter的时候每次就知道copy代码,对LayoutInflater不求深入了解,真是惭愧!今天抽空把LayoutInflater的源码看了看,终于有些感悟,记录下来,希望对大家有些帮助。
二、方法介绍
我们最常用的就是以下2个方法:
1.LayoutInflater.from(MainActivity.this).inflate(int resource, ViewGroup root);
2.LayoutInflater.from(MainActivity.this).inflate(int resource, ViewGroup root, boolean attachToRoot);
第1个方法的源码是这样的:
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
可以看的出来,在方法中调用的是第2中方法,这样算起来就剩下第2个方法了,使有的时候会出现这二种情况:
1、convertView = mInflater.inflate(R.layout.item, parent ,false);
2、convertView = mInflater.inflate(R.layout.item, parent ,true);
可以看的出,它们的区别就在于最后一个boolean类型的参数,那它们二个有什么区别呢?我们先来看个例子
三、举个例子看现象
我们在Linearnlayout中分别添加以下三种情况的view,看看它们的显示:
inflate(layoutId, null )
inflate(layoutId, root, false )
inflate(layoutId, root, true )
Activity代码
private LinearLayout contentLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
contentLayout = (LinearLayout) findViewById(R.id.contentLayout);
// View view1 = LayoutInflater.from(MainActivity.this).inflate(R.layout.item,null);
// View view2 = LayoutInflater.from(MainActivity.this).inflate(R.layout.item,contentLayout,false);
// View view3 = LayoutInflater.from(MainActivity.this).inflate(R.layout.item,contentLayout,true);
// contentLayout.addView(view1);//情况1
// contentLayout.addView(view2);//情况2
// contentLayout.addView(view3);//情况3
}
activity_main代码:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/contentLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
</LinearLayout>
item代码:
<?xml version="1.0" encoding="utf-8"?>
<Button xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="button"
/>
显示如下:
1.inflate(layoutId, null )的情况
2.inflate(layoutId, root, false ) 的情况
3.inflate(layoutId, root, true ) 的情况
是的,要相信自己,你没看错,第3种情况确实是报错了!
由上面的效果图我想信大家一定有这几个疑问:
1.为什么情况1宽度是整个屏幕,情况2是自适应。
2.为什么情况3报错了。
接下来,我们来看看源码,找出是什么导致了这些情况!
四、源码解析
上面我们分析过,这3中情况最后都调用了一个方法,我们来看看它的源码:
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
....
//先将root赋值给result,后面我们会根据不同的参数改变result值,最后我们要返回resulut;
View result = root;
try {
// Look for the root node.
...
这块是用解析xml布局代码
...
if (TAG_MERGE.equals(name)) {
...
} else {
// Temp is the root view that was found in the xml
//在createViewFromTag()方法的内部调用createView()方法,然后使用反射的方式创建出View的实例并返回。
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)
// 当root不为null,attachToRoot为false时,为temp设置了LayoutParams.
temp.setLayoutParams(params);
}
}
// 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.
//重点看这里,如果root不为空 并且 attachToRoot为true的时候,给temp添加LayoutParams属性
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.
//重点看这里,如果root不为空 并且 attachToRoot为 false的时候,直接返回temp
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;
}
}
这了让大家看的简单一点,我省略了很多无用的代码,大家看源码中有汉字的地方,那是重点。
1:声明了View result = root ;//最终返回值为result
2:temp = createViewFromTag(root, name, attrs);创建了View
3:
if(root!=null)
{
params = root.generateLayoutParams(attrs);
if (!attachToRoot)
{
temp.setLayoutParams(params);
}
}
当root不为null,attachToRoot为false时,为temp设置了LayoutParams.
4:
if (root != null && attachToRoot)
{
root.addView(temp, params);
}
当root不为null,attachToRoot为true时,将tmp按照params添加到root中。
5.
if (root == null || !attachToRoot) {
result = temp;
}
如果root为null,或者attachToRoot为false则,将temp赋值给result。
最后返回result。
经上面的分析,我们可以得到以下结论:
从上面的分析已经可以看出:
1.Inflate(resId , null ) 只创建temp ,返回temp
2.Inflate(resId , parent, false )创建temp,然后执行temp.setLayoutParams(params);返回temp
3.Inflate(resId , parent, true ) 创建temp,然后执行root.addView(temp, params);最后返回root
由此我们可以解释为什么情况3会报错:
因为root.addView(temp, params)这句话执行了,我们的contentLayout已经将button添加到里面去了,如果我们再调用contentLayout.addView(view3);它肯定会报错的。我们可以验证一下,代码如下:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
contentLayout = (LinearLayout) findViewById(R.id.contentLayout);
View view3 = LayoutInflater.from(MainActivity.this).inflate(R.layout.item,contentLayout,true);
}
这们在这里只是调用了inflate(R.layout.item,contentLayout,true);并没有调用contentLayout.addView(view3)。看一下效果图:
看到没有,这就证实了我们上面的分析:
Inflate(resId , parent, true ) 创建temp,然后执行root.addView(temp, params);最后返回root
再在看看第2个坑:为什么情况1的宽度是整个屏幕,情况2是自适应呢?并且在情况1中不管button设置成多大的值,它的宽度都是整个屏幕。
这里我要向大家道歉,因为我也不知道为什么。对不起。在上面我们分析了:
Inflate(resId , null ) 只创建temp ,返回temp
只创建view,并没有设置LayoutParams,这才导致它的显示不正常,但为什么不正常,我还没找到原因。后面我会再找找。
五、结尾
好了就讲到这里吧,希望对大家有所帮助。
在技术上我依旧是个小渣渣,加油!勉励自己!