Layout Inflation不能这么用

Layout inflation在Android上下文环境下转换XML文件成View结构对象的时候须要用到。java

LayoutInflater这个对象在Android的SDK中很常见,可是你绝对没想到居然可以找到一个使用误区。说不定你的App里就是这么用的!若是你在写APP的时候像以下代码同样使用LayoutInflater的话:android

inflater.inflate(R.layout.my_layout, null);

请你继续读完这篇文章,稍后我会解释为何这样作不对。框架

认识LayoutInflater

首先看一下LayoutInflater的工做原理,有两个重载的版本可使用:函数

inflate(int resource, ViewGroup root) 和 inflate(int resource, ViewGroup root, boolean attachToRoot)布局

第一个参数指出要载入的布局文件资源,第二个参数指出视图结构中载入的布局将要放入的根视图。若是有第三个参数,那么它用来决定是否把载入后的视图绑定到给出的根视图中。ui

最后两个参数可能会致使一些问题。若是使用两个参数的版本,Layoutinflater会自动尝试把载入的视图绑定到给定的根视图对象中。可是,若是你传递null,系统就不会尝试绑定操做了,不然应用程序就崩溃了。spa

不少开发者会这样作,认为传递null做为根视图就能够禁用绑定操做了。不少时候不少开发者甚至不知道还有三个参数的Layoutinflater版本的存在,若是这么作的话,也会同时禁用了根视图的一个很重要的函数……可是以前我没有研究过。code

框架中的示例

如今咱们来仔细看看Android框架关于动态载入布局的场景。orm

Adapter是最经常使用的场景,咱们常常须要使用LayoutInflater来自定义ListView(经过重写getView()方法),具体的方法签名是这样的:xml

getView(int position, View convertView, ViewGroup parent)

Fragment也会用到inflation操做,经过onCreateView()方法建立view的时候会用到。这个方法的签名是这样的:

onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)

不知你有没有注意到这一点,每次Framework须要你去载入一个布局文件时,都会传入一个ViewGroup参数(最后须要绑定到的根视图),若是Layoutinflater设为自动绑定到根视图的话,会抛出一个异常。

因此你想一想看,若是我作绑定操做的话,为何要给你一个ViewGroup参数呢?事实证实父视图在这个inflation操做过程当中是很重要的,它会计算被载入的XML在根元素中的LayoutParams,若是传入null话,就等因而告诉框架“我不知道载入的View要放到哪一个父视图中”。

问题在于,android:layout_xxx属性会在父视图对象中被从新计算,结果就是全部你定义的LayoutParams都会被忽略掉(由于没有已知的父视图对象)。而后你就纳闷“为何框架会忽略掉我本身定义的布局属性呢?仍是去StackOverFlow上看看,提一个bug吧”。

若是没有设置LayoutParams,那么最终ViewGroup也会给你生成一个默认的属性,幸运的话(不少时候),这些默认的设置正好和你在XML文件中定义的同样……因此你就察觉不到其实已经出现问题了。

应用案例

你敢说你没有在应用中碰到过这样的场景吗?看看下面的代码,为Listview简单地载入一个布局文件:

R.layout.item_row

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="?android:attr/listPreferredItemHeight"
    android:gravity="center_vertical"
    android:orientation="horizontal">
    <TextView
        android:id="@+id/text1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:paddingRight="15dp"
        android:text="Text1" />
    <TextView
        android:id="@+id/text2"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:text="Text2" />
</LinearLayout>

这里咱们想把高度设置为固定高度,上面把它设为当前主题下的推荐高度……看似很合理。

可是,当咱们这样载入布局文件的时候,就不对了

public View getView(int position, View convertView, ViewGroup parent) {
    if (convertView == null) {
        convertView = inflate(R.layout.item_row, null);
    }
  
    return convertView;
}

而后结果就变成这样了:

为何设定的固定高度不起做用?这是由于你没有把全部子View的高都设为固定高度,只须要把根视图的高设置成wrap_content就能够了。不须要知道为何会这样(你能够吐槽一下Google为何这么处理!)。

而若是这样载入布局的话就没有问题:

public View getView(int position, View convertView, ViewGroup parent) {
    if (convertView == null) {
        convertView = inflate(R.layout.item_row, parent, false);
    }
  
    return convertView;
}

这样咱们就获得了想要的结果:

任何规则都有例外

固然,也有须要在载入布局的时候指定null做为父布局对象,但这种状况很是少。一个典型的例子就是为AlertDialog中载入一个自定义布局。看看下面的例子,使用和上面同样的XML布局文件来做为对话框的布局:

AlertDialog.Builder builder = new AlertDialog.Builder(context);
View content = LayoutInflater.from(context).inflate(R.layout.item_row, null);
  
builder.setTitle("My Dialog");
builder.setView(content);
builder.setPositiveButton("OK", null);
builder.show();

这里的问题就是,AlertDialog.Builder支持自定义布局,可是setView()方法不提供带有布局文件做为参数的版本,因此只能先手动载入XML布局文件。因为最终会进入到对话框里面,不会接触到根布局(事实上这时候尚未根布局),因此咱们也操做不了布局文件的最终父视图对象,固然也就不能用于载入使用了。事实证实,这些都是可有可无的,由于AlertDialog会擦除布局上的全部Layoutparams而后替换为match_parent

因此,下次使用inflate()函数时,若是还想输入null应该停下来想想“我真的不知道它该放到哪里吗?”

最后,你应该想一想两个参数的inflate()版本做为一个便捷的使用方式,能够忽略第三个参数(默认为true),可是不要想着为了方便而传递一个null却忽略了第三个参数会默认是false

相关文章
相关标签/搜索