Analysis and Application of LayoutInflater Source Code

Posted by Submerged on Sat, 06 Jul 2019 23:02:03 +0200

I. Brief Introduction

LayoutInflater is literally translated into LayoutInflater, which is used to create layout views. Commonly used inflate() converts an xml layout file into a View. Here are three ways to get LayoutInflater and two ways to create View.

1. Three ways to get LayoutInflater

  1. LayoutInflater inflater = getLayoutInflater(); // call Activity's getLayoutInflater()
  2. LayoutInflater inflater =(LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
  3. LayoutInflater inflater = LayoutInflater.from(context);

In fact, either way, the LayoutInflater is finally obtained through Mode 2, such as:

2. Two Ways to Create View

  1. View.inflate();
  2. LayoutInflater.from(context).inflate();

Source code analysis

The above two ways of creating Views are commonly used in development, so what is the relationship between them? Following is the method call analysis of View.inflate():

1. Exploration of the final invocation method of View.inflate()

1) Press Ctrl + left mouse button to view View.inflate() method

You can see that View.inflate() calls LayoutInflater.from(context).inflate().

Okay, so let's be clear at this point. Whatever way we study, we actually study Mode 2, namely LayoutInflater.from(context).inflate().

2) Press Ctrl + left mouse button to view LayoutInflater.from(context).inflate(resource, root) method.

Uh huh? LayoutInflater.from(context).inflate(resource, root) calls its own overloaded inflate (resource, root, root!= null).

3) Press Ctrl + left mouse button to view LayoutInflater. from (context). inflate (resource, root). inflate (resource, root, root!= null) method.

Hmm?? LayoutInflater. from (context). inflate (resource, root). inflate (resource, root, root!= null) calls its own overloaded inflate(parser, root, attachToRoot).

4) Press Ctrl + left mouse button to view LayoutInflater. from (context). inflate (resource, root). inflate (resource, root, root!= null). inflate (parser, root, attachToRoot) method.

This is the end, but the code is too long. Here is half of the graph (which is not the point).

Okay, here's the point. Now we can see that the whole method invocation chain of View.inflate() is as follows:

View.inflate() = 
    LayoutInflater.from(context)
        .inflate(resource, root)
        .inflate(resource, root, root != null)
        .inflate(parser, root, attachToRoot)

2. What did LayoutInflater's inflate(parser, root, attachToRoot) do?

Because the code is too long for screenshots, the key code in the code is pasted below.

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
    synchronized (mConstructorArgs) {

                ...
                //Eliminate code~
                ...

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

                ...
                //Eliminate code~
                ...

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

                // Decide whether to return the root that was passed in or the
                // top view found in xml.
                if (root == null || !attachToRoot) {
                    result = temp;
                }

                ...
                //Eliminate code~
                ...

        return result;
    }
}

The inflate method has the following four steps:

  1. The xml layout file is converted into view temp by using XmlPullParser parser.
  2. Determine whether the ViewGroup root object is null or not to set LayoutParams for temp.
  3. Determine whether boolean attachToRoot is true to decide whether temp should be added to the ViewGroup root by the way.
  4. Finally, return to view temp.

Now that we have analyzed the process of creating views, we will compare the differences between View.inflate() and LayoutInflater.from(context).inflate().

3. The difference between View.inflate() and LayoutInflater.from(context).inflate()

1) Analysis of the third parameter of View.inflate():

The third parameter (ViewGroup root) is often passed into null in development. From the analysis of the final inflate method above, we can see that if the ViewGroup root is null, the resulting view temp will not be set to LayoutParams. Here's an experiment:

View itemView = View.inflate(parent.getContext(), android.R.layout.simple_list_item_1, null);
ViewGroup.LayoutParams params = itemView.getLayoutParams();
Log.e("CSDN_LQR", "params == null : " + (params == null));

The printing results are as follows:

Similarly, when you pass the third parameter into a real ViewGroup, the result is that the view temp can get LayoutParams, and you can try it yourself if you are interested.

2) Advantages of LayoutInflater.from(context).inflate():

* The following scenario analysis will demonstrate the flexibility of LayoutInflater.from(context).inflate().

If you use View.inflate() to create layout views in RecyclerView or ListView, and want to set parameters such as height for the created layout views, what bottlenecks will there be?

Here's a piece of code I wrote before for a waterfall stream adapter:

public class MyStaggeredAdapter extends RecyclerView.Adapter<MyStaggeredAdapter.MyViewHolder> {

    private List<String> mData;
    private Random mRandom = new Random();

    public MyStaggeredAdapter(List<String> data) {
        mData = data;
    }

    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        //Here we use Android's own text control layout
        View itemView = View.inflate(parent.getContext(), android.R.layout.simple_list_item_1, null);
        return new MyViewHolder(itemView);
    }

    @Override
    public void onBindViewHolder(MyViewHolder holder, int position) {
        //In order to achieve the effect of waterfall flow, it is necessary to set the height of each item (so that the height of each item is different).
        ViewGroup.LayoutParams params = holder.mTv.getLayoutParams();
        params.height = mRandom.nextInt(200) + 200;
        holder.mTv.setLayoutParams(params);
        holder.mTv.setBackgroundColor(Color.argb(255, 180 + mRandom.nextInt(60) + 30, 180 + mRandom.nextInt(60) + 30, 180 + mRandom.nextInt(60) + 30));
        holder.mTv.setText(mData.get(position));
    }

    @Override
    public int getItemCount() {
        return mData.size();
    }

    class MyViewHolder extends RecyclerView.ViewHolder {

        TextView mTv;

        public MyViewHolder(View itemView) {
            super(itemView);
            mTv = (TextView) itemView.findViewById(android.R.id.text1);
        }
    }

}

After parsing the third parameter of View.inflate() above, the problem of this code can be seen at a glance, right, that is ViewGroup.LayoutParams params = holder.mTv.getLayoutParams(); this line of code gets empty LayoutParams, don't you believe it? Take one.

Next, it's natural to make the resulting LayoutParams not empty, so modify the onCreateViewHolder() code as follows:

@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    //Here we use Android's own text control layout
    View itemView = View.inflate(parent.getContext(), android.R.layout.simple_list_item_1, parent);
    return new MyViewHolder(itemView);
}

The incoming ViewGroup parent is not null, so it is certain that the Layout Params obtained are not empty, but there is another problem, see the error.

Why would such a mistake be reported? Look back at the four steps of the final inflate():

  1. The xml layout file is converted into view temp by using XmlPullParser parser.
  2. Determine whether the ViewGroup root object is null or not to set LayoutParams for temp.
  3. Determine whether boolean attachToRoot is true to decide whether temp should be added to the ViewGroup root by the way.
  4. Finally, return to view temp.

Step 2 makes Layout Params not empty, right, but Step 3 goes wrong. When you pass in parent using View.inflate(parent.getContext(), android.R.layout.simple_list_item_1, parent), boolean attachToRoot takes the value of true, so the created entry will be added to ViewGroup by the way (here). The ViewGroup is RecyclerView, and the RecyclerView itself automatically adds entries to itself, so it adds them twice, and the error is reported. So why is attachToRoot true? Look again at the entire method invocation chain of View.inflate():

View.inflate() = 
    LayoutInflater.from(context)
        .inflate(resource, root)
        .inflate(resource, root, root != null)
        .inflate(parser, root, attachToRoot)

The value of boolean attachToRoot depends on whether the root (parent) is empty, which is the bottleneck of View.inflate(), which can not flexibly specify the value of boolean attachToRoot. Here I just want to create a view that can get LayoutParams, but not added to the ViewGroup. This requirement can be achieved through LayoutInflater.from(context).inflate(). So the code for onCreateViewHolder() is modified as follows:

@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    View itemView = LayoutInflater.from(parent.getContext()).inflate(android.R.layout.simple_list_item_1,parent,false);
    ViewGroup.LayoutParams params = itemView.getLayoutParams();
    Log.e("CSDN_LQR", "params == null : " + (params == null));
    return new MyViewHolder(itemView);
}

In the code, LayoutInflater. from (parent. getContext (). inflate (android. R. layout. simple_list_item_1, parent, false) is passed into parent (that is, ViewGroup is not null), so the created view can get LayoutParams, and specify that attachToRoot is false, that is, not added to ViewGroup. At this point, the problem of adding subcontrols over and over again is solved. Let's sum up.

  • If the third parameter of View.inflate() is not null, then the created view will get LayoutParams, and vice versa. (explained below)
  • LayoutInflater.from(context).inflate() can flexibly specify whether the incoming ViewGroup is empty to determine whether the created view can obtain LayoutParams, and can also specify the value of attachToRoot to determine whether the created view should be added to the ViewGroup.

3. Small details

* The source code of LayoutInflater has been analyzed above. Now there is a small problem. It has little to do with the theme of this article. Let's look at it as an extension.

As mentioned earlier, if the third parameter of View.inflate() is not null, the created view will certainly get LayoutParams, and vice versa. How do you understand that?

That is to say, even if the third parameter of View.inflate() is null, it is possible to create a view with LayoutParams. Yes, in the final analysis, the existence of this LayoutParams actually depends on whether the entry itself has a parent control, and look at the simple_list_item_1 layout used above:

Find out, just a TextView, without a parent control, what if I add a parent control to it and use the original way to get LayoutParams smoothly? The code is as follows:

@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    View itemView = View.inflate(parent.getContext(), R.layout.item_layout, null);
    return new MyViewHolder(itemView);
}

@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
    ViewGroup.LayoutParams params = holder.mTv.getLayoutParams();
    Log.e("CSDN_LQR", "params == null : " + (params == null));
    ...
    //Control Settings
    ...
}

The layout code of item_layout is as follows:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="vertical">

    <TextView
        android:id="@android:id/text1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_vertical"
        android:minHeight="?android:attr/listPreferredItemHeightSmall"
        android:textAppearance="?android:attr/textAppearanceListItemSmall"/>
</LinearLayout>

Running, you can get LayoutParams, the printing results are as follows:

IV. Lastly

I am also the first time to write an analytical article. If the description is incorrect, please don't hesitate to give me advice. At the same time, I would like to ask all the visitors to take care of it. I will revise it as soon as possible. Thank you.

Topics: Android xml encoding