一般学过一段时间的 Android 的开发者都知道 LayoutInflater 可以用来动态把 layout 布局文件生成 View。下面我结合实例与源码一步一步分析 LayoutInflater 的使用方法和内部原理,首先从如何获得 LayoutInflater 的实例开始。
获得 LayoutInflater 实例
我们没法通过构造函数实例化 LayoutInflater 对象,因为构造函数是 protected 的,一般通过下面两种方法得到一个标准的 LayoutInflater 实例。
第一种方式
|
|
第二种方式
|
|
LayoutInflater 有个静态方法对第二种方式做了一下封装,也是我经常使用的方式,源码如下:
|
|
inflate 加载布局文件
得到 LayoutInflater 实例后就可以调用 inflate 方法加载布局文件了,一般使用其提供的下面两个方法:
|
|
可以看出其实第一个方法只是第二个方法的简化版,其实是一样的,我写了一个 demo 测试一下:
text.xml :
|
|
代码 :
|
|
运行结果如下:
根据方法的说明和测试结果可以知道:
(1) 在指定了不为 null 的 root 参数时,若 attachToRoot 参数为 true,root 会作为已加载布局的 parent,而且方法返回的也是 root 而不是已加载布局的根视图; 若 attachToRoot 参数为 false,root 参数只是用来给已加载布局的根视图创建相应的 LayoutParams,方法返回的还是已加载布局的根视图。
(2) 在 root 参数为 null 时,已加载布局的根视图的 layout 属性会失效,如上面的 demo 中 textView3 的宽度不是 600px 而是 match_parent。相信有些开发也会遇到同样的问题,怎么根视图的 layout 属性无效了呢,而其他的非 layout 属性(如 padding)还是生效,看起来有点莫名其秒,而且非根视图的 layout 属性还是有效的。
现在带着疑问看看 LayoutInflater 在 inflate 方法中的return inflate(parser, root, attachToRoot)
中具体是怎么加载布局文件的。
inflate 方法源码解析
因为 inflate(parser, root, attachToRoot) 的过程比较复杂,所以我直接在代码中加入了解析说明,在一行的开头出现中文注释的就是我加的说明,源码如下:
|
|
|
|
经过上面的源码解析后,大家已经对 LayoutInflater 加载布局文件的过程已经大致了解了,但是可能还是不清楚为什么 root 参数为 null 时,就不去读取布局文件中的 layout 属性呢。下面我讲述一下个人的看法,大家有什么看法欢迎在文章下面评论:
首先大家要清楚 LayoutParams 的定义,LayoutParams 是视图用来告诉它们的父视图它们想在父视图中如何被布局的,注意其实 LayoutParams 真正是给父视图如何排列布局它的子视图的,也就是每个视图的 LayoutParams 其实是跟父视图有很大关系的。LayoutParams 在 Android的 容器视图中都有不同的子类,LinearLayout 下有 LinearLayout.LayoutParams,继承自 ViewGroup.MarginLayoutParams,还新增了 layout_weight 和 layout_gravity 两个 layout 属性。RelativeLayout.Params 在 ViewGroup.MarginLayoutParams 基础上加了 layout_toLeftOf、layout_toRightOf、layout_alignParentTop 等属性,FrameLayout.LayoutParams 在 ViewGroup.MarginLayoutParams 基础上加了 layout_gravity 属性。A 容器视图的 layout 属性无法被 B 容器视图解析,layout 属性只有在确定其容器视图的情况下才是有意义的,所以在 inflate 方法中,root 为 null 时,不知道根视图将来会添加到什么容器,所以也不知道生成什么 LayoutParams,可能有人会说 layout_width 和 layout_height 两个 layout 属性是所有 LayoutParams 共有的,为什么这两个属性也不读取呢。因为 Android 中的容器视图在 addview 的时候会调用 checkLayoutParams 检查 LayoutParams 与自己的 LayoutParams 是否相符,如果不符会调用 generateLayoutParams 生成默认的布局属性。例如根视图设置 FrameLayout.LayoutParams,但是之后被添加到 LinearLayout 中,checkLayoutParams 为 false,然后还是用的 LinearLayout 默认的布局属性。
至于如何解决布局文件中的属性失效的问题,可以调用 inflate(resourceId, parent, false) 方法,相信有人在使用 ListView 的 Adapter 过程中,发现 getView 方法中加载的布局文件的 margin 属性失效了,这时就可以用上面的方法解决了。如果没法确定 parent 视图,那就只能在外面再包一层 FrameLayout 了,当然个人不推荐这么做。
blink 标签
在上面解析源码的过程发现有个 blink 标签,大家可能基本没听过,平常也没有用过。其实这是 Android 的一个很有意思的东西,可以让它的 child view 闪烁。下面我们研究下源码,我分别截取关键部分:
|
|
根据上面源码可以知道我们可以在 xml 中添加 作为一个视图,还可以在里面添加子视图,在 blink 标签内的视图,每隔 500ms 就会不显示,所以在界面上看就是一直闪烁了。这个东西个人感觉是 Android 中的一个彩蛋,还是挺有意思的,大家有空可以尝试一下。
关于 LayoutInflater 的原理我就讲到这里,大家有什么意见或问题欢迎在文章下面评论^_^