LayoutInflate渲染view源码分析

一般来说,加载渲染试图view所用方法无非两种:
第一种是View类中的静态inflate()方法;
第二种就是LayoutInflater.from(context).inflate()方法
那么这两种方法有什么区别呢?
首先我们看一下View.inflate()方法,点进去看源码:

public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) {
        LayoutInflater factory = LayoutInflater.from(context);   //还是使用的LayoutInflater.from
        return factory.inflate(resource, root);
    }

那么这个方法和LayoutInflate方法到底有什么区别呢?我们继续点击factory.inflate(resource, root)进去看一下


 public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
        return inflate(resource, root, root != null);------注意处。
    }
这块很明显了,View.inflate(contex,resource,root)最后其实还是调用的LayoutInflater.from(context).inflate(resource, root, attachToRoot),只是这个布尔值attachToRoot直接为了root != null。attachToRoot很清楚的翻译:是否绑定到root。用View.inflater渲染时,如果传入了有意义的root,那么最后的attachToRoot=true。
LayoutInflater.from(context).inflate(resource, root, attachToRoot)这个方法大家肯定不陌生,在listview或者recyclerView对viewholder绑定时都要用到这个方法来渲染出item进行绑定。那么我们一直在adapter中是这样写的:LayoutInflater.from(context).inflate(resource, root, false)。那么最后这个参数有什么影响呢?我们不妨将自己项目中的参数进行修改,改为传递true进去运行,结果如下:
java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.

错误信息提示我们这个childview已经有了自己的父集view,你必须先去remove掉child的第一个父集view。
什么意思?我们什么时候去将我们渲染出来的child添加到其他view中了呢?深入下去:

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");

            final Context inflaterContext = mContext;
            //解析xml布局文件获取属性
            final AttributeSet attrs = Xml.asAttributeSet(parser);
            Context lastContext = (Context) mConstructorArgs[0];
            mConstructorArgs[0] = inflaterContext;
            View result = root;

            try {
                // Look for the root node.
                int type;
                while ((type = parser.next()) != XmlPullParser.START_TAG &&
                        type != XmlPullParser.END_DOCUMENT) {
                    // Empty
                }

                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("**************************");
                }

                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) {
                        if (DEBUG) {
                            System.out.println("Creating params from root: " +
                                    root);
                        }
                        // 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);
                            //-参数的设置,如果root为null,就不会设置view的参数、
                        }
                    }

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

                    // Inflate all children under temp against its context.
                    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.
                    //第三处
                    if (root != null && attachToRoot) {
                        root.addView(temp, params);
                        //listview或者recyclerview中传false是因为不需要自己去addchild,最后listview等都会自己去加载自己渲染出来的child。
                    }

                    // Decide whether to return the root that was passed in or the
                    // top view found in xml.
                    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;
        }
    }

分开来讲第一处:

 //第一处
                    // Temp is the root view that was found in the xml
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);
很简单的一个英语翻译:这个temp就是从你传入的xml文件中找到的根节点,什么意思?这个temp就是每一个渲染出来的view,在recyclerview和listview中就是每一个item。

继续第二处分析:

//第二处
 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);
         //-参数的设置,如果root为null,就不会设置view的参数、
         }
        }
如果root不为null,它会去给你获取到属性值,如果attachToRoot是false,他才会给你渲染出来的temp设置进去。所以如果单独去渲染一个view出来,想要获取到他的LayoutParams,那么在调方法时,就要注意root和attachToRoot的参数的传入了,不然获取LayoutParams只会返回null。

OK,最后看一下第三处:

  //第三处
                    if (root != null && attachToRoot) {
                        root.addView(temp, params);
                        //listview或者recyclerview中传false是因为不需要自己去addchild,最后listview等都会自己去加载自己渲染出来的child。
                        // Decide whether to return the root that was passed in or the
                    // top view found in xml.
                    if (root == null || !attachToRoot) {
                        result = temp;
                    }
                    }
这地方就是The specified child already has a parent. You must call removeView() on the child's parent first.的真正原因了。如果root不为null且attachToRoot=true,他会直接将你渲染出来的view(temp)添加到root中,但是listview和recyclerview他的内部都会将渲染出来的item添加到其内部,所以如果这里就直接给temp添加了一个parent,那么在listview等添加时就会直接报错。
那么root能否提供null呢?很显然整个方法块下来,root是否传null对view的渲染没有影响,仅仅是在给view设置参数和添加parent是会进行判断。

猜你喜欢

转载自blog.csdn.net/m0_37194191/article/details/78435353