一篇永远搞定LayoutInflater源码

目录

一、关于LayoutInflater

二、关于inflate()

三、关于inflate()的实验

四、源码分析


一、关于LayoutInflater

        三种获取方式

       方式一、Activity.getLayoutInflater()

       方式二、LayoutInflater.from(Context)

       方式三、Context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)

        总结:activity通过通过调用getLayoutInflater(),实际上到PhoneWindow里间接调用LayoutInflater.from(Context),最终会调用到方式三,如下图

二、关于inflate()

        四种重载

方式一:View inflate(@LayoutRes int resource, @Nullable ViewGroup root)
方式二:View inflate(XmlPullParser parser, @Nullable ViewGroup root)
方式三:View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)
方式四:View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot)

         总结:注意它们只是参数的不同,最后都是调用到方式四。

三、关于inflate()的实验

我们研究五种情形:

inflater.inflate(R.layout.item,null)
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);

        先看布局activity_main.xml,也就是作为inflate的第二个参数

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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:id="@+id/root"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

</RelativeLayout>

        然后再建一个布局item.xml,作为inflate的第一个参数

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="200dp"
    android:layout_height="200dp"
    android:background="#FFEB3B">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="人生易老天难老,岁岁重阳" />
</LinearLayout>

        我要把item.xml这个布局文件添加到我的activity的布局中,为了看到五种情形有什么不一样,我依次替换圈起来的代码,查看展示效果 

        

        情形一:inflater.inflate(R.layout.item,null)

        情形二:inflater.inflate(R.layout.item, null, true);

                

        情形三:inflater.inflate(R.layout.item, null, false);

        情形四:inflater.inflate(R.layout.item, parent, true);

         使用该方法时rl.addView(view);代码报错,错误异常如下

     出错的原因,这是因为在源码里面执行了下面这句,这时候temp已经有父布局了,所以你在MainActivity里面又执行了parent.addView(view)的时候会在addView里面抛出异常,所以inflate()的第三个参数要么传入false,这样源码里面就不会自动addView,你可以在外面手动addView。要么传入true,但是这样你就不要在外面再手动addView了,因为会抛异常

   解决方法

        删除parent.addView(view);这一行代码,然后运行展示效果如下

        情形五:inflater.inflate(R.layout.item, parent, false);

         

       总结:只有inflate的第二个参数为null,xml的根布局的宽高无效;

                 如果inflate的第二个参数不为null,第三个参数为true,则在外面不要再给parent布局addView了;如果第三个参数为false,则需要手动给parent布局addView

               标准用法是情形五                                                                  

四、源码分析


    public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
            // 获取上下文对象,即LayoutInflater.from传入的Context.
            final Context inflaterContext = mContext;
            // 根据parser构建XmlPullAttributes.
            final AttributeSet attrs = Xml.asAttributeSet(parser);
            // 保存之前的Context对象.
            Context lastContext = (Context) mConstructorArgs[0];
            // 保存之前的Context对象.
            mConstructorArgs[0] = inflaterContext;
            // 注意,默认返回的是父布局root.
            View result = root;

            try {
                // Look for the root node.
                //找到根节点,即查找xml的开始标签.
                int type;
                while ((type = parser.next()) != XmlPullParser.START_TAG &&
                        type != XmlPullParser.END_DOCUMENT) {
                    // Empty
                }
                // 如果没有找到有效的开始标签,则抛出InflateException异常.
                if (type != XmlPullParser.START_TAG) {
                    throw new InflateException(parser.getPositionDescription()
                            + ": No start tag found!");
                }
                //获得标签的名字
                final String name = parser.getName();

                if (DEBUG) {
                    System.out.println("**************************");
                    System.out.println("Creating root view: "
                            + name);
                    System.out.println("**************************");
                }
                //如果标签是merge,则root不能为空,且attachToRoot不能为false,因为merge标签是要合并到根布局的
                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");
                    }
                    //解析内部View,传入的父布局为root
                    rInflate(parser, root, inflaterContext, attrs, false);
                } else {
                    // Temp is the root view that was found in the xml
                    //实例化xml中的根布局
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);
                    //设置LayoutParams为null,这个很关键,很多布局参数跟实际有出入的问题都是它导致的,xml里的LayoutParams刚解析出默认为null
                    ViewGroup.LayoutParams params = null;
                    //如果root布局(非XML)存在
                    if (root != null) {
                        if (DEBUG) {
                            System.out.println("Creating params from root: " +
                                    root);
                        }
                        // Create layout params that match root, if supplied
                        //则根据XML根布局的属性生成LayoutParams。也就是说加入root为null,XML里的根布局的View布局参数一定为null
                        params = root.generateLayoutParams(attrs);
                        //attachToRoot为false则将LayoutParams设置给XML里的根布局
                        if (!attachToRoot) {
                            //情形五

                            // Set the layout params for temp if we are not
                            // attaching. (If we are, we use addView, below)
                             如果实例化的XML根布局不需要添加到root中,则直接将根据root生成的params参数设置
                            // 给它即可.
                            
                            temp.setLayoutParams(params);
                        }
                    }

                    if (DEBUG) {
                        System.out.println("-----> start inflating children");
                    }

                    // Inflate all children under temp against its context.
                    //解析所有的子View,并把其添加到XML的根布局里
                    rInflateChildren(parser, temp, attrs, true);

                    if (DEBUG) {
                        System.out.println("-----> done inflating children");
                    }

                    // We are supposed to attach all the views we found (int temp)
                    // to root. Do that now.
                    //如果root不为null,且attachToRoot为true则将xml根布局添加到root里
                   
                    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.
                    //如果root为null或者attachToRoot为false则直接返回xml的根布局
                    //这里注意,之前我一直搞不明白为什么root!=null,attachToRoot=false时为什么
                    //返回的是xml的根布局,原因就在这里的||!attachToRoot
                    //情形一、二、三
                    if (root == null || !attachToRoot) {
                        result = temp;
                    }
                }

            } catch (XmlPullParserException e) {
                final InflateException ie = new InflateException(e.getMessage(), e);
                ie.setStackTrace(EMPTY_STACK_TRACE);
                throw ie;
            } catch (Exception e) {
                final InflateException ie = new InflateException(parser.getPositionDescription()
                        + ": " + e.getMessage(), e);
                ie.setStackTrace(EMPTY_STACK_TRACE);
                throw ie;
            } finally {
                // Don't retain static reference on context.
                mConstructorArgs[0] = lastContext;
                mConstructorArgs[1] = null;

                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }

            return result;
        }
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值