//1、通过 LayoutInflater 的静态方法 from 获取val layoutInflater: LayoutInflater = LayoutInflater.from(this)//2、通过系统服务 getSystemService 方法获取val layoutInflater: LayoutInflater = getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater//3、如果是在 Activity 或 Fragment 可直接获取到实例layoutInflater //相当于调用 getLayoutInflater()复制代码
实际上,1 是 2 的简单写法,只是 Android 给我们做了一下封装拿到 LayoutInflater 实例后,我们就可以调用它的 inflate 系列方法了,这几个方法是本篇文章的一个重点,如下:image-20210622163719911从 Xml 布局到创建 View 对象,这几个方法扮演着至关重要的作用,其中我们用的最多就是第一个和第三个重载方法,现在我们就来使用一下二、例子1、创建一个新项目,MainActivity 对应的布局如下:<?xml version="1.0" encoding="utf-8"?><androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/cons_main" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"/>复制代码
2、创建一个新的布局取名 item_main.xml,如下图:image-202106221746208783、修改 MainActivity 中的代码class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val consMain = findViewById<ConstraintLayout>(R.id.cons_main) val itemMain = layoutInflater.inflate(R.layout.item_main, null) consMain.addView(itemMain) }}复制代码
上述代码我们使用了两个参数的 inflate 重载方法,第二个参数 root 传了一个 null ,然后把当前布局添加到 Activity 中,运行看下效果:啥情况?怎么和预想的不一样呢?我的背景颜色怎么不见了?把这个问题 1 先记着接下来,我们修改一下 MainActivity 中的代码,如下:val itemMain = layoutInflater.inflate(R.layout.item_main, consMain)//等同下面这行代码val itemMain = layoutInflater.inflate(R.layout.item_main, consMain,true)复制代码
实际上上面这句代码就相当于调用了三个参数的重载方法,且第三个参数为 true,我们看下它两个参数的源码:public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) { return inflate(resource, root, root != null);}复制代码
现在在运行看下结果:报错了,提示我们当前 child 已经有了一个父 View,你必须先调用父 View 的 removeView 方法移除当前 child 才行是不是疑问更多了呢?把这个问题 2 也先记着我们在修改一下 MainActivity 中的代码,如下:val itemMain = layoutInflater.inflate(R.layout.item_main, consMain,false)复制代码
在运行看下结果:image-20210622190835239嗯,现在达到了我们预期的效果现在回到上面那两个问题,分析发现是 LayoutInflater inflate 方法传了不同的参数导致的,那这些参数到底有什么玄乎的地方呢?接下来跟着我的脚步分析下源码,或许你就豁然开朗了三、LayoutInflater inflate 系列方法源码分析在分析源码之前,我们需要明白一些基础知识:我们一般都会使用 layout_width 和 layout_height 来设置 View 的大小,实际上是要满足一个条件,那就是这个 View 必须存在于一个容器或布局中,否则没有意义,之后如果将 layout_width 设置成 match_parent 表示让 View 的宽度填充满布局,如果设置成 wrap_content 表示让 View 的宽度刚好可以包含其内容,如果设置成具体的数值则 View 的宽度会变成相应的数值这也是为什么这两个属性叫作 layout_width 和layout_height,而不是 width 和 height 明白了上面这些知识,我们继续往下看实际上,我们调用 LayoutInflater inflate 系列方法,最终都会走到上述截图的第 4 个重载方法,看下它的源码,仅贴出关键代码:public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) { synchronized (mConstructorArgs) { //... //获取布局 Xml 里面的属性集合 AttributeSet attrs = Xml.asAttributeSet(parser); // 将传入的 root 赋值 给 result View result = root; // 创建根 View 赋值给 temp final View temp = createViewFromTag(root, name, inflaterContext, attrs); ViewGroup.LayoutParams params = null; if (root != null) { //... //如果传入的 root 不为空,通过 root 和布局属性生成布局参数 params = root.generateLayoutParams(attrs); if (!attachToRoot) { // 如果传入的 attachToRoot 为 false 则给当前创建的根 View 设置布局参数 temp.setLayoutParams(params); } } //递归创建子 View 并添加到父布局中 rInflateChildren(parser, temp, attrs, true); if (root != null && attachToRoot) { //如果 root 不为空且 attachToRoot 为 true,添加当前创建的根 View 到 root root.addView(temp, params); } if (root == null || !attachToRoot) { //如果 root 为空或者 attachToRoot 为 false, 将当前创建的根 View 赋值给 result result = temp; } //... //返回当前 result return result; }}复制代码
上述代码我们可以得到一些结论:1、如果传入的 root 不为 null 且 attachToRoot 为 false,此时会给 Xml 布局生成的根 View 设置布局参数注意:Xml 布局生成的根 View 并没有被添加到任何其他 View 中,此时根 View 的布局属性不会生效,但是我们给它设置了布局参数,那么它就会生效,只是没有被添加到任何其他 View 中2、如果传入的 root 不为 null 且 attachToRoot 为 true,此时会将 Xml 布局生成的根 View 通过 addView 方法携带布局参数添加到 root 中注意:此时 Xml 布局生成的根 View 已经被添加到其他 View 中,注意避免重复添加而报错3、如果传入的 root 为 null ,此时会将 Xml 布局生成的根 View 对象直接返回注意:此时 Xml 布局生成的根 View 既没有被添加到其他 View 中,也没有设置布局参数,那么它的布局参数将会失效明白了上面这些知识点,我们在看下为啥为会出现之前那些问题四、问题分析1、问题 1上述问题 1 实际上我们是调用了 LayoutInflater 两个参数的 inflate 重载方法:inflate(@LayoutRes int resource, @Nullable ViewGroup root)复制代码
传入的实参: resouce 传入了一个 Xml 布局,root 传入了 null根据我们上面源码得到的结论,当传入的 root 为 null ,此时会将 Xml 布局生成的根 View 对象直接返回那么此时这个布局根 View 不在任何 View 中,因此它的布局属性失效了,但是 TextView 在一个布局中,它的布局属性会生效,因此就出现了上述截图中的效果2、问题 2上述问题 2 我们调用的还是 LayoutInflater 两个参数的构造方法传入的实参: resouce 传入了一个 Xml 布局,root 传入了 consMain实际又会调用 LayoutInflater 三个参数的 inflate 重载方法:inflate(@LayoutRes int resource, @Nullable ViewGroup root,boolean attachToRoot)复制代码
此时传入实参变为:resouce 传入了一个 Xml 布局,root 传入了 consMain,attachToRoot 传入了 true根据我们上面源码得到的结论:当传入的 root 不为 null 且 attachToRoot 为 true,此时会将 Xml 布局生成的根 View 通过 addView 方法携带布局参数添加到 root 中此时我们在 MainActivity 中又重复调用了 addView 方法,因此就报那个错了如果想不报错,把 MainActivity 中的那行 addView 去掉就可以了3、预期效果上述预期效果,我们调用的是 LayoutInflater 三个参数的 inflate 重载方法传入的实参:resouce 传入了一个 Xml 布局,root 传入了 consMain,attachToRoot 传入了 false根据我们上面源码得到的结论:当传入的 root 不为 null 且 attachToRoot 为 false,此时会给 Xml 布局生成的根 View 对象设置布局参数此时根 View 的布局属性会生效,只不过没有被添加到任何 View 中,而又因为 MainActivity 中调用了 addView 方法,把当前根 View 添加了进去,所以达到了我们预期的效果到这里,你是否明白了 LayoutInflater inflate 方法的应用了呢?如果还有疑问,欢迎评论区给我提问,我们一起讨论五、为啥 Activity 中布局根 View 的布局属性会生效?看下面这张图:注意:Android 版本号和应用主题会影响到 Activity 页面组成,这里以常见页面为例image-20210622210219600我们的页面中有一个顶级 View 叫 DecorView,DecorView 中包含一个竖直方向的 LinearLayout,LinearLayout 由两部分组成,第一部分是标题栏,第二部分是内容栏,内容栏是一个FrameLayout,我们在 Activity 中调用 setContentView 就是将 View 添加到这个FrameLayout 中看到这里你应该也明白了:Activity 中布局根 View 的布局属性之所以能生效,是因为 Android 会自动在布局文件的最外层再嵌套一个FrameLayout六、总结本篇文章重点内容:1、 LayoutInflater inflate 方法参数的应用,记住下面这个规律:当传入的 root 不为 null 且 attachToRoot 为 false,此时会给 Xml 布局生成的根 View 设置布局参数当传入的 root 不为 null 且 attachToRoot 为 true,此时会将 Xml 布局生成的根 View 通过 addView 方法携带布局参数添加到 root 中当传入的 root 为 null ,此时会将 Xml 布局生成的根 View 对象直接返回2、Activity 中布局根 View 的布局属性会生效是因为 Android 会自动在布局文件的最外层再嵌套一个 FrameLayout好了,本篇文章到这里就结束了,希望能给你带来帮助 感谢你阅读这篇文章(图片来源网络,侵删)
0 评论