Android自定义View之布局的产生器LayoutInflater

目录

一、R.layout.activity_main的布局如何加载的?

二、探究LayoutInflater的inflate()的使用场景

三、代码验证

四、总结

一、R.layout.activity_main的布局如何加载的?

做过Android开发的都知道,我们创建一个Activity,一般都是在onCreate()生命周期调用setContentView(R.layout.activity_main)来加载我们的xml布局文件,那么这个布局文件是如何加载的呢?跟踪源码可以发现:Activity的setContentView()--->抽象类Window的实现类PhoneWindow的setContentView()--->LayoutInflater的inflate();

由此我们都知道,Android中一个xml的布局的产生是由一个布局生成器inflate(鼓吹)生成出来的。

二、探究LayoutInflater的inflate()的使用场景

我们开发过程中涵盖以下四种情況:

inflater.inflate(R.layout.item, null, true);

inflater.inflate(R.layout.item, null, false);

inflater.inflate(R.layout.item, parent, true);

inflater.inflate(R.layout.item, parent, false);

通过源码可以得知:上面四个方法最终都会走到源码的:public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {}方法。

所以我们主要分析这个方法,这个方法的全部源码如下:

源代码分析:(分析之前先声明一下,下面所说的“传进来的参数”是指inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)这个最初的方法传进来的参数,这点要牢记)

上图中的1处,是获取到的传进来的第一个布局参数的相关布局属性;2处result就是方法要返回的布局,初始值为传进来的第二个参数,可能为null可能有值。

上图中的这个if判断是判断传进来的第一个布局参数的根布局是否为marge节点,是的话如果传进来的第二个参数为空或者第三个参数为false,就会拋异常,用过marge节点的同学就知道,这个节点不能单独使用,必须要嵌套在别的布局里面才能显示。

上图是重点,即判断第一个参数的根布局不是marge标签,就会走到这个else里面去。

图中1处:通过参数root(一开始传进来的第二个参数)、name(第一个参数的布局名字)、inflaterContext(上下文文本)、attrs(第一个布局的相关布局属性参数)来创建一个属于传进来的第一个布局参数的View对象。

图中2处:首先判断传进来的第二个参数如果不为空,会根据第一个参数的根布局属性attrs创建一个LayoutParams属性,別看这里是root.generateLayoutParams(),点进去这个方法会发现也是new LayoutParams()的。到这里就拿到了第一个参数的根布局的params了。接着当传进来的第二个参数root不为空且传进来的第三个参数为false,会把这个params设置给图1中生产的临时View对象。

图中3处:当传进来的第二个参数不会null且第三个参数为true的时候,会主动把图1生成的view和它的布局属性添加到root这个父布局中去。这个时候我们写代码就不需要手动在addView一遍了,否则会报错:java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.

图中4处:当传进来的第二个参数为null,直接把图1中生成的view返回了,没有给任何的params。当我们手动或者其他方式addview的时候,会使用系統默认的wrap_content(这是我猜的,看了一下addview一个参数的代码,确实如此,如下图:)。

图中4处:图中的if的条件是“或”的关系,或后面的条件:传进来的第三个参数为false的时候,直接把图1中生成的view返回了,并且带上了图中2处的params参数(图中2处最后一个if判断可以看出,已经把第一个参数即布局的最外层的属性params给了temp临时view对象了)。当我们手动或者其他方式addview的时候,就会使用使用上xml布局中设置的相关属性了。

三、代码验证

我的Activity的布局如下R.layout.activity_inflate:一个超简单的RelativeLayout 布局,我们在这理解为父布局(即inflate的第二个参数ViewGroup root)

/**
 *activity_inflate的布局
 **/
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/rl"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

</RelativeLayout>

布局准好了,下面进入正题。我要把layout_view.xml这个布局文件添加到我的activity的布局中,我的Activity的代码如下:

@Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_inflate);
        RelativeLayout relativeLayout = findViewById(R.id.rl);
        View view = LayoutInflater.from(this).inflate(R.layout.layout_view,relativeLayout,false);
        relativeLayout.addView(view);
    }
/**
 * layout_view.xml
 */
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="150dp"
    android:layout_height="150dp"
    android:background="#ff0000"><!--红色-->

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="开开心心每一天" />

</LinearLayout>

为了看到四个方法有什么不一样,依次修改代码,查看展示效果:

1、inflater.inflate(R.layout.layout_view, null, true);------>效果

2、inflater.inflate(R.layout.layout_view, null, false);------>效果

3、inflater.inflate(R.layout.layout_view, parent, true);------>效果

报错:意思是非法状态异常,指定的child已经有一个父容器了。你必须先用chile的父容器调用removeView()。

第三个参数:attachToRoot传入true代表layout文件填充的View会被直接添加addView()进parent,而传入false则代表创建的View会以其他方式被添加进parent。

解决方法:

删除rl.addView(view);这一行代码,因为源码里面已经addView()一次了;然后运行展示效果:

4、inflater.inflate(R.layout.layout_view, parent, false);

从案例中我们可以看到1、2这两种方式运行结果一样,第二个参数传null会使要添加的布局视图中根视图的宽高设置失效,在使用这种方式的时候会造成我们无形中多加一层布局,为了使其子view显示相应高度。在这里不推荐使用;
3这种方式我们在使用的时候一定要注意,它会使代表layout文件填充的View会被直接添加进parent,如果我们使用这种方式后,在执行addView()方法就会造成上面的错误。

4这种方式比较推荐,只是要我们手动addView(),或者其他代码方式添加view。

四、总结

如果root为null,attachToRoot将失去作用,设置任何值都没有意义。(图4处可以验证)

如果root不为null,attachToRoot设为true,则会给加载的布局文件指定一个父布局,即root,并会带上布局文件的LayoutParams。(图3可以验证)

如果root不为null,attachToRoot设为false,则会将布局文件最外层的所有layout属性进行设置,当该view被添加到父view当中时,这些layout属性会自动生效。(图2可以验证)

关于上面还有一些补充说明,如果root不为null,布局文件最外层的layout关于LayoutParams设置的属性和其他属性都会被保留下来,attachToRoot设为true,则会给加载的布局文件的指定一个父布局,我们不需要自己在addView,否则会报错;attachToRoot设为false,需要我们自己addView,root为null时,被加载的布局LayoutParams的属性会被改变,但是其它属性例如背景颜色什么的会被保留。

猜你喜欢

转载自blog.csdn.net/sunbinkang/article/details/112747822
今日推荐