LayoutInflater source code analysis

Preface: Recently I was doing an app similar to mall (e-commerce), and then I used Ali’s new open source VLayout layout to do, it is not too convenient ah, if free, I will write an article about VLayout, its bottom layer is RecyclerView, It’s just wrapping LayoutManager and Adapter, but we need to wrap it again when we use it, because we don’t have addHeader and addFooter, In the process, I stumbled upon the fact that LayoutInflater’s header and footer width didn’t work, which made me anxious (because the project was in a rush). Baidu then discovered that LayoutInflaters load layouts differently from XML.

1. Method of use

(1) There are two ways to obtain LayoutInflater instances:

LayoutInflater layoutInflater = LayoutInflater.from(context);

LayoutInflater layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
Copy the code

(2). Inflate method:

layoutInflater.inflate(int layoutId, ViewGroup root); /** * layoutId layout file * root nested a layer of parent layout on the outermost layer of the layout, if not required to null * attachToRoottrue{layoutInflater.inflate(int layoutId, ViewGroup root, attachToRoot); layoutInflater.inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot)Copy the code

Note: The third parameter attachToRoot is explained here in the following cases:

If root is null, no matter attachToRoot is true or false, the effect is the same

If root is not null, attachToRoot is true, indicating that the layout is added to the root layout

If root is not null and attachToRoot is false, the layout is not added to the root layout. To do so, manually addView

(4) If root is not null and no attachToRoot is set, the situation is the same as in (2)

The first two methods are the most common for loading a layout, and the last method will eventually be called to load the layout:

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
	...
	
	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 paramsfor temp ifwe are not // attaching. (If we are, we use addView, below) temp.setLayoutParams(params); }}... // 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); }... }... }Copy the code

LayoutInflater parses the XML layout file using a Pull parser (XmlPullParser). After the node name is resolved, the rInflate method is called, which iterates through the children of the root layout. And then it calls the createViewFromTag method, what does that method do? If root is null, a params will be generated for the View. If root is null, no params will be generated for the View. Finally, if root is not null, the addView method is called to add the generated View to the root layout.

    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();

            if (TAG_REQUEST_FOCUS.equals(name)) {
                pendingRequestFocus = true;
                consumeChildElements(parser);
            } else if (TAG_TAG.equals(name)) {
                parseViewTag(parser, parent, attrs);
            } else if (TAG_INCLUDE.equals(name)) {
                if (parser.getDepth() == 0) {
                    throw new InflateException("<include /> cannot be the root element");
                }
                parseInclude(parser, context, parent, attrs);
            } else if (TAG_MERGE.equals(name)) {
                throw new InflateException("<merge /> must be the root element");
            } else {
                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(); }}Copy the code

If you’re careful, you’ll notice that the above two pieces of code call the same method createViewFromTag, recursively calling this method each time to create a child element under the View and add it to the root layout. Let’s take a look at what’s going on in this method:

    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; }}Copy the code

This method generates a View object from the node name of the XML we passed in. How to do this? Calls a createView method (which is also called in the onCreateView method) :

public final View createView(String name, String prefix, AttributeSet attrs) throws ClassNotFoundException, InflateException {
	Constructor<? extends View> constructor = sConstructorMap.get(name);
    if(constructor ! = null && ! verifyClassLoader(constructor)) { constructor = null; sConstructorMap.remove(name); }... final View view = constructor.newInstance(args); . }Copy the code

So you see here, you know, you create the final View by reflection.

Now, you can see why layoutinflaters sometimes lose their View width and height.

Layoutinflaters have two methods for loading layouts with two and three parameters:

If root is null, no matter attachToRoot is true or false, a View without LayoutParams is returned.

If root is not null and attachToRoot is true, a View with LayoutParams is returned and added to the root layout.

If root is not null and attachToRoot is false, a View with LayoutParams will also be returned, but will not be added to the root layout.

(4) If root is not null and no attachToRoot is set, the situation is the same as in (2).