@flyouting
2014-06-26T15:32:09.000000Z
字数 3127
阅读 4753
android
layout
inflation
Layout inflation是一个android中的术语,用于表达一个XML布局源文件被解析转变成一个View的层次结构对象。
这在AndroidSDK中是常见的做法,但你也许会惊讶地发现,有一个错误的方式去使用LayoutInflater,你的应用也许就是其中之一。如果你在你的Android应用程序曾经写过类似下面的代码:
inflater.inflate(R.layout.my_layout, null);
请继续读下去,因为你做错了,我想向你解释为什么。
首先我们来看一看 LayoutInflater
是怎么工作的。这有两种inflate()
方法可用的方式:
inflate(int resource, ViewGroup root)
inflate(int resource, ViewGroup root, boolean attachToRoot)
第一个参数指向你想要的图形化的布局资源。第二个参数是你图形化资源后所要依附的根view。第三个参数是否直接将图形化后的view添加进根view。
后边这两个参数,可能会导致一些混乱。在两个参数的方法中,LayoutInflater
将自动尝试将view附加到根view上。然而,Android框架在这有个检查,如果root参数你传的null,那么它就会绕过这个操作以防止应用崩溃。
很多开发者采用这种方式意思是这是种正确的方式,即root参数传null,去禁止在图形化过程中自动附加上view。很多情况下,他们都没有意识到有三个参数的inflate()方法存在,这种方式也导致了我们禁掉了根view的一些重要功能。
我们来检查一下在Android中,框架希望我们作为开发者inflate部分视图的情况:
适配器中使用LayoutInflater的最常见的方式就是自定义Adapter,重写getView方法:
getView(int position, View convertView, ViewGroup parent)
Fragment也经常通过onCreateView()方法使用LayoutInflater来创建view:
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
注意到没,每当系统想让你添加一个布局,都会传给你一个父viewgroup,而且,在大多数情况下(包括以上两个例子),如果允许LayoutInflater自动将view添加进父视图,就会抛出一个异常。
那么,如果我们不想直接添加上去,为什么又要我们给出这个viewgroup呢?原来,父view是inflate过程中很重要的一部分,因为我们需要取得XML中根元素的LayoutParams。这里传null意思就好比我也不知道这个view要添加到哪。
这么做的问题是 android:layout_xxx这些属性是在父view的context中获取到。因此,如果没有具体的父view,在XML根元素声明的LayoutParams都会被抛弃。然后你会困惑为啥系统忽略了我设置的这些属性。
没有 LayoutParams,生成view添加进viewgroup时就会依照默认属性。掩盖了你所设置的真正属性。
你要说你从来没见过这种情况,那看如下代码:
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>
我们想把item的高度设置成一个特定值,在这种情况下,优先选定为当前主题,看起来是合理的。
然而当我们以错误的方式生成时:
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = inflate(R.layout.item_row, null);
}
return convertView;
}
我们得到的结果如此:
我们设置的行高呢?这就是常见的我们设置了高度,却最终是自适应的情况。
如果我们换一种实现方式:
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = inflate(R.layout.item_row, parent, false);
}
return convertView;
}
我们就会得到我们希望的结果:
当然有些情况下你可以发现parent确实是个null,但是这种情况很少,其中一例是,初始化一个自定义的布局,然后添加进一个AlertDialog,看一下如下代码:
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支持一个自定义view,但是没有提供一个支持传入布局资源ID的setView()方法,所以,我们需要先手动初始化我们的布局XML,然后,因为初始化后的对象是要被添加进Dialog,而不是其他的什么根view(事实上也不存在),我们没有进入父一级布局,所以实例化过程中我们不能使用它,但这无关紧要,因为AlertDialog会清除布局的任何LayoutParams,且用match_parent取代。
所以,当你下次又想直接在inflate()方法中传null时,先问下自己,你真的知道这个view最后如何展示么。
最后,你需要记住两个参数的inflate()方法省略的参数代表true,而不是你所认为的代表的false。