从 LayoutInflater 谈起

LayoutInflater 是我们用来加载布局的类,经常用于 Adapter 的 getView,Fragment 的 onCreateView 方法中。但我在使用时,经常有些疑惑,比如 inflater 方法参数的数目有时候为 2 个,有时候 3 个。第 2 个参数经常为 null,而第 3 个参数在第 2 个参数不为 null 的情况下常为 false。这些到底有什么区别?通过一段时间的实践,稍微有了些体会,写文章来总结下。

获取 LayoutInflater 实例

我们首先从简单的说起,如何获取 LayoutInflater 的实例。
比较常用的方法有 3 种:

  • LayoutInflater inflater = getLayoutInflater(); // 调用 Activity 的 getLayoutInflater()
  • LayoutInflater inflater = LayoutInflater.from(context);
  • LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

前两种看起来简单,第三种看起来最繁琐。但实质上前两种都是调用的第三种。

inflate(int resource, ViewGroup root, boolean attachToRoot)

然后来到我们的重点,LayoutInflater.inflate 的方法。

参数的数量

inflate 方法有 2 个参数的,也有 3 个参数的。但本质上来讲 2 个参数只是 3 个参数的缩略形式。
inflate(resource, root) <=> inflate(resource, root, true)
inflate(resource, null) <=> inflate(resource, root, false)
通过判断 root == null 来添加第 3 个参数,若是 null 则为 false, 反之则为 true。

参数的意义

官方文档)
第 1 个参数大家都很清楚,是要加载的布局 id。
而第 2 个参数从直观但不准确的意义上来讲,有两种作用。在 attachToRoot 为 false 的情况下,root 使得要加载的布局文件最外层的布局的 layout_height、layout_width 等属性生效。在 attachToRoot 为 true 的情况下,root 为加载的布局的父布局。
第 3 个参数如上所述,是用来控制 root 参数所发挥的作用。

例子

例子来源Android LayoutInflater原理分析,带你一步步深入了解View

activity_main.xml

1
2
3
4
5
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/main_layout"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</LinearLayout>

button_layout.xml

1
2
3
4
5
<Button xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="80dp"
android:layout_height="80dp"
android:text="Button" >
</Button>

MainActivity.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MainActivity extends Activity {
private LinearLayout mainLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mainLayout = (LinearLayout) findViewById(R.id.main_layout);
LayoutInflater layoutInflater = LayoutInflater.from(this);
View buttonLayout = layoutInflater.inflate(R.layout.button_layout, null);
mainLayout.addView(buttonLayout);
}
}

当运行程序时,我们会发现,button 的大小和我们的设定不一致。这是因为 root 设为 null,使得加载的布局的最外层,这里也就是 Button,它的layout_width,layout_height 等都会失效。
那怎么解决这个问题?参考的文章中采用了在 Button 外面再套一层 RelatvieLayout 的方法。但是,这显然不是好方法。因为 RelativeLayout 这个布局几乎没什么作用。那如何做到优化呢?其实我们只要简单修改下 inflate 的参数就可以了。

1
2
3
4
5
6
7
8
9
10
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mainLayout = (LinearLayout) findViewById(R.id.main_layout);
LayoutInflater layoutInflater = LayoutInflater.from(this);
// 将 null 改为 mainLayout,相当于 inflate(R.layout.button_layout, mainLayout, true)
// 也相当于 View view = layoutInflater.inflate(R.layout.button_layout, mianLayout, false);
// 加上 mainLayout.addView(view);
layoutInflater.inflate(R.layout.button_layout, mainLayout);
}

使用

那么问题来了,我们平时应该如何使用 inflate 呢?
我个人的看法是,在类似 adapter 的 getView 和 fragment 的 onCreateView 等方法中,应该使用 inflate(resource, root, false) 而不是 inflate(resource, null)。后者会使得布局最外层的 layout_width, layout_height 等属性失效。

另外值得注意的是自定义 view。自定义 view 的构造方法中(特指参考文中的组合控件),我们应该使用 inflate(resource, root) 或 inflate(resource, root, true)。
参考Android自定义View的实现方法,带你一步步深入了解View中的组合控件。
在你的应用中有自定义的导航栏,你想在多处使用,那你就可以自定义一个导航栏。
但是这样做,有一个不好的地方。你继承的是 FrameLayout, 然后通过 inflate 返回 LinearLayout。这样,外层的 FrameLayout 就基本没什么用。
正确的做法应该是,直接继承 LinearLayout, 然后读取的布局文件以 merge 作为根布局,可以参考Creating custom and compound Views in Android - Tutorial

题外话

写了才知道写博客真的挺难的,我深刻感受到了自己的词不达意。
上面参考的两篇文章都来自郭霖大神的博客。该博客有大量干货,而且写得浅显易懂。
其实参数 root 起到的作用是为 view 添加 LayoutParams, 而这 LayoutParams 会在 addView(view) 中发挥作用,大家可以参照源码,但直观来讲就是使最外层布局的相关 layout 属性生效。