LayoutInflater自从我们第一天开发程序,应该就会用到这个类,我第一次接触是在ListView的BaseAdapter,里面的getView方法会使用到,那也是我第一次见,只是简单的知道这样使用能返回一个View。后来需要动态添加布局的时候,也会采用这种方法,还有一种是View.inflate(Context context, @LayoutRes int resource, ViewGroup root),而跟进去可以看到这个方法最终也是调用LayoutInflater中的inflate方法,所以我们要了解他的真实参数和用法,只需要了解LayoutInflater中的inflate方法即可。我们先上个例子:
布局名:activity_main.xml
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.tb.myapplication.MainActivity">
<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
android:textSize="30dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<LinearLayout
android:id="@+id/ll"
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv" />
android.support.constraint.ConstraintLayout>
布局名:test.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:background="#aadd3344"
android:layout_height="match_parent">
<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="this is a test~"
android:textSize="30dp" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/ic_launcher_round" />
RelativeLayout>
上面两个布局,一个是主布局,里面的LinearLayout是我们要动态加入内容的parent,下面的布局是填充LinearLayout的,我们看第一种情况:
LinearLayout ll=findViewById(R.id.ll);
View view= LayoutInflater.from(this).inflate(R.layout.test,null,false);
Log.e(TAG, "onCreate: "+view.getClass().getCanonicalName());
ll.addView(view);
为了方便查看返回的view是什么,我们把布局的名称打印出来,看效果如下:
打印结果:
图中分析打印结果如期所料,把test的根布局直接加入到了LinearLayout中,这个视图结构我们也可以AndroidStudio自带的Layout Inspector看到:
画红线处可以证明上述猜想。但是有一个奇怪的问题来了,relativelayout我们明明写的是match_parent,为什么出来后跟预想结果不一样???这个我们留到后面讲。再来看另外一种情况:
View view= LayoutInflater.from(this).inflate(R.layout.test,null,true);
Log.e(TAG, "onCreate: "+view.getClass().getCanonicalName());
ll.addView(view);
结果跟上面是一样的,这个我就不贴图了,再看:
View view= LayoutInflater.from(this).inflate(R.layout.test,ll,false);
Log.e(TAG, "onCreate: "+view.getClass().getCanonicalName());
ll.addView(view);
终于得到我们想要的结果了,而且打印出来的依然是Relativelayout,再看最后一种情况:
View view= LayoutInflater.from(this).inflate(R.layout.test,ll,true);
Log.e(TAG, "onCreate: "+view.getClass().getCanonicalName());
ll.addView(view);
直接报错了:
我们可以看到打印出来的变成我们的parent——LinearLayout了,而且错误信息是child已经有一个父布局了,想要加入其它父布局必须先让之前的父布局移除掉自己的child。我们把最后一句addView去掉呢?运行后发现一切正常,而且打印出来的也是LinearLayout。这样所有情况我们都列举完了,下面带你一步一步去分析这其中缘由。
首先我们还是要去扒源码,上面说了View.inflate跟踪到最后也是调用LayoutInflater的inflate方法,而inflate方法有四个重载,分别是:
上面三个最终都是调用的最后一个,所以这里我们就分析最后一个就行了,上源码:
/**
* Inflate a new view hierarchy from the specified XML node. Throws
* {@link InflateException} if there is an error.
*
* Important For performance
* reasons, view inflation relies heavily on pre-processing of XML files
* that is done at build time. Therefore, it is not currently possible to
* use LayoutInflater with an XmlPullParser over a plain XML file at runtime.
*
* @param parser XML dom node containing the description of the view
* hierarchy.
* @param root Optional view to be the parent of the generated hierarchy (if
* attachToRoot is true), or else simply an object that
* provides a set of LayoutParams values for root of the returned
* hierarchy (if attachToRoot is false.)
* @param attachToRoot Whether the inflated hierarchy should be attached to
* the root parameter? If false, root is only used to create the
* correct subclass of LayoutParams for the root view in the XML.
* @return The root View of the inflated hierarchy. If root was supplied and
* attachToRoot is true, this is root; otherwise it is the root of
* the inflated XML file.
*/
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
//.......
View result = root;
//......
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);
// 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);
}
// Decide whether to return the root that was passed in or the
// top view found in xml.
if (root == null || !attachToRoot) {
result = temp;
}
}
//......
return result;
}
这里我只列举了基本的核心代码,不重要的就……省略了。首先从方法体的注释我们大致可以了解的比较清楚了,三个参数第一个不用多说,第二个就是根布局,这个参数其实是和第三个参数联合使用的,参考代码我们可以看到,分为下面三种情况处理:
就是我们上面的第一种情况,既没有指定父布局,也没有跟父布局做关联,这种直接返回了当前的view,而这个view是没有设置过layoutParam的,所以后面我们addView的时候也没有去指定layoutParam,系统就给我们指定了默认的,就造成了上面的效果。跟进去addView源码我们可以看看:
generateDefaultLayoutParams()这个方法,实际运行过程中会根据具体的layout加载不同的类,而每种的默认情况都是不一样的:
LinearLayout:
RelativeLayout:
FrameLayout:
看到这些你应该恍然大悟了吧,也就解释了为什么上面第一种情况明明test.xml中根布局写的是match_parent,结果却是wrap_content的效果,同理,这里无论你写什么,最终都是wrap_content,因为根布局是RealtiveLayout。如果想要期望的结果,就要我们addView的时候去手动创建并指定LayoutParam了(比如下面这样)。
ll.addView(view,new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT));
指定了父布局,但是并没有跟它attach到一起,这样就会把root的layoutParam设置给子布局,最后我们在addView的时候虽然没有指定layoutParam,但是其实在inflate的时候已经指定好了(参考代码43-53行),也就出现了我们想要的结果,不过如果你在addView的时候指定了,仍然以addView中的优先级最高。
为什么这样我们就会出现崩溃,看第60行代码你应该都明白了,这两个参数直接帮你把子布局加入到了根布局了,而且返回的result就是上面的root,即父布局,所以打印出来就是LinearLayout了,这个时候子布局已经有了父布局了,所以再进行addView当然就会出现上面的错误了,就好比这个孩子已经有了父亲,其他人肯定不能再强行拥有该孩子了,否则就是拐卖儿童了,系统肯定不允许,哈哈~
最后说一下merge,代码第32行我们可以看到对merge标签的处理,采用merge可以减少一层布局嵌套,这在app优化的时候经常会用到,当使用merge的时候,里面的子view的属性直接使用的就是外面的根布局的,而且必须是第四种情况,也就是说这个孩子必须是我的,谁也不能认领。看一下代码和最终效果以及嵌套层级:
<merge xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:background="#aadd3344"
android:layout_height="match_parent">
<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="this is a test~"
android:textSize="30dp" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/ic_launcher_round" />
merge>
可以看到merge仅仅是把里面的布局合并到LinearLayout中,里面设置的背景色并没有作用,而且里面的布局是父布局LinearLayout的上下排列,看层级结构也发现直接parent就是LinearLayout,起到了减少嵌套的效果。
OK,以上就是对LayoutInflater的一些讲解,希望大家都能更好的去使用这个类,不明白的小伙伴可以在下方留言,谢谢大家~