The layoutInflater.inflate () method converts an XML-formatted layout file into a series of views that are combined in a parent-child relationship. This converted structure is also called a View Hierarchy.

We usually pass three arguments to the inflate() method: the resId of the layout resource, the nullable ViewGroup: root, and the Boolean value attachToRoot. Many beginners use the latter two parameters rather stiffly and lack understanding.

The reason I understand root to be introduced is to read the layout attribute of the outermost tag in an Xml file. The layout property is the property that we often see beginning with, and it represents the size and position that a View wants from the parent layout. Therefore, the layout property is provided to the parent layout to read and calculate the size and position. The layout property is represented in the View by the mLayoutParams variable, which is of type viewGroup.layoutParams. Different ViewGroup subclasses implement different LayoutParams classes to read the layout properties they care about from Xml. The type of a View’s mLayoutParams must be the same as the LayoutParams implemented by its parent layout.

For the outermost tag in an Xml file, the View it converts to doesn’t know what type its parent layout will be, so it doesn’t generate the mLayoutParams variable, and all the attributes of that tag are not applied to the View. To avoid this, we need to tell the outermost View what type its parent layout is and generate the corresponding LayoutParams to store the layout properties. Root will help us generate LayoutParams, and if root is the desired parent layout, then attachToRoot is set to true so that our XML-generated View Hierarchy can be directly added to root. We don’t need to do this manually.

If we think of the nested structure of AN Xml file as a tree structure, the parsing of each tag is actually a depth-first traversal of the tree. During the traversal, we generate a tree with a View as the node and a parent-child relationship.

There are four steps to generate each View in View Hierarchy:

1. Generate a View from the tag

2. Generate the corresponding LayoutParams based on the type of the View’s parent layout and set the LayoutParams to the View

3. Generate all Children of the View

4. Add View to its parent layout

The process of converting a tag to a View is to load the view. Class file of the tag from the ClassLoader and obtain the constructor. This is equivalent to calling the View(Context Context, @Nullable AttributeSet attrs) to construct an instance. So our custom control needs to implement this constructor in order to be used in Xml and then get the attributes in Xml from the ATTRs variable.

Introduction to the Inflate Method

In Android development, we use the layoutInflater.inflate () method to convert an Xml resource file in the Layout directory into a View Hierarchy that returns a View object.

View Hierarchy is roughly a View Hierarchy in literal translation, which refers to a series of views associated with parent and child relationships. We can obtain all View objects in the structure through the root View of View Hierarchy.

If we were to design a method to transform the layout file into a View Hierarchy, we might think of reading the tags in Xml line by line and using the information in the tags to generate a new View/ViewGroup. When there is a nesting relationship in Xml, we need to use a parent-child relationship to associate two views. The Inflate () method does just that.

We can use the Activity. GetLayoutInflater () method so as to obtain a LayoutInflater instance, These methods essentially get a system service from getSystemService(context.layout_inflater_service), which eventually returns an instance of PhoneLayoutInflater.

There are several overloading methods inflate, but you’ll end up with one

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot)
Copy the code

The first argument may seem unfamiliar to us, but most of the time we just need to pass in a Layout resource file, and the Framework will help us get a Parser based on the resource. Note that Android parses the Layout resource file at compile time and generates the Parser for efficiency purposes, so it is not possible to use a plain Xml file (not a layout resource) with the Inflate () method to generate the View during use.

The last two parameters are covered during source code parsing.

Let’s take a look at the official definition of these parameters:

The inflate() method returns a View, returning root if root is not empty and attachToRoot is true. Otherwise, return the root View of the XML-generated View Hierarchy.

Inflate method source code parsing

Look at the core code for the Inflate phase

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, Boolean attachToRoot) {// 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 {// Final View temp = createViewFromTag(root, name, inflaterContext, attrs); if (root ! = null) {/ / when the root is not null, create match the root LayoutParams params = root. GenerateLayoutParams (attrs); if (! AttachToRoot) {// supply params only to Temp temp. SetLayoutParams (params); }} // Generate all Chiildre rInflateChildren with Temp (Parser, Temp, attrs, true); // If root is not empty and attachToRoot is true, add temp to root and run the params if (root! = null && attachToRoot) { root.addView(temp, params); } if (root ! = null && attachToRoot) return root; }else { return temp; }}}Copy the code

Regardless of the tag, the inflate() method executes the process:

1. Convert the outermost label to a View, denoted as temp.

2. When root is not empty, use temp LayoutParams generated by root.

3. Parse Xml and generate all subviews of Temp.

4. When root is not empty and attachToRoot is true, add Temp as a child of root.

5. If root is not empty and attachToRoot is true, return root. Otherwise, return Temp.

This process contains several details:

1. Generate View objects from Xml tags

2. Generate View parent structure based on Xml nested structure

3. Apply root and attachToRoot

Below, we analyze these details from the outside to the inside. Let’s first look at the root and attachToRoot parameters in the pass.

Root and attachToRoot

The first role of root in the inflate() method is to generate a LayoutParams.

if (root ! = null) {/ / when the root is not null, create match the root LayoutParams params = root. GenerateLayoutParams (attrs); }Copy the code

LayoutParams holds the layout properties of a control. So let’s see why we need to generate LayoutParams with root.

Layout properties with LayoutParams

In Xml files, there are many attributes prefixed by layout_, such as the most familiar layout_width/layout_height, which we call layout attributes. The View uses the layout properties to tell the parent the desired size and location. Different types of parent layouts read different layout properties, such as the layout_centerInParent property, which works when the parent is a RelativeLayout but not when the parent is a LinearLayout.

The layout property in Xml saved to the View is the mLayoutParams variable, which is of type viewGroup.layoutParams. In fact, every subclass of ViewGroup implements a nested class that extends from viewGroup.layoutParams, which determines which layout properties it reads. We see a RelativeLayout. LayoutParams part of the source code:

public LayoutParams(Context c, AttributeSet attrs) { super(c, attrs); TypedArray a = c.obtainStyledAttributes(attrs, com.android.internal.R.styleable.RelativeLayout_Layout); . final int N = a.getIndexCount(); for (int i = 0; i < N; i++) { int attr = a.getIndex(i); switch (attr) { ... case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toLeftOf: rules[LEFT_OF] = a.getResourceId(attr, 0); break; case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toRightOf: rules[RIGHT_OF] = a.getResourceId(attr, 0); break; case com.android.internal.R.styleable.RelativeLayout_Layout_layout_above: rules[ABOVE] = a.getResourceId(attr, 0); break; case com.android.internal.R.styleable.RelativeLayout_Layout_layout_centerInParent: rules[CENTER_IN_PARENT] = a.getBoolean(attr, false) ? TRUE : 0; break; . }}... a.recycle(); }Copy the code

This code with our custom controls reads the Xml of the custom attribute is the same way, we see a RelativeLayout. LayoutParams read when creating a series of attributes and store layout, such as layout_centerInParent, Other LayoutParams do not read this property.

We can find the use of LayoutParams in the relativelayout.onMeasure () method.

// RelativeLayout.java @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { ... int count = views.length; for (int i = 0; i < count; i++) { View child = views[i]; if (child.getVisibility() ! = GONE) { ... LayoutParams params = (LayoutParams) child.getLayoutParams(); int[] rules = params.getRules(layoutDirection); . }}... }Copy the code

When calculating the position of each child View, need to read the layout of their property, at this point of View LayoutParams casts for RelativeLayout. LayoutParams, here may quote us unable to convert types of errors, So we need to make sure that added to the RelativeLayout View LayoutParams type are RelativeLayout. LayoutParams.

Let’s summarize the core points of LayoutParams:

1. LayoutParams save Xml layout attributes starting with layout_

ViewGroup subclasses usually implement a LayoutParams class that reads the layout properties they need

3. The type of the View’s LayoutParams must match the type of its parent layout, otherwise an error will be reported during the onMeasure process

Go back to root with attachToRoot

When we parse the outermost tag in the Xml, the root View of the View Hierarchy, the program doesn’t know what type its parent layout will be, so no LayoutParams are generated. All the layout properties in the outermost tag, including layout_width/layout_height, will not be recorded in the View object.

However, in practice, we can usually know the parent layout or the type of the parent layout to be added to the View Hierarchy generated by Xml. We pass in a root argument to read the root View layout property and generate the corresponding LayoutParams based on the root type. The code looks like this:

if (root ! = null) {/ / root is not null, generation and root corresponding LayoutParams params = root. GenerateLayoutParams (attrs); } public LayoutParams generateLayoutParams(AttributeSet attrs) {// Different viewgroups generate different LayoutParams details return new LayoutParams(getContext(), attrs); }Copy the code

AttachToRoot is used to determine whether the root is directly used as the parent or if only its type information is needed.

if (root != null) {
    if (attachToRoot) {
        root.addView(temp, params);
    }else{
        // Set the layout params for temp if we are not attaching. 
        temp.setLayoutParams(params);
    }
}
Copy the code

Recursively process all tags in Xml

With the inflate method, parsing of all remaining tags other than the root tag uses only one method:

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) { ... // Inflate all children under temp against its context. rInflateChildren(parser, temp, attrs, true); . } final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException { rInflate(parser, parent, parent.getContext(), attrs, finishInflate); }Copy the code

This method simply calls the rInflate method; there is no additional action. The r in the rInflate method means Recursive, or recursively, and we can guess that this method uses a Recursive approach to handling all nested relationships in the Xml. Let’s look at the rInflate core:

This method involves the knowledge of XmlPullParser, which converts an Xml file into an object. The next event is obtained through the next() method. There are five events START_DOCUMENT, START_TAG, TEXT, END_TAG, and END_DOCUMENT. If START_TAG is not read, the depth is 0. If START_TAG is read, the depth is incretracted by 1.

void rInflate(XmlPullParser parser, View parent, Context context, AttributeSet attrs, boolean finishInflate) { final int depth = parser.getDepth(); 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)) { ... } 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 {// Convert the current Tag to View final View View = createViewFromTag(parent, name, context, attrs); Final ViewGroup ViewGroup = (ViewGroup) parent; final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs); // All children rInflateChildren(Parser, View, attrs, true); Viewgroup.addview (View, params); }}}Copy the code

Let’s examine the flow with the rInflate method with the example:

<! -- depth --> <LayoutA > <! -- 1 --> <ViewB /> <! -- 2 --> <LayoutC > <! -- 2 --> <ViewC1 /> <! -- 3 --> </LayoutC > <! -- 2 --> </LayoutA > <! 1 - - - >Copy the code
  1. First write down the current depth
  2. Take the next event in the Xml until the end of the file is reached, or the END_TAG at the current depth is reached. As an example, if rInflate is parsed with the start, the execution will end with the jump out of the while. 3. In a while loop, only STAST_TAG is parsed to ensure that each Tag generates only one View. For example, when a View is parsed, it does not need to be parsed.

The logic for parsing normal tags is as follows:

Final View View = createViewFromTag(parent, name, context, attrs); Final ViewGroup ViewGroup = (ViewGroup) parent; final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs); // All children rInflateChildren(Parser, View, attrs, true); Viewgroup.addview (View, params);Copy the code

The important concern here is the nested rInflateChildren() method, which also goes to rInflateChildren, with the currently resolved View as the Parent, and with the contents of the next level resolved.

Inflate process deduction

Let’s work through the inflate method execution as shown above.

<! -- depth --> <LayoutA > <! -- 1 --> <ViewB /> <! -- 2 --> <LayoutC > <! -- 2 --> <ViewC /> <! -- 3 --> </LayoutC > <! -- 2 --> </LayoutA > <! 1 - - - >Copy the code
  • Inflate the method, and resolve the LayoutA object
  • All children of LayoutA are resolved with the rInflate method, starting at a depth of 1
    • Fetch, parse out the ViewB object, ViewB has no children, and its rInflateChildren will read the END_TAG of ViewB and terminate, adding ViewB to LayoutA
    • Fetch, resolve the LayoutC object, and call the rInflate method to resolve the Children
      • Take, parse out the ViewC object, no Children, add ViewC to LayoutC
      • The rInflateChildren process for LayoutC is over
      • Add a LayoutC object to a LayoutA
    • At END_TAG and depth 1, the rInflate method ends
  • After the View Hierarchy completes parsing, it performs some processing with root and attachToRoot to return

The whole process parses all tags in the Xml file from top to bottom and generates the corresponding View Hierarchy, and the nested relationship is smoothly transformed into the parent-child relationship. The generated View Hierarchy is a tree structure, and the generation process has a similar feel to the depth-first traversal of the tree.

Merge Label Resolution

With the rInflate method resolved, let’s look at the parsing of the tag:

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

It simply ignores this layer and adds everything directly to root.

The corresponding View is generated from the Tag

The last detail in this process is how to convert a single Tag into a View object

Either the inflate method is resolved on the root View, or the nested resolution in rInflate, calls the createViewFromTag() method. Let’s take a look at the core of this method.

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs, If (name.equals("view")) {name = attrs.getAttributeValue(null, "class"); } // the theme is read from the Xml and is applied to the View by Context 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); } // Generate a View 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]; // View(Context Context, @nullable AttributeSet attrs) // The first parameter passed is Context attrs, int defStyleAttr) mConstructorArgs[0] = context; If (-1 == name.indexof ('.')) {view = onCreateView(parent, name, attrs); } else { view = createView(name, null, attrs); } } finally { mConstructorArgs[0] = lastContext; } } return view; }... }Copy the code

See directly the part that generated the View

  • Generated by Factory2
  • Generated by the Factory
  • Generated by mPrivateFactory
  • Generated by the onCreateView/createView method

There is a sequence of generation methods here, so once the View is generated, you don’t have to go through the following methods. Factory2 and Factory can be thought of as giving us hook code that allows us to convert a Tag to a View as we wish. The difference between them is that the Factory methods pass different parameters.

If no factory is set

if (-1 == name.indexOf('.')) {
    view = onCreateView(parent, name, attrs);
} else {
    view = createView(name, null, attrs);
}
Copy the code

Xml can use system controls without namespaces, so there is no “.” The onCreateView method prefixes the system control with “android.view.” and calls the createView method. We mentioned earlier that the actual LayoutInflater we use is usually a PhoneLayoutInflater, which overwrites the onCreateView method:

/// PhoneLayoutInflater.java @Override protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException { for (String prefix : sClassPrefixList) { try { View view = createView(name, prefix, attrs); if (view ! = null) { return view; } } catch (ClassNotFoundException e) { // In this case we want to let the base class take a crack // at it. } } return super.onCreateView(name, attrs); } private static final String[] sClassPrefixList = { "android.widget.", "android.webkit.", "android.app." }; /// LayoutInflater.java protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException { return createView(name, "android.view.", attrs); }Copy the code

The operations here are for the system control to complete the namespace, the specific generation of View is completed by createView, the core code is as follows:

public final View createView(String name, String prefix, AttributeSet attrs) throws ClassNotFoundException, InflateException { ... Clazz = McOntext.getclassloader ().loadClass(prefix! = null ? (prefix + name) : name).asSubclass(View.class); constructor = clazz.getConstructor(mConstructorSignature); constructor.setAccessible(true); Object[] args = mConstructorArgs; args[1] = attrs; Final View View = constructor. NewInstance (args); return view; . }Copy the code

We load the class through the ClassLoader, get the constructor, and then instantiate the subclass of View. The constructor is View(Context Context, @Nullable AttributeSet attrs), so our custom control needs to implement this constructor to work properly in Xml.