Android inflate everything you need to know

The idea of ``converting xml layout to view object'' came into our minds when we met this requirement. It is a small case. The api provided by Android: the inflate method of View and the inflate method of LayoutInflater can quickly complete our needs. However, life is not all smooth sailing. When we use it in recyclerview or in fragment, we may encounter such problems:
1. The layout parameters are not available, and the project sometimes crashes directly.
2. Crash error: The child view already has a parent layout. If you want to specify a parent layout for this view, you must first delete the child view in the parent layout of the view. (Java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.) As
soon as we encountered a problem, we might have thought of Du Niang, google and then refer to other people’s statements and changed Changing the parameters passed in the inflate method is amazing, but are you really familiar with inflate? Here we summarize it.

Knowledge points

Insert picture description here

The difference between LayoutInflater and View

There is actually a relationship between the two, and we immediately understand the method call of the source code. (The thief is simple...)

(1) Usual use

View.inflate(mContext, R.layout.layout_recyclerview_item, null)

We feel cordial when we see the code above! frequently used. Next we will enter the inflate method to see:

(2) View's inflate method source code

 public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) {
    
    
        LayoutInflater factory = LayoutInflater.from(context); // 调用了LayoutInflater的from方法获得LayoutInflater对象
        return factory.inflate(resource, root); // 调用了LayoutInflater对象的inflate方法(两个参数的)
    }

The source code is too simple! Is this the source code? Let's see this first, we only need to understand the relationship between the two here. There are several overloads of the inflate method. We will analyze the different meanings of the parameters later. . .

(3) Conclusion

The underlying call of the inflate method of View is the inflate method with two parameters of LayoutInflater.

Second, detailed explanation of inflate method

By viewing the source code of View's inflate method, we know that this inflate method is static and can be used directly with the class name, but the use of the inflate method of LayoutInflater requires an object call. How to get the LayoutInflater object at this time? Google engineers provided three ways. as follows:

1. Obtaining the LayoutInflater object
         //方式1:
        LayoutInflater layoutInflater = getLayoutInflater();
          //方式2:
        LayoutInflater layoutInflater = LayoutInflater.from(this);
          //方式3:
        LayoutInflater layoutInflater = (LayoutInflater) this.getSystemService(LAYOUT_INFLATER_SERVICE);

The LayoutInflater object can be obtained in the above three ways. Method 1 is not used much, but method 2 is the most commonly used method. The way 3 is written is still a bit compelling. Because the bottom layer of mode 1 and mode 2 is calling mode 3 (see source code analysis below). An engineer with a level of skill according to the way 3 is written will find out, hey, you guys are good! Read the source code and be good at imitating. . . .

ps: How to choose among the three can be based on our own habits.

2. Inflate overload and call chain

In fact, there are many overloaded methods of inflate, but no matter which method we call, we will eventually call the method with specific parameters (see the source code below)

(1) Chestnut with specific parameters

Take the custom view we usually write as an example, as follows:

/**
 * Created by sunnyDay on 2019/9/4 20:34
 */
public class MyCustomView extends View {
    
    
    public MyCustomView(Context context) {
    
    
        this(context,null);
    }

    public MyCustomView(Context context, AttributeSet attrs) {
    
    
        this(context, attrs,0);
    }

    /**
     * 用户无论调用哪个方法,最终都会使用这个方法处理。
     * */
    public MyCustomView(Context context, AttributeSet attrs, int defStyleAttr) {
    
    
        super(context, attrs, defStyleAttr);
    }
}

(2) Overload method of inflate


1、View	inflate(int resource, ViewGroup root)

2、View	inflate(int resource, ViewGroup root, boolean attachToRoot)

3、View	inflate(XmlPullParser parser, ViewGroup root)

4、View	inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot)

The above four methods, we usually use method 1, method 2.
1. Method 1 is called internally by the inflate method of the above View.
2. When we use the inflate method of LayoutInflater, we generally use method 2

(3) Observe the source code

Since the inflate method of View calls the inflate(resource, root) of LayoutInflater at the bottom, we start from here. The source code of View inflate is as follows:

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

----> ctrl+right mouse button to enter factory.inflate(resource, root)

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

You can see that the three-parameter method is called here. The three-parameter method is an extra boolean parameter. This parameter is used to determine whether to add the converted view to the root layout.
It can be seen that when we use the inflate method of View, when the root is passed as null, the root != null does not hold, so the value here is false when the method with three parameters is called. Equivalent to calling inflate(resource, root, false)

----> ctrl+right mouse button to continue viewing the source code of the three parameters

  public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
    
    
         //1、首先根据用户传的Context 获得Resources对象
        final Resources res = getContext().getResources();
        if (DEBUG) {
    
    
            Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                    + Integer.toHexString(resource) + ")");
        }
        // 2、根据用户传递进来的资源id 来获得xml资源解析器对象
        final XmlResourceParser parser = res.getLayout(resource);
        try {
    
    
        //3、吧布局转换为view
            return inflate(parser, root, attachToRoot);
        } finally {
    
    
            parser.close();
        }
    }

It can be seen that there are three main steps:
1. First obtain the Resources object according to the Context
passed by the user 2. Obtain the xml resource parser object according to the resource id passed in by the user
3. Call inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot ) Method
: Convert the layout to view ps: The inside of getLayout is the process of parsing the xml file to generate the XmlResourceParser object.

(4) Inflate call chain

Insert picture description here

Three, chestnuts

Now that we understand the call chain, the core method is the inflate(int resource, ViewGroup root, boolean attachToRoot) three-parameter method, and the resource in the three-parameter method must be passed, so don't consider it. So we only need to consider whether root is null and attachToRoot is true or false. Next, let's explore the situation separately.

Several situations
  • root is not null, attachToRoot is true
  • root is not null, attachToRoot is false
  • root is null
1. Root is not null, attachToRoot is true

root is not null, attachToRoot is true: it means that the specified layout A is added to the root container, and the attributes of the root node of A layout are all effective during the adding process.

(1) Simply make an activity
Insert picture description here

As above, create a simple activity with only a LinearLayoutCompat added to the layout.

(2) Create a layout file (layout_mytest.xml as follows)

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.LinearLayoutCompat
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:background="@color/colorAccent"
        android:id="@+id/my_test"
        android:layout_width="250dp"
        android:layout_height="250dp"
        android:orientation="vertical"
        android:gravity="center">

    <android.support.v7.widget.AppCompatTextView
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:text="测试"
            android:background="#ffffff"
            android:textColor="#000000"
            android:gravity="center"/>

</android.support.v7.widget.LinearLayoutCompat>

(3) Convert the layout_mytest.xml layout to view and add it to the layout container of TestActivity

public class TestActivity extends AppCompatActivity {
    
    

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);

        LinearLayoutCompat layoutRoot = findViewById(R.id.layout_root);  // TestActivity根布局的View对象

        LayoutInflater inflater = LayoutInflater.from(this);
        View mView = inflater.inflate(R.layout.layout_mytest, layoutRoot, true);  // 布局转换为view

    }
}

Insert picture description here

You can see that we have obtained an mView object, and did not perform layoutRoot.addView(mView); however, a magical scene appeared, and the layout was actually loaded into the root container we specified. In fact, the value of attachToRoot is to control whether the specified layout is loaded into the specified root container. Here we only need to modify it slightly, change true to false and run it, and you will find that the layout is not loaded successfully.

(4) Expansion-Do It Yourself Series

As follows: If we have low hands, we write more code when the third parameter of inflate is true

   LinearLayoutCompat layoutRoot = findViewById(R.id.layout_root);  // TestActivity根布局的View对象

        LayoutInflater inflater = LayoutInflater.from(this);
        View mView = inflater.inflate(R.layout.layout_mytest, layoutRoot, true);  // 布局转换为view

        layoutRoot.addView(mView); //自作聪明 吧view添加到容器

Insert picture description here

Can't run, just explode. . . Because the sentence inflater.inflate(R.layout.layout_mytest, layoutRoot, true) means to add the specified view to the specified root container. It means that the view of mView has a parent container layoutRoot. At this time, we call layoutRoot (or other containers) again to add mView and it will be wrong. In fact, there is a solution, that is, we can change the true of the above code to false. It won't collapse.

2. Root is not null, attachToRoot is false

Nani? The root is not empty and attachToRoot is false. Isn't the above mentioned? It is not that the specified layout is not added to the root container. . . . We may still have doubts, since it is not added to the root, I just leave the root blank, but things are not as simple as we thought.

(1) It involves a statement

Only when a control is in a container, the width and height properties of the control are meaningful and take effect.
Therefore, after we specified the root layout above, either directly add view with attachToRoot set to true, or add view with attachToRoot set to false+ layoutRoot.addView(mView). When adding, the attributes of the root node of the layout we specified are all effective. Because we designated a parent container for him.

3. When root is null

When root is null, the effect of attachToRoot being true or false is the same. At this point, if we convert the xml layout A to a view and add it to the container, the root node attribute of A will not take effect.

   LinearLayoutCompat layoutRoot = findViewById(R.id.layout_root);  // TestActivity根布局的View对象

        LayoutInflater inflater = LayoutInflater.from(this);
        View mView = inflater.inflate(R.layout.layout_mytest, null, false);  // 布局转换为view

        layoutRoot.addView(mView);

(1) The parent layout fails
Insert picture description here

(2) Modification of child view properties

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.LinearLayoutCompat
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:background="@color/colorAccent"
        android:id="@+id/my_test"
        android:layout_width="250dp"
        android:layout_height="250dp"
        android:orientation="vertical"
        android:gravity="center">

<!--    宽度由之前的50 变为500 -->
    <android.support.v7.widget.AppCompatTextView
            android:layout_width="500dp"
            android:layout_height="50dp"
            android:text="测试"
            android:background="#ffffff"
            android:textColor="#000000"
            android:gravity="center"/>

</android.support.v7.widget.LinearLayoutCompat>

Insert picture description here

It can be found that the width and height attributes (250dp, gravity) of the root layout we originally set are invalid. We will also find that AppCompatTextView is effective when we modify the width and height of AppCompatTextView, because AppCompatTextView is in the parent container.

Fourth, source code analysis

In fact, inadvertently, I could not bear to analyze part of the source code when introducing the call chain of the inflate method. Here we will continue to analyze the core part.

    
     	/**
     	*
		*局部代码   部分有省略
		*/
	public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
    
    
        synchronized (mConstructorArgs) {
    
    
          
            View result = root; // 用户传递的root 赋值给result

					// 1、xml 布局转换为view
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);



                    ViewGroup.LayoutParams params = null; // 布局参数

                    // 2、用户传递的root view不为null时给指定的子view(temp)添加布局参数
                    if (root != null) {
    
    
          
                        params = root.generateLayoutParams(attrs);//生成布局参数的方法
						
                        if (!attachToRoot) {
    
    //当temp没有被添加到root时开始给temp 设置布局参数(添加过就不用设置了)
						
                            temp.setLayoutParams(params);
                        }
                    }



					
					// 3、root 不为null 且用户指定attachToRoot为 ture时,吧temp 添加到root中,告知root temp的布局参数信息。
                    if (root != null && attachToRoot) {
    
    
                        root.addView(temp, params);
                    }

                    if (root == null || !attachToRoot) {
    
     //root为null 或者用户attachToRoot 为false 时直接返回 用户指定布局的生成的 view: temp
                        result = temp;
                    }
                }

            return result;
        }
    }

You can see that these processes are mainly done here:
1. The xml layout is converted to view
2. When the root view passed by the user is not null, add layout parameters to the specified child view (temp)
3. Root is not null and the user specifies attachToRoot as When ture, add temp to root and inform the layout parameter information of root temp.

end

After a series of discussions, I found that my understanding of inflate has really deepened and I have gained a lot. Happy! ! !
Insert picture description here

Reference:
1. Three cases show you the difference between the two parameters and the three parameters of the inflate method in LayoutInflater

2、LayoutInflater.inflate和View.inflate

Guess you like

Origin blog.csdn.net/qq_38350635/article/details/100542733