关于LayoutInflater的一些知识

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/binbinqq86/article/details/79603641

转载请注明出处:http://blog.csdn.net/binbinqq86/article/details/79603641

LayoutInflater自从我们第一天开发程序,应该就会用到这个类,我第一次接触是在ListView的BaseAdapter,里面的getView方法会使用到,那也是我第一次见,只是简单的知道这样使用能返回一个View。后来需要动态添加布局的时候,也会采用这种方法,还有一种是View.inflate(Context context, @LayoutRes int resource, ViewGroup root),而跟进去可以看到这个方法最终也是调用LayoutInflater中的inflate方法,所以我们要了解他的真实参数和用法,只需要了解LayoutInflater中的inflate方法即可。我们先上个例子:

布局名:activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.tb.myapplication.MainActivity">

    <TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        android:textSize="30dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <LinearLayout
        android:id="@+id/ll"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:orientation="vertical"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/tv" />

</android.support.constraint.ConstraintLayout>

布局名:test.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:background="#aadd3344"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="this is a test~"
        android:textSize="30dp" />

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@mipmap/ic_launcher_round" />
</RelativeLayout>

上面两个布局,一个是主布局,里面的LinearLayout是我们要动态加入内容的parent,下面的布局是填充LinearLayout的,我们看第一种情况:

LinearLayout ll=findViewById(R.id.ll);
View view= LayoutInflater.from(this).inflate(R.layout.test,null,false);
Log.e(TAG, "onCreate: "+view.getClass().getCanonicalName());
ll.addView(view);

为了方便查看返回的view是什么,我们把布局的名称打印出来,看效果如下:
这里写图片描述

打印结果:
这里写图片描述

图中分析打印结果如期所料,把test的根布局直接加入到了LinearLayout中,这个视图结构我们也可以AndroidStudio自带的Layout Inspector看到:
这里写图片描述

画红线处可以证明上述猜想。但是有一个奇怪的问题来了,relativelayout我们明明写的是match_parent,为什么出来后跟预想结果不一样???这个我们留到后面讲。再来看另外一种情况:

View view= LayoutInflater.from(this).inflate(R.layout.test,null,true);
Log.e(TAG, "onCreate: "+view.getClass().getCanonicalName());
ll.addView(view);

结果跟上面是一样的,这个我就不贴图了,再看:

View view= LayoutInflater.from(this).inflate(R.layout.test,ll,false);
Log.e(TAG, "onCreate: "+view.getClass().getCanonicalName());
ll.addView(view);

这里写图片描述

终于得到我们想要的结果了,而且打印出来的依然是Relativelayout,再看最后一种情况:

扫描二维码关注公众号,回复: 3759901 查看本文章
View view= LayoutInflater.from(this).inflate(R.layout.test,ll,true);
Log.e(TAG, "onCreate: "+view.getClass().getCanonicalName());
ll.addView(view);

直接报错了:

这里写图片描述

我们可以看到打印出来的变成我们的parent——LinearLayout了,而且错误信息是child已经有一个父布局了,想要加入其它父布局必须先让之前的父布局移除掉自己的child。我们把最后一句addView去掉呢?运行后发现一切正常,而且打印出来的也是LinearLayout。这样所有情况我们都列举完了,下面带你一步一步去分析这其中缘由。

首先我们还是要去扒源码,上面说了View.inflate跟踪到最后也是调用LayoutInflater的inflate方法,而inflate方法有四个重载,分别是:
这里写图片描述

上面三个最终都是调用的最后一个,所以这里我们就分析最后一个就行了,上源码:

/**
     * Inflate a new view hierarchy from the specified XML node. Throws
     * {@link InflateException} if there is an error.
     * <p>
     * <em><strong>Important</strong></em>&nbsp;&nbsp;&nbsp;For performance
     * reasons, view inflation relies heavily on pre-processing of XML files
     * that is done at build time. Therefore, it is not currently possible to
     * use LayoutInflater with an XmlPullParser over a plain XML file at runtime.
     *
     * @param parser XML dom node containing the description of the view
     *        hierarchy.
     * @param root Optional view to be the parent of the generated hierarchy (if
     *        <em>attachToRoot</em> is true), or else simply an object that
     *        provides a set of LayoutParams values for root of the returned
     *        hierarchy (if <em>attachToRoot</em> is false.)
     * @param attachToRoot Whether the inflated hierarchy should be attached to
     *        the root parameter? If false, root is only used to create the
     *        correct subclass of LayoutParams for the root view in the XML.
     * @return The root View of the inflated hierarchy. If root was supplied and
     *         attachToRoot is true, this is root; otherwise it is the root of
     *         the inflated XML file.
     */
    public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        //.......

        View result = root;

            //......

                final String name = parser.getName();

                if (TAG_MERGE.equals(name)) {
                    if (root == null || !attachToRoot) {
                        throw new InflateException("<merge /> can be used only with a valid "
                                + "ViewGroup root and attachToRoot=true");
                    }

                    rInflate(parser, root, inflaterContext, attrs, false);
                } else {
                    // Temp is the root view that was found in the xml
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                    ViewGroup.LayoutParams params = null;

                    if (root != null) {
                        // Create layout params that match root, if supplied
                        params = root.generateLayoutParams(attrs);
                        if (!attachToRoot) {
                            // Set the layout params for temp if we are not
                            // attaching. (If we are, we use addView, below)
                            temp.setLayoutParams(params);
                        }
                    }
                    // Inflate all children under temp against its context.
                    rInflateChildren(parser, temp, attrs, true);

                    // We are supposed to attach all the views we found (int temp)
                    // to root. Do that now.
                    if (root != null && attachToRoot) {
                        root.addView(temp, params);
                    }

                    // Decide whether to return the root that was passed in or the
                    // top view found in xml.
                    if (root == null || !attachToRoot) {
                        result = temp;
                    }
                }

            //......

        return result;
    }

这里我只列举了基本的核心代码,不重要的就……省略了。首先从方法体的注释我们大致可以了解的比较清楚了,三个参数第一个不用多说,第二个就是根布局,这个参数其实是和第三个参数联合使用的,参考代码我们可以看到,分为下面三种情况处理:

第一种:root为空,attachToRoot为false

就是我们上面的第一种情况,既没有指定父布局,也没有跟父布局做关联,这种直接返回了当前的view,而这个view是没有设置过layoutParam的,所以后面我们addView的时候也没有去指定layoutParam,系统就给我们指定了默认的,就造成了上面的效果。跟进去addView源码我们可以看看:
这里写图片描述

generateDefaultLayoutParams()这个方法,实际运行过程中会根据具体的layout加载不同的类,而每种的默认情况都是不一样的:

LinearLayout:
这里写图片描述

RelativeLayout:

这里写图片描述

FrameLayout:

这里写图片描述

看到这些你应该恍然大悟了吧,也就解释了为什么上面第一种情况明明test.xml中根布局写的是match_parent,结果却是wrap_content的效果,同理,这里无论你写什么,最终都是wrap_content,因为根布局是RealtiveLayout。如果想要期望的结果,就要我们addView的时候去手动创建并指定LayoutParam了(比如下面这样)。

    ll.addView(view,new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT));
第二种:root为空,attachToRoot为true,这种其实没有任何作用,跟第一种是一样的
第三种:root不为空,attachToRoot为false

指定了父布局,但是并没有跟它attach到一起,这样就会把root的layoutParam设置给子布局,最后我们在addView的时候虽然没有指定layoutParam,但是其实在inflate的时候已经指定好了(参考代码43-53行),也就出现了我们想要的结果,不过如果你在addView的时候指定了,仍然以addView中的优先级最高。

第四种:root不为空,attachToRoot为true

为什么这样我们就会出现崩溃,看第60行代码你应该都明白了,这两个参数直接帮你把子布局加入到了根布局了,而且返回的result就是上面的root,即父布局,所以打印出来就是LinearLayout了,这个时候子布局已经有了父布局了,所以再进行addView当然就会出现上面的错误了,就好比这个孩子已经有了父亲,其他人肯定不能再强行拥有该孩子了,否则就是拐卖儿童了,系统肯定不允许,哈哈~

最后说一下merge,代码第32行我们可以看到对merge标签的处理,采用merge可以减少一层布局嵌套,这在app优化的时候经常会用到,当使用merge的时候,里面的子view的属性直接使用的就是外面的根布局的,而且必须是第四种情况,也就是说这个孩子必须是我的,谁也不能认领。看一下代码和最终效果以及嵌套层级:

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:background="#aadd3344"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="this is a test~"
        android:textSize="30dp" />

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@mipmap/ic_launcher_round" />
</merge>

这里写图片描述

这里写图片描述

可以看到merge仅仅是把里面的布局合并到LinearLayout中,里面设置的背景色并没有作用,而且里面的布局是父布局LinearLayout的上下排列,看层级结构也发现直接parent就是LinearLayout,起到了减少嵌套的效果。

OK,以上就是对LayoutInflater的一些讲解,希望大家都能更好的去使用这个类,不明白的小伙伴可以在下方留言,谢谢大家~

猜你喜欢

转载自blog.csdn.net/binbinqq86/article/details/79603641
今日推荐