它是Android SDK中的一个常见用法,但或许你会惊奇的发现,这里有一些LayoutInflater的错误使用方式,或许你的应用就是这些错误中的一个。如果你曾在Android应用程序中写过像下面代码使用LayoutInflater的方式:
inflater.inflate(R.layout.my_layout, null);
请继续阅读,因为你正在犯错误,接下来我将为你解释这个是为什么。
了解LayoutInflater
首先让我们来看看LayoutInflater是如何工作的,inflate()方法有两个可用版本提供给标准的应用程序。
inflate(int resource, ViewGroup root)
inflate(int resource, ViewGroup root, boolean attachToRoot)
第一个参数指向要被图形化的布局资源文件。第二个参数是图形化资源要依附的根级视图层。第三个参数的出现,代表图形化后的视图对象是否要提供给根级视图。
最后的两个参数会让我产生一些迷惑。这两个参数的方法LayoutInflater将自动尝试将图形化后的视图对象提供给根试图。然而android框架在这个地方有一个检查,防止开发者使用null来替代根级视图而造成应用程序的崩溃。
很多开发者使用向参数传递null值,这样做可以正确让图形化的视图不再依附顶级视图。在很多情况下甚至没有意识到 三个参数inflate()方法的存在。这种编码方式导致我们忽略了另一个根级视图的重要的功能...,但我们已经领先了一步。
来自Android Framework的例子
让我们来查看一下在Android框架内是如何希望你作为一个开发者去交互图形化视图部分的情况。
适配器是最常用的LayoutInflater方式,自定义ListView控件适配器时要覆盖getView()方法,它有如下的方法特征。
getView(int position, View convertView, ViewGroup parent)
Fragment当创建可见视图onCreateView()也经常使用图形化 注意它的方法特征。
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
你是否注意到无论何时android框架要图形化一个布局它都会传递给你父ViewGroup对象,它将最终被附属上吗?注意在大多数情况下(包括上面的两个例子),随后LayoutInflater 如果被允许自动尝试图形化后的视图依附到根视图,它将抛出一个异常。
所以你会猜为什么提供给我们ViewGroup,如果我们不使用它呢?原来在图形化过程中父级视图是非常重要的部分,因为它需要按顺序评估XML文件根级元素中LayoutParams的声明。不传任何东西就是告诉Android框架“抱歉,我不知道这个视图将要依附给那个父视图。”
问题是android:layout_xxx这些属性总被考虑到父视图的上下文中,结果却不知道父视图是谁,你声明在XML文件的根节点中所有的LayoutParams将被忽略,接下来您会问:“为什么Android框架忽略我自定义的部分?我最好查找一下原因并提交一个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>
我们要将ListView的行高设置成为一个固定高度,在这种情况下现在item的高为当前主体的高...似乎是合理的。
然而,当我们图形化这个布局的时候是错误的方法。
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = inflate(R.layout.item_row, null);
}
return convertView;
}
最终的结果看起来像这样:
发生了什么呢?恒定的高度我们设置了吗?这最终通常是对你所有的子视图设置固定高度,切换到根元素高度或者成为wrap_content,进而没有真正理解为什么被破坏了。(你或许会在这个过程中诅咒谷歌)
如果我们使用相同的布局替换图形化
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布局,但设置它作为Dialog视图。
这里的问题是AlertDialog.Builder支持一个自定义视图,但不能提供一个setView()实现,这带一个布局资源;所以你必须手动图形化XML。然后,因为结果将进入对话框,这里不需要暴露根视图(事实上,它也不存在),我们没有访问最终的父布局,所以我们不能使用它图形化。事实证明,这是不相干,因为AlertDialog将抹去LayoutParams很多布局并替换成match_parent.
所以下次你的手指将忍不住的向inflate()方法中填入null,你需要停止并且问问自己“我真的不知道这个view将结束吗?”
至少,你应当想到两个参数版本的inflate()是快捷忽略true作为第三个参数的方式。你不应该向参数传入null是快捷忽略false的方式。
译文链接:https://possiblemobile.com/2013/05/layout-inflation-as-intended/