LayoutInflater源码浅读

LayoutInflater代码解读

本文主要解读LayoutInflater加载XML的过程。

我们经常在ListView或者RecyclerView亦或是Fragment的onCreateView中调用到LayoutInflater来加载布局得到一个View对象。(当然其实Activity的setContent方法也是要间接的调用到的)。于是就源码来进行跟踪分析。

比如我们

    LayoutInflater.from(mContext).inflate(R.layout.item, null);

如此调用。那么就来看一看它所走的流程吧最终是如何通过加载一个xml布局文件返回一个View对象。

当然如果inflate方法root参数和attachToRoot参数传入会有较大的区别,详见源码

首先from(context)方法返回了LayoutInflater对象本身。然后就是关键的inflate方法。

      public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
        return inflate(resource, root, root != null);
    }

这里是调用了重载了的另一个方法。

    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {  
    final Resources res = getContext().getResources();  
        //debug的日志。忽略
    if (DEBUG) {        Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("                + Integer.toHexString(resource) + ")");    } 
        //通过layout的id返回一个XmlResourceParser
    final XmlResourceParser parser = res.getLayout(resource);  
    try {       
        //真正的执行加载方法
    return inflate(parser, root, attachToRoot);   
    } finally {     
    parser.close();   
    }
    }

然后进入真正的加载方法

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

        final Context inflaterContext = mContext;
        final AttributeSet attrs = Xml.asAttributeSet(parser);
        Context lastContext = (Context) mConstructorArgs[0];
        mConstructorArgs[0] = inflaterContext;
        //最终返回给外部的View对象
        View result = root;

        try {
           
            int type;
            //遍历真个xml标签,找开始标签,START_TAG和END_TAG具体是什么稍后在看
            while ((type = parser.next()) != XmlPullParser.START_TAG &&
                    type != XmlPullParser.END_DOCUMENT) {
                // Empty
            }
            //如果遍历完成都没有START_TAG就抛出异常
            if (type != XmlPullParser.START_TAG) {
                throw new InflateException(parser.getPositionDescription()
                        + ": No start tag found!");
            }
            //拿到标签的name,具体实现暂不深究,猜测就是TextView等这种名字
            final String name = parser.getName();
            //debug日志省略
            //处理根标签是merge的
            if (TAG_MERGE.equals(name)) {
                //处理merge标签。如果没有root会抛出异常。
                if (root == null || !attachToRoot) {
                    throw new InflateException(" 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
                //创建跟布局对应的View或ViewGroup
                final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                ViewGroup.LayoutParams params = null;
            //root不为空的时候设置layoutparams,这也是为什么root不传的时候最外层布局             //的一些属性无效的原因比如,padding、margin等
                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);
                    }
                }

                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.
                //如果rootview存在则。add到父布局。
                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;
                }
            }

        } 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;
    }
}

先来继续看一下createViewFromTag。用于创建根布局

     View createViewFromTag(View parent, String name, Context context, AttributeSet attrs, boolean ignoreThemeAttr) {
    if (name.equals("view")) {
        name = attrs.getAttributeValue(null, "class");
    }

    // Apply a theme wrapper, if allowed and one is specified.
    if (!ignoreThemeAttr) {
        final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
        final int themeResId = ta.getResourceId(0, 0);
        if (themeResId != 0) {
            context = new ContextThemeWrapper(context, themeResId);
        }
        ta.recycle();
    }

    if (name.equals(TAG_1995)) {
        // Let's party like it's 1995!
        return new BlinkLayout(context, attrs);
    }

    try {
        View view;
        if (mFactory2 != null) {
            view = mFactory2.onCreateView(parent, name, context, attrs);
        } else if (mFactory != null) {
            view = mFactory.onCreateView(name, context, attrs);
        } else {
            view = null;
        }

        if (view == null && mPrivateFactory != null) {
            view = mPrivateFactory.onCreateView(parent, name, context, attrs);
        }

        if (view == null) {
            final Object lastContext = mConstructorArgs[0];
            mConstructorArgs[0] = context;
            try {
                if (-1 == name.indexOf('.')) {
                    view = onCreateView(parent, name, attrs);
                } else {
                    view = createView(name, null, attrs);
                }
            } finally {
                mConstructorArgs[0] = lastContext;
            }
        }

        return view;
    } catch (InflateException e) {
        throw e;

    } catch (ClassNotFoundException e) {
        final InflateException ie = new InflateException(attrs.getPositionDescription()
                + ": Error inflating class " + name, e);
        ie.setStackTrace(EMPTY_STACK_TRACE);
        throw ie;

    } catch (Exception e) {
        final InflateException ie = new InflateException(attrs.getPositionDescription()
                + ": Error inflating class " + name, e);
        ie.setStackTrace(EMPTY_STACK_TRACE);
        throw ie;
    }
}

然后对子View的递归遍历

    void rInflate(XmlPullParser parser, View parent, Context context,
        AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {

    final int depth = parser.getDepth();
    int type;
    boolean pendingRequestFocus = false;

    while (((type = parser.next()) != XmlPullParser.END_TAG ||
            parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {

        if (type != XmlPullParser.START_TAG) {
            continue;
        }

        final String name = parser.getName();
//如果设置了requestFocus,比如EditText。会让View获取焦点
        if (TAG_REQUEST_FOCUS.equals(name)) {
            pendingRequestFocus = true;
            consumeChildElements(parser);
            
        } else if (TAG_TAG.equals(name)) {
            //如果设置了tag属性会解析出来,对View 进行setTag,调用 view.setTag(key, value);key 是int 类型的id,value可以是任何的Object
            parseViewTag(parser, parent, attrs);
        } else if (TAG_INCLUDE.equals(name)) {
            //对include标签的处理
            if (parser.getDepth() == 0) {
                throw new InflateException(" cannot be the root element");
            }
            parseInclude(parser, context, parent, attrs);
        } else if (TAG_MERGE.equals(name)) {
            //这里说明merge标签不能随意使用。只有在根布局。在childview使用是会抛出异常的
            throw new InflateException(" must be the root element");
        } else {
            //如果不是上面的特殊类型,就创建View,设置params,add到对应的ViewGroup中去
            final View view = createViewFromTag(parent, name, context, attrs);
            final ViewGroup viewGroup = (ViewGroup) parent;
            final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
            rInflateChildren(parser, view, attrs, true);
            viewGroup.addView(view, params);
        }
    }

    if (pendingRequestFocus) {
        parent.restoreDefaultFocus();
    }

    if (finishInflate) {
        parent.onFinishInflate();
    }
}

我觉得从中总结到最有用的几点:

  • 没有root最外层的layoutparams是无效的。

  • 如果root为空,mereg标签是不能用在根标签的、另外在使用include的时候,merge必须在include的布局的根标签。

  • 还发现了一个BlinkLayout继承自FramLayout,用于制作闪烁效果。查了下。说是为了纪念1995复活节!

你可能感兴趣的:(LayoutInflater源码浅读)