首先感谢AndroidDevelopment邪教群中月亮和六便士的讲解。接下来让我们步入正题:
Android中用来填充布局的主要的两个方法如下:
LayoutInflater.from(getContext()).inflate(resource, root, attachToRoot);
View.inflate(context, resource, root);
根据个人习惯不同,可能大家使用方法不同,之前我是习惯使用 View.inflate(getContext(),R.layout.layout_main,null);
方法的,直到有一天我遇到一个问题:
我发现使用该方法,为listView(RecycleView)填充item条目的时候,根布局设置的layout参数总是不起作用,后来了解,在这种情况下,其实根布局的所有的属性都是不管用的。
今天下午在邪教群 月亮和六便士 讲解里我了解了其中的原因:
首先看下View.inflate方法的几个参数的意义:
第一个参数 Context。第二个参数,布局id,第三个参数root。这里主要讲下第三个参数,因为第三个参数我们一般都会传一个null,但是大都数人都不知道其中的原因。
首先来看下官方给出的方法注解:
/**
* Inflate a view from an XML resource. This convenience method wraps the {@link
* LayoutInflater} class, which provides a full range of options for view inflation.
*
* @param context The Context object for your activity or application.
* @param resource The resource ID to inflate
* @param root A view group that will be the parent. Used to properly inflate the
* layout_* parameters.
* @see LayoutInflater
*/
public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) {
LayoutInflater factory = LayoutInflater.from(context);
return factory.inflate(resource, root);
}
注意看:A view group that will be the parent. Used to properly inflate the layout_* parameters.
前半句的意思是,该参数将作为一个你布局的父View,后半句意思是,使用你inflate布局的跟布局的layout_*
参数,也就是你根布局layout_*
参数将会生效。反之layout_*
参数将会不生效。这样我们就可以理解为什么我们设置的跟布局参数无法生效了,因为我们该参数传了个null。
大家可能会说那我就讲parent传进去就好了呀,事情当然不会这么简单,比如我们在listView的getView方法中,将getView的参数作为inflate的第三个参数传入,就会报错,错误的意思是getView的返回的view已经有个parent不能再为其指定一个parent了,相信大家都知道getView方法中的parent是listView本身,不知道的去打印下。
这是为什么? 接着往下看你就会明白了:
/**
* Inflate a new view hierarchy from the specified xml resource. Throws
* {@link InflateException} if there is an error.
*
* @param resource ID for an XML layout resource to load (e.g.,
* R.layout.main_page
)
* @param root Optional view to be the parent of the generated hierarchy.
* @return The root View of the inflated hierarchy. If root was supplied,
* this is the root View; otherwise it is the root of the inflated
* XML file.
*/
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
继续点击查看源码实际上,factory.inflate(resource, root);调用了inflate(resource, root, root != null)方法,
这个方法也就是LayoutInflater.from(getContext()).inflate(resource, root, attachToRoot);
好,那么我们有必要解释下inflate的三个参数,以及他们各自的作用了
/**
* Inflate a new view hierarchy from the specified xml resource. Throws
* {@link InflateException} if there is an error.
*
* @param resource ID for an XML layout resource to load (e.g.,
* R.layout.main_page
)
* @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(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
第一个参数不多说 填入你定义好的布局
第二参数root也就是View.inflate(context, resource, root);的第三个参数,看下注解的解释:
root:
* @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.)
注解的大概意思就是:如果 attachToRoot 参数为true的时候,你inflate返回的view将会是parent本身,而parent将会调用addView方法添加到这个布局view中;如果attachToRoot参数为false的时候,inflate将把你的布局的参数,作为下次调用addView的方法的时候的布局依据。当然,上边说的这些都是在root不为空的前提下的。
attachToRoot:
* @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.
这里多次用了attachToRoot参数,该参数的作用是什么,通过注解可以看到 该参数决定了infalte返回的view是root,还是你的布局view。
如果为true返回root,如果为false,返回的是你的View,那么我们有点蒙了,我们什么时候返回false,什么时候返回true。群里的@深圳-奕超同学说了一个可能就是如果你的resource的跟布局是一个
标签的话就必须使用true了,原因是:
inflate的时候如果不指定root并指定为true,那么infate返回的是被填充资源的根布局。
而如果为merge标签,merge本身不是一个view,Android在填充这个布局讲merge转成view的时候会报错,因为merge没有对应的Java对象。
好现在我们应该能得到的结论是:inflate(resource, root, attachToRoot);
root参数是否制定,会影响你的view根布局参数是否会生效。
attachToRoot参数的true 和 false 将决定你的infalte方法返回值是root还是你的填充资源的根布局。
那么回到之前的问题,view.infalte中的第三个参数传入parent就会报错的问题
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
上边,这段代码是view.infalte将会走的方法,可以看见如果我们指定root对象,他就会将attachToRoot设置为true,然后根据之前的讲解,如果attachToRoot为true,inflate返回的View就会是root本身(布局view已经作为子view添加到rootView)。而listVIew中的parent参数就是listView本身,那么在getView中又返回了listView,listView本身是父View的,这时候我们相当于把listView作为Item又添加到listView中,这时候我们先相当于又给其指定了一个父View即listView,这时候报错也是必然的了,因为一个view只能有一个父View。
下面继续深入理解下inflate(resource, root, attachToRoot);后两个参数的作用:
假设这样一个场景:
我用一ViewGroup1(相对布局),里边有个btn。我现在想通过inflate的方法为这个ViewGroup添加一个子View,并放在btn的右边
<LinearLayout
.....
android:Layout_toRightOf:"@+id/btn"
/>
我们的inflate参数是这样写的:
View view = inflate(R.Layout.linear_layout,viewGroup2,false);
注意:viewGroup2也是个相对布局,但是并不是viewGroup1
然后调用,viewGroup1.addView(view),这时候view会不会位于btn的右侧?
答案是肯定的,如果你足够明白上边所讲的知识,inflate(R.Layout_linear_layout,viewGroup2,false),方法将会是R.layout.linear_layout
的根布局参数生效。当viewGroup1.addView(view)则按照view的跟布局参数设置view的位置
那好,我们将ViewGroup1换成线性布局会发生什么事呢,聪明的你肯定猜到了,view肯定不会再btn右侧了
(这个如果你的线性布局水平布局的话还是有可能的,当然我们讲的中点不是这个)
我们要说的是,造成这个结果原因是,虽然我们view的根布局有toRightof的属性,但是Linearlayout根本不认呀。
好最后放大招总结:inflate(resource, root, attachToRoot);
此时,inflate返回的是添加了你的布局的root。