1. Introduction
In development, we use the inflate() method of layoutInflaters almost every day to transform the XML layout into the corresponding View object. However, the arguments to the inflate() method are a little more confusing. Even a look at the documentation doesn’t solve the puzzle.
Maybe you can only experiment with each use and it won’t affect development very much; Perhaps, remember how to pass specific parameters in a specific scenario without knowing why.
We should not overlook the details:
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)
Copy the code
ViewGroup root
What are parameters for, and why are they sometimes passednull
Sometimes you can’t?boolean attachToRoot
When do parameters passtrue
When will it be postedfalse
? Why does it sometimes passtrue
Will collapse?- Why do layout parameters set by the root node in the XML sometimes not take effect?
This article focuses on the meaning of the inflate() method parameter and its use in specific scenarios.
2. The body
2.1 Analysis by the inflate() method
In the LayoutInflater class, there are several overloaded inflate() methods:
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root)
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)
public View inflate(XmlPullParser parser, @Nullable ViewGroup root)
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot)
Copy the code
You should use the first two more in the actual development.
Their invocation relationships (arrows pointing in the direction of invocation) are as follows:
As can be seen from the figure, the first three final calls are the last ones:
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot)
Copy the code
So now, let’s focus on the last one. Just figure out the last one, and you’ll figure out the other three.
The first argument to this method is XmlPullParser parser.
final Resources res = getContext().getResources();
XmlResourceParser parser = res.getLayout(resource);
Copy the code
XML is a class converted from XML and used to parse XML.
Ok, so now we know what the first parameter means, which is to pass in the XML layout to be transformed.
Then look at the following two arguments: @Nullable ViewGroup root and Boolean attachToRoot. Note that the @nullable annotation in front of ViewGroup root indicates that the ViewGroup root argument can be null.
How many combinations of these two parameters can we take? 4.
Combination of the values | ViewGroup root | boolean attachToRoot |
---|---|---|
The first group | notNull | false |
The second group | notNull | true |
The third group | null | false |
The fourth group | null | true |
What does the difference in the combination of values have to do with the return value View?
View the inflate(XmlPullParser Parser, @nullable ViewGroup root, Boolean attachToRoot) method.
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) { synchronized (mConstructorArgs) { final Context inflaterContext = mContext; final AttributeSet attrs = Xml.asAttributeSet(parser); View result = root; advanceToRootNode(parser); // Get the name of the root node, such as LinearLayout, FrameLayout, etc. final String name = parser.getName(); If (TAG_MERGE. Equals (name)) {/ / the name of the root node is the merge the 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 // Get the root view object of the XML layout, such as LinearLayout object, FrameLayout object, etc. final View temp = createViewFromTag(root, name, inflaterContext, attrs); ViewGroup.LayoutParams params = null; if (root ! = null) { // 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); } } // Inflate all children under temp against its context. rInflateChildren(parser, temp, attrs, true); // 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; } } return result; }}Copy the code
Let’s ignore the merge root, because this is a special root. It helps to solve common problems by analyzing common situations first.
2.1.1 Analysis of the first group values when the root node is not Merge
\
Whether the root node is merge | Combination of the values | ViewGroup root | boolean attachToRoot |
---|---|---|---|
no | The first group | notNull | false |
View result = root; Assign the root value to View result, then the result value is null.
On line 21, if (root! = null), and cannot enter the if statement.
In line 23 params = root. GenerateLayoutParams (attrs); LayoutParams (ViewGroup.LayoutParams); The corresponding values, such as layout_width and layout_height, are converted to the field values in the layout parameter object, such as width and height. The corresponding source code in ViewGroup is as follows:
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(getContext(), attrs);
}
public LayoutParams(Context c, AttributeSet attrs) {
TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_Layout);
setBaseAttributes(a,
R.styleable.ViewGroup_Layout_layout_width,
R.styleable.ViewGroup_Layout_layout_height);
a.recycle();
}
Copy the code
This method, when overwritten by a subclass of ViewGroup, will parse more of the layout parameters in the XML, such as the layout_weight and layout_gravity parameters when overwritten in LinearLayout.
In line 24 if (! AttachToRoot (true because attachToRoot is false), enter the if branch, at line 25 temp.setLayoutParams(params); , sets the layout parameters to the root node control object.
On line 34, if (root! = null && attachToRoot) if attachToRoot is false, the root node control object and layout parameters are not set to root.
In 39 if (root = = null | |! AttachToRoot is false, so the value is true. The if statement goes to line 40 result = temp. That is, the root control object is assigned to the Result variable.
On line 43, return result; , returns the root node object.
To sum up:
Whether the root node is merge | Combination of the values | ViewGroup root | boolean attachToRoot | The return value |
---|---|---|---|---|
no | The first group | notNull | false | What is returned is the View object of the root node of the XML layout, and it has the layout parameters on the root node. |
2.1.2 Analysis of the values of the second group when the root node is not Merge
Whether the root node is merge | Combination of the values | ViewGroup root | boolean attachToRoot |
---|---|---|---|
no | The second group | notNull | true |
We’ll just start at line 24, because the code flow is exactly the same as the first set of values.
On line 24, if (! AttachToRoot is false because attachToRoot is true and the if branch is not entered, that is, the layout parameter is not set to the root node control object.
On line 34, if (root! = null && attachToRoot) if root is not null and attachToRoot is true root.addView(temp, params); That is, the root node control object and layout parameters are set to root.
In 39 if (root = = null | |! Because root is not null and attachToRoot is not false, the branch will not be entered.
On line 43 return result; Result is assigned root in line 5 and is not reassigned, so root is returned.
To summarize:
Whether the root node is merge | Combination of the values | ViewGroup root | boolean attachToRoot | The return value |
---|---|---|---|---|
no | The second group | notNull | true | The return is that the root node was addedView Object and layout parametersroot object |
2.1.3 Analysis of group 3 values when the root node is not Merge
Whether the root node is merge | Combination of the values | ViewGroup root | boolean attachToRoot |
---|---|---|---|
no | The third group | null | false |
View result = root; Assign the root value to View result, then result value is null.
On line 21, if (root! Viewgroup.layoutparams = null; if (root) = null; if (root) = false;
On line 34, if (root! = null && attachToRoot (false) if branch is not entered, that is, root control object and layout parameters are not set to root.
In 39 if (root = = null | |! AttachToRoot (true because root is null, enter if branch at line 40, result = temp; , assigns the root node control object temp to the View result variable.
On line 43 return result; Who comes back? The root node control object with no layout parameters is returned.
In summary: \
Whether the root node is merge | Combination of the values | ViewGroup root | boolean attachToRoot | The return value |
---|---|---|---|---|
no | The third group | null | false | The root node with no layout parameter information is returnedView object |
2.1.4 Analysis of group 4 values when the root node is not Merge
Whether the root node is merge | Combination of the values | ViewGroup root | boolean attachToRoot |
---|---|---|---|
no | The fourth group | null | true |
We’ll start at line 34 directly, because the previous code flow is exactly the same as the third group.
In line 34, if (root! = null && attachToRoot (false) if branch is not entered, that is, root control object and layout parameters are not set to root.
In 39 if (root = = null | |! AttachToRoot (true because root is null, enter if branch at line 40, result = temp; , assigns the root node control object temp to the View result variable.
On line 43 return result; Who comes back? The root node control object with no layout parameters is returned.
The fourth set of values is the same as the third set of values.
2.1.5 Merge Root Node
In line 9, if (tag_merge.equals (name)) is the root of the merge and enters the if branch.
In line 11 if (root = = null | |! If root is null or attachToRoot is false, the judgment will be true. After entering the if statement, an exception will be thrown.
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
Copy the code
This reminds us that when the root node is merge, root must not be null and attachToRoot must be true.
On line 43 return result; , and result in line 5 View result = root; Is assigned root.
To summarize the value situation:
Whether the root node is merge | Combination of the values | ViewGroup root |
boolean attachToRoot |
The return value |
---|---|---|---|---|
no | The first group | notNull |
false |
What is returned is the View object of the root node of the XML layout, and it has the layout parameters on the root node. |
no | The second group | notNull |
true |
The return is that the root node was addedView Object and layout parametersroot Object. |
no | The third group | null |
false |
The root node with no layout parameter information is returnedView Object. |
no | The fourth group | null |
true |
The root node with no layout parameter information is returnedView Object. |
is | notNull (must) |
true (must) |
Returns theroot Object. |
2.2 Actual Application
2.2.1 Customizing the Control Layout
The layout custom_view_layout.xml that needs to be filled is as follows:
<? The XML version = "1.0" encoding = "utf-8"? > <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="wrap_content"> <ImageView android:layout_margin="16dp" android:id="@+id/icon" android:layout_gravity="center_vertical" app:srcCompat="@mipmap/ic_launcher" android:layout_width="wrap_content" Android :layout_height="wrap_content" /> <TextView Android :id="@+id/title" Android :text=" title" android:textColor="@android:color/black" android:textSize="16sp" android:layout_gravity="center_vertical" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <View android:layout_weight="1" android:layout_width="0dp" android:layout_height="0dp" /> <Switch android:layout_marginEnd="16dp" android:layout_gravity="center_vertical|end" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout>Copy the code
The CustomView class is as follows:
public class CustomView extends LinearLayout { public CustomView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); inflate(context, R.layout.custom_view_layout, this); }}Copy the code
The inflate() method here is the static method of the View class:
public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) {
LayoutInflater factory = LayoutInflater.from(context);
return factory.inflate(resource, root);
}
Copy the code
Internally, we call the first inflate() method of the LayoutInflater:
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) { return inflate(resource, root, root ! = null); }Copy the code
ViewGroup root
Don’t fornull
且 boolean attachToRoot
为 true
, the root node is notmerge
Tag, so this corresponds to the second set of cases in the table, which returns the added root nodeView
Object and layout parametersroot
Object, that is, root nodeView
The object has been added inroot
Object inside. Here, we use the Layout Inspector tool of Android Studio (open in Tools -> Layout Inspector) to look at the Layout:You can see the repeated layout. We know that,merge
Tags can be used to optimize repeated layouts.
Now let’s change the layout file to custom_merge_view_layout.xml:
<? The XML version = "1.0" encoding = "utf-8"? > <merge xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <ImageView android:layout_margin="16dp" android:id="@+id/icon" android:layout_gravity="center_vertical" app:srcCompat="@mipmap/ic_launcher" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:id="@+id/title" Android :text=" title" Android :textColor="@android:color/black" Android :textSize="16sp" android:layout_gravity="center_vertical" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <View android:layout_weight="1" android:layout_width="0dp" android:layout_height="0dp" /> <Switch android:layout_marginEnd="16dp" android:layout_gravity="center_vertical|end" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </merge>Copy the code
Fill in the code with the modified layout:
public class CustomMergeView extends LinearLayout { public CustomMergeView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); inflate(context, R.layout.custom_merge_view_layout, this); }}Copy the code
Use the layout viewer again to view the layout:You can see the use ofmerge
Tags eliminate duplicate layouts.
2.2.2 Fragment Filling layout
Create a new FragmentInflateActivity. Java file:
public class FragmentInflateActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.fragment_inflate_activity); getSupportFragmentManager().beginTransaction() .add(R.id.fl_container, MyFragment.newInstance()) .commit(); } public static void start(Context context) { Intent starter = new Intent(context, FragmentInflateActivity.class); context.startActivity(starter); }}Copy the code
The corresponding fragment_inflate_activity.xml:
<? The XML version = "1.0" encoding = "utf-8"? > <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:background="@android:color/holo_purple" android:id="@+id/fl_container" android:padding="8dp" android:layout_width="match_parent" android:layout_height="match_parent" />Copy the code
MyFragment. Java is as follows:
public class MyFragment extends Fragment { private static final String TAG = "MyFragment"; @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { Log.d(TAG, "onCreateView: container=" + container); return inflater.inflate(R.layout.my_fragment, container, false); } public static MyFragment newInstance() { Bundle args = new Bundle(); MyFragment fragment = new MyFragment(); fragment.setArguments(args); return fragment; }}Copy the code
My_fragment. XML is as follows:
<? The 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="200dp" android:gravity="center" android:background="@android:color/holo_green_light" android:orientation="vertical"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="MyFragment" android:textAllCaps="false" android:textSize="24sp" /> </LinearLayout>Copy the code
Effect after operation:
Notice the line of log printed in the onCreateView() method:
D/MyFragment: onCreateView: container=android.widget.FrameLayout{d613ebe V.E...... . ID 0, 0, 0, 0 # 7 f07005c app: ID/fl_container}Copy the code
Container is a FrameLayout. Its ID is R.i.D.fl_container. Inflate (int resource, ViewGroup root, Boolean attachToRoot) corresponds to FrameLayout in the FragmentInflateActivity layout.
The padding here corresponds to the second group, which returns the View object of the root node of the XML layout with the layout parameters on the root node.
What if you changed the third argument to attachToRoot to true in the inflate method in onCreateView?
return inflater.inflate(R.layout.my_fragment, container, true);
Copy the code
Will crash after running:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.layoutinflaterinflateparamstudy, PID: 23076
java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.
at android.view.ViewGroup.addViewInner(ViewGroup.java:5168)
at android.view.ViewGroup.addView(ViewGroup.java:4997)
at android.view.ViewGroup.addView(ViewGroup.java:4937)
at android.view.ViewGroup.addView(ViewGroup.java:4910)
at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManagerImpl.java:887)
Copy the code
Why did it crash? Let’s look at it in detail:
inflater.inflate(R.layout.my_fragment, container, true); Corresponding to the first set of values, the return is the root object with the View object and layout parameters added to the root node. That is, the container returned to fill the root object is the FrameLayout object with id R.i.D.fl_container.
In fact, the FragmentManager is responsible for adding the View object returned by the onCreateView() method to the FrameLayout object with id R.i.D.fl_container.
Error: FrameLayout = parent; error: FrameLayout = parent; error: FrameLayout = parent; The specified child already has a parent. You must call removeView() on the child’s parent first. In Android, a View can have only one parent.
What happens if we change the inflate() method in the onCreateView() method to correspond to the third group:
return inflater.inflate(R.layout.my_fragment, null, false);
Copy the code
If you look closely, there is a yellow warning message in the null space:
Avoid passing null as the view root (needed to resolve layout parameters on the inflated layout's root element)
Copy the code
Effect after operation:
We did set the root node LinearLayout in my_fragment.xml to 200dp, why didn’t it work?
Because of the third set of cases, we return the root View object with no layout parameter information, that is, we set the height of 200dp layout parameter information is not set to the root View object filled. That’s why an amber alert was issued.
Since there are no layout parameters, why does the width and height of the root View object fill the screen after it is filled?
This is because in the addView() method of the ViewGroup class,
public void addView(View child, int index) {
if (child == null) {
throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
}
LayoutParams params = child.getLayoutParams();
if (params == null) {
params = generateDefaultLayoutParams();
if (params == null) {
throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null");
}
}
addView(child, index, params);
}
Copy the code
In line 6, found the child params is null, the layout of the View parameter line 7 will go, by generateDefaultLayoutParams () to generate the default layout parameters; And here we ViewGroup is FrameLayout, FrameLayout rewrite the generateDefaultLayoutParams () method is as follows:
protected LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
}
Copy the code
2.2.3 RecyclerView entry filling layout
I won’t go into detail here, but it’s very similar to the Fragment fill layout. Here are just a few highlights.
In the onCreateViewHolder() method of the RecyclerView adapter:
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
Log.d(TAG, "onCreateViewHolder: parent=" + parent);
View view = layoutInflater.inflate(R.layout.recycle_item, parent, false);
return new ViewHolder(view);
}
Copy the code
The following information is displayed:
onCreateViewHolder: parent=androidx.recyclerview.widget.RecyclerView{2e97f7b VFED..... .F.... ID 0, 0-1440204 8 # 7 f070081 app: ID/recycler_view}Copy the code
ViewGroup parent is a RecyclerView object.
If you change the third parameter of the inflate() method to true, the program crashes:
E/AndroidRuntime: FATAL EXCEPTION: main Process: com.example.layoutinflaterinflateparamstudy, PID: 26322 java.lang.IllegalStateException: ViewHolder views must not be attached when created. Ensure that you are not passing 'true' to the attachToRoot parameter of LayoutInflater.inflate(... . boolean attachToRoot) at androidx.recyclerview.widget.RecyclerView$Adapter.createViewHolder(RecyclerView.java:7080) at androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:6235) at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6118) at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6114) at androidx.recyclerview.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:2303) at androidx.recyclerview.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1627) at androidx.recyclerview.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1587) at androidx.recyclerview.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:665) at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep2(RecyclerView.java:4134) at androidx.recyclerview.widget.RecyclerView.dispatchLayout(RecyclerView.java:3851) at androidx.recyclerview.widget.RecyclerView.onLayout(RecyclerView.java:4404)Copy the code
In fact, when to add child View to RecyclerView, is responsible by RecyclerView, developers only need to create child View to RecyclerView.
2.2.4 AlertDialog Fills a custom layout
In the previous example, the ViewGroup parent, the second argument to the inflate() method, is passed to null, causing the problem of losing layout parameters. However, in the AlertDialog custom layout, the ViewParent does not exist and must be passed to NULL.
View view = LayoutInflater.from(MainActivity.this).inflate(R.layout.dialog, null);
AlertDialog alertDialog = new AlertDialog.Builder(MainActivity.this)
.setTitle("AlertDialog")
.setView(view)
.create();
alertDialog.show();
Copy the code
This corresponds to the third set of cases, which return the root View object with no layout parameter information. That is, the layout parameter information of the root node is lost in r.layout. dialog. The AlertDialog is responsible for creating the layout parameter information.
3. The last
This article takes a close look at the various parameters of the inflate() method and illustrates the various parameter passing cases as examples. I hope I can help you. Like the students remember to like + attention, if you need more Android learning information, you can pay attention to the wechat public number: Android Lao PI. The code has been uploaded to Github address.