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

LayoutInflater这个对象在Android的SDK中很常见,但是你绝对没想到竟然能够找到一个使用误区。说不定你的App里就是这么用的!如果你在写APP的时候像如下代码一样使用LayoutInflater的话:

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

请你继续读完这篇文章,稍后我会解释为什么这样做不对。

认识LayoutInflater

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

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

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

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

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

框架中的示例

现在我们来仔细看看Android框架关于动态载入布局的场景。

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

1
getView(int position, View convertView, ViewGroup parent)

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

1
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<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>

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

但是,当我们这样载入布局文件的时候,就不对了:

1
2
3
4
5
6
7
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为什么这么处理!)。

而如果这样载入布局的话就没有问题:

1
2
3
4
5
6
7
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布局文件来作为对话框的布局:

1
2
3
4
5
6
7
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

Layout Inflation :Unconditional layout, inflation from view adapter的更多相关文章

  1. iOS UIKit:Auto Layout

    @import url(http://i.cnblogs.com/Load.ashx?type=style&file=SyntaxHighlighter.css); @import url(/ ...

  2. LayoutInflater作用是将layout的xml布局文件实例化为View类对象。

    获取LayoutInflater的方法有如下三种: LayoutInflater inflater=(LayoutInflater)context.getSystemService(Context.L ...

  3. 自己定义View Layout过程 - 最易懂的自己定义View原理系列(3)

    前言 自己定义View是Android开发人员必须了解的基础 网上有大量关于自己定义View原理的文章.但存在一些问题:内容不全.思路不清晰.无源代码分析.简单问题复杂化等等 今天,我将全面总结自己定 ...

  4. Android 性能优化(4)Optimizing Layout Hierarchies:用Hierarchy Viewer和Layoutopt优化布局

    Optimizing Layout Hierarchies This lesson teaches you to Inspect Your Layout Revise Your Layout Use ...

  5. 自定义控件(视图)2期笔记05:自定义控件之继承自View(滑动开关)

    1.  开关按钮点击效果,如下: 2. 继承已有View实现自定义View 3. 下面通过一个案例实现滑动开关的案例: (1)新建一个新的Android工程,命名为" 开关按钮", ...

  6. RecyclerView Bug:IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter的解决方案(转)

    转自:RecyclerView Bug:IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter的解 ...

  7. Auto Layout Guide----(一)-----Understanding Auto Layout

    Understanding Auto Layout 理解自动布局 Auto Layout dynamically calculates the size and position of all the ...

  8. myeclipse报错:Could not create the view: An unexpected exception was thrown.

    打开server窗口,发现显示:Could not create the view: An unexpected exception was thrown. 此处解决方法: 关闭myeclipse 删 ...

  9. MyEclipse无法创建servers视图:Could not create the view: An unexpected exception was thrown

    今天上班刚打开MyEclipse,就发现servers视图无法打开了,显示:Could not create the view: An unexpected exception was thrown. ...

随机推荐

  1. JS的浅拷贝与深拷贝

    浅拷贝 //这样的拷贝有一个问题.那就是,如果父对象的属性等于数组或另一个对象,//那么实际上,子对象获得的只是一个内存地址,而不是真正拷贝,因此存在父对象被篡改的可能. function exten ...

  2. 性能测试二十四:环境部署之Redis多实例部署

    由于redis服务端是单线程实现的,因此只能占用CPU的单核,为了充分利用CPU资源,可以在一台服务器上同时启动多个redis-server实例 首先删除之前的rdb.aof文件 注释掉3个save ...

  3. MVC开发中的常见错误-06-"无法在发送 HTTP 标头之后进行重定向。"

    通过监视可以看到: 原来是跳转到登录页面后,登录页面中又发送了一个GeMneuItems的请求,用于加载页面图片

  4. js读取xml文件

    假设我们现在要读取下面的 info.xml 文件 <?xml version="1.0" encoding="gb2312"?> <root& ...

  5. 如何重置mate的面板到初始化时的默认设置?

    在你的任何终端中敲入如下命令: gsettings reset-recursively org.mate.panel

  6. LICEcap方便快捷制作gif图片的工具

    总是看见别人的博客里面动态的小图片,是不是有种冲动自己也想搞,但是就是不知道咋搞,这里简单介绍一款很实用的制作gif的软件. LICEcap的网址:http://www.cockos.com/lice ...

  7. AOJ 2170 Marked Ancestor[并查集][离线]

    题意: 给你一颗N个节点的树,节点编号1到N.1总是节点的根.现在有两种操作: M v: 标记节点v Q v: 求出离v最近的标记的相邻节点.根节点一开始就标记好了. 现在给一系列操作,求出所有Q操作 ...

  8. 【AtCoder】ARC083

    C - Sugar Water 计算一下可以达到水是多少,可以到达的糖是多少 枚举水,然后加最多能加的糖,是\(min(F - i *100,E * 100)\),计算密度,和前一个比较就行 #inc ...

  9. Storm消息容错机制(ack-fail机制)

    storm消息容错机制(ack-fail) 1.介绍 在storm中,可靠的信息处理机制是从spout开始的. 一个提供了可靠的处理机制的spout需要记录他发射出去的tuple,当下游bolt处理t ...

  10. jquery数组(sort() 排序)

    HTML: <h3>字符串数组排序前</h3> <div id="show5"></div> <h3>排序后</h ...