理解Android的LayoutInflater.inflate()
方法
[Android 官方文档](http://developer.android.com/reference/android/view/LayoutInflater.html#inflate(int, android.view.ViewGroup, boolean))
BigNerdranch 博客
注:相对于官方博客,BigNerdranch上的博客提供了更接地气的通俗易懂的解释。
下文为汲取了诸多前辈的经验知识后个人总结所得,如有理解或解释错误的地方,请诸位看客老爷指正!
inflate
意思是膨胀,打气。从XML到View,原本的code到可视化的view,就是一个充气、具象化的过程。所以我理解为具象化。
LayoutInflater
的作用是 将一个XML布局文件实例化到其对应的View对象中。它不能直接用,需要检索当前的上下文来获取配置,从而实例化。
LayoutInflater.inflate:
View inflate (int resource, ViewGroup root, boolean attachToRoot)
参数名 | 解释 |
---|---|
resource | int,要加载的XML布局文件ID |
root | ViewGroup,可选项(取决于第三个参数),作为要加载的布局文件的父节点 |
attachToRoot | boolean,是否将新具象化的层次结构附加到根参数 |
官方文档给的解释到此为止了,那么这个attachToRoot到底做了什么?
通俗解释
如果attachToRoot
被设为true
,那么第一个参数中被指定的布局会被附加到第二个参数中指定的ViewGroup中。
然后该方法返回它们组合形成的View,并将第二个参数ViewGroup作为根节点。
如果attachToRoot
被设为false
,那么第一个参数中指定的布局被具象化,并作为一个View返回。
这个意思是,之后这个View会以其他某种方式加入到ViewGroup中。
在以上两种情况下,我们都需要ViewGroup的布局参数,来确定我们第一个参数具象化的View的size和它的位置。
设 attachToRoot 为 true
假设我们在一个XML布局文件 custom_button
中定义了一个Button,并设置了它的宽高为match_parent。
<Button xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/custom_button"/>
现在,我们通过代码方式把这个Button添加到一个Fragment或者Activity的线性布局里。假设这个LinearLayout已经是一个
成员变量,记为 mLinearLayout
,我们这样添加Button:
inflater.inflate(R.layout.custom_button, mLinearLayout, true);
我们首先指明我们想要从这个button的布局文件中具象化它;之后我们告诉inflater,我们想把这个button添加到mLinearLayout中。那布局参数
就很高兴,因为知道了button已经被添加到一个线性布局文件中了。那这Button的布局参数就应该是LinearLayout.LayoutParams
。
另一种传递true
值给attachToRoot
的情况是使用定制View。看一个例子。在一个布局文件中用<merge>
标签作为它的根节点。
这表示这个布局文件 允许 根据它可能具有的根ViewGroup的类型 来灵活安排。
public class MyCustomView extends LinearLayout {
...
private void init() {
LayoutInflater inflater = LayoutInflater.from(getContext());
inflater.inflate(R.layout.view_with_merge_tag, this);
}
}
在这个例子中,布局文件没有作为根的ViewGroup,所以我们指定了我们定制的LinearLayout作为它的根。
如果我们的布局文件有一个FrameLayout作为它的根而不是<merge>
,那么这个FrameLayout和它的子节点就会正常具象化。然后这个FrameLayout和子节点会被加载到这个LinearLayout,使得LinearLayout成为根节点包含其它节点。
设 attachToRoot 为 false
在这种情况下,在第一个参数中指定的View不会在这个时候被添加到第二个参数所指的ViewGroup。
还是刚才的Button。当我们传false给attachToRoot的时候,依然可以把Button添加到mLinearLayout
上————只要我们后面自己手动添加即可。
Button button = (Button) inflater.inflate(R.layout.custom_button, mLinearLayout, false);
mLinearLayout.addView(button);
上面这两行代码和之前的效果是相同的。传入false后,我们不想第一时间把这具象化的view添加到第二个参数指定的ViewGroup。而是,之后某个时间添加。在这个例子中,某个时间就是在具象化后,调用addView()
方法的时候。
上面的例子中,为了实现相同的效果,传入false的做法比传入true的做法要费事一点。那么为什么还这么做?有没有一些情况下必须要这样做的呢?答案是肯定的!有些情况下,必须这么做~
一个RecyclerView的子类在具象化的时候,attachToRoot必须传入false。子视图是在onCreateViewHolder()
方法中具象化的:
public ViewHolder onCreateViewHolder(LayoutInflater inflater, ViewGroup parent, int viewType){
View view = inflater.inflate(android.R.layout.list_item_recyclerView, parent, false);
return new ViewHolder(view);
}
这里,RecyclerView(而不是我们)负责决定什么时候具象化并且展示它的子视图。因此,任何时候attachToRoot
参数就一定要是false,我们不负责添加它到ViewGroup。
当我们在onCreateView()
中具象化并且返回一个Fragment的视图时,要确保传false到attachToRoot
中。如果我们传入true,就会得到一个IllegalStateException
,因为指定的孩子View已经有父ViewGroup了。我们应该做的是,指明这个Fragment的view将会放在我们的Activity的什么地方。这个,就是FragmentManager
的工作了,它负责添加、移除和替换Fragments:
FragmentManager fragmentManager = getSupportFragmentManager();
Fragment fragment = fragmentManager.findFragmentById(R.id.root_viewGroup);
if (fragment == null) {
fragment = new MainFragment();
fragmentManager.beginTransaction()
.add(R.id.root_viewGroup, fragment)
.commit();
}
root_viewGroup
容器是我们指定给onCreateView()
的第一个参数,作为ViewGroup来容纳我们的Fragment。同时,它也作为LayoutInflater.inflate()
的第二个参数。FragmentManager
会将Fragment的View添加到这个ViewGroup当中,当然,我们不希望添加它两次,所以,传递给attachToRoot
的值是false:
public View onCreateView(LayoutInflater inflater, ViewGroup parentViewGroup, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_layout, parentViewGroup, false);
…
return view;
}
如果我们不想在onCreateView()
中将View附加给ViewGroup,那为什么我们还要将Fragment的父ViewGroup放在第一位置呢?又为什么inflate()
方法要求有一个根ViewGroup呢?
这是因为,即使我们不想立刻将具象化的View添加到它的父ViewGroup中去,为了知道当这个View最终被添加的时候,它的size和位置,我们也要需要父ViewGroup的布局参数LayoutParams
。
最后一点建议,通常来说,当我们可以添加父ViewGroup的时候,我们还是加上,不将其设置为null。
总结
希望这篇理解文章可以帮你在用LayoutInflater时避免程序崩溃、误解或者不能掌控它的行为。
下面是在特定场合使用时的一些注意点:
- 当我们有一个父ViewGroup可以传入ViewGroup时,将它传进去。
- 尽量避免传入null给root ViewGroup。
- 当我们不需要负责将布局文件的视图添加给它的root ViewGroup的时候,设 attachToRoot 为 false。
- 不要给一个已经被添加到ViewGroup的View传入true。
- 定制视图是传入true值的经典运用。
网友评论