LayoutInflater.inflate方法的参数解析
发现一个小问题
项目中需要加载xml文件时,经常使用下面这个方法
(其实View.inflate,setContentView内部都是用LayoutInflater实现的)
View view = LayoutInfalter.from(this).inflate(**.xml, null);
其中第二个root参数显示为@Nullable,但是实际上传入null时,又会飘黄警告:
Avoid passing null as the view root (needed to resolve layout parameters on the inflated layout's root element) less... (Ctrl+F1)
When inflating a layout, avoid passing in null as the parent view, since otherwise any layout parameters on the root of the inflated layout will be ignored.
避免将null作为root参数传入该方法(因为传入null会使根部局的layout参数失效)
接下来就学习一下这个警告是什么意思
源码分析
Android官方API给出了四个方法
// 第一个方法
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
//第二个方法
public View inflate(XmlPullParser parser, @Nullable ViewGroup root) {
return inflate(parser, root, root != null);
}
//第四个方法
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
if (DEBUG) {
Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
+ Integer.toHexString(resource) + ")");
}
final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
可以发现最终这些方法都会调用第三个方法,所以我们只需要关注这个方法。其中,重要的部分可以概括如下
final AttributeSet attrs = Xml.asAttributeSet(parser);
View result = root;
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = root.generateLayoutParams(attrs);
temp.setLayoutParams(params);
if (root != null && attachToRoot) {
root.addView(temp, params);
}
if (root == null || !attachToRoot) {
result = temp;
}
return result;
这段代码首先用result获取了可能存在的父视图,如果root为null,result就向下返回当前view;如果root不为null,就把当前view添加到ViewGroup中,最终形成一个DOM结构,最后把DOM树最顶层的根部局返回。
总结得到了以下结论:
- 如果root为null,attachToRoot将失去作用;
- 如果root不为null,attachToRoot为true,则会将当前view添加到父view中;
- 如果root不为null,attachToRoot为false,则会将布局文件最外层的所有layout属性进行设置,当该view被添加到父view的时候,父view的最外层layout属性将会改为子view的layout属性;
实例验证
首先定义parent.xml为父视图:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fatherLinearLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#00ff00" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="这是父视图"/>
</LinearLayout>
child.xml为子视图:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="200dp"
android:layout_height="400dp"
android:orientation="vertical"
android:background="#ffff00" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="这是子视图"/>
</LinearLayout>
inflate(resource, null)
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
View parentView = LayoutInflater.from(this).inflate(R.layout.parent, null);
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) parentView.getLayoutParams();
if (layoutParams != null){
Log.d(TAG, "before:" + layoutParams.width);
Log.d(TAG, "before:" + layoutParams.height);
}
parentView = LayoutInflater.from(this).inflate(R.layout.child, null);
layoutParams = (LinearLayout.LayoutParams) parentView.getLayoutParams();
if (layoutParams != null){
Log.d(TAG, "after:" + layoutParams.width);
Log.d(TAG, "after:" + layoutParams.height);
}
setContentView(parentView);
}
}
运行结果:
Logcat中没有任何输出信息,表明inflate前后的layout属性均为null
这是因为resource外层没有root,导致layout属性失效,因此默认match_parent;
inflate(resource, root)
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
View parentView = LayoutInflater.from(this).inflate(R.layout.parent, null);
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) parentView.getLayoutParams();
if (layoutParams != null) {
Log.d(TAG, "before:" + layoutParams.width);
Log.d(TAG, "before:" + layoutParams.height);
}
parentView = LayoutInflater.from(this).inflate(R.layout.child, (ViewGroup) parentView);
layoutParams = (LinearLayout.LayoutParams) parentView.getLayoutParams();
if (layoutParams != null) {
Log.d(TAG, "after:" + layoutParams.width);
Log.d(TAG, "after:" + layoutParams.height);
}
setContentView(parentView);
}
}
运行结果:
Logcat中同样没有任何输出信息,这说明inflate(resource, root)和inflate(resource, root, true)只是将resource加到了root之下,并没有改变最外层的layout属性。
inflate(resource, root, false)
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
View parentView = LayoutInflater.from(this).inflate(R.layout.parent, null);
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) parentView.getLayoutParams();
if (layoutParams != null) {
Log.d(TAG, "before:" + layoutParams.width);
Log.d(TAG, "before:" + layoutParams.height);
}
parentView = LayoutInflater.from(this).inflate(R.layout.child, (ViewGroup) parentView, false);
layoutParams = (LinearLayout.LayoutParams) parentView.getLayoutParams();
if (layoutParams != null) {
Log.d(TAG, "after:" + layoutParams.width);
Log.d(TAG, "after:" + layoutParams.height);
}
setContentView(parentView);
}
}
运行结果:
输出信息如下:
2018-11-17 17:28:54.567 4036-4036/com.rimson.c.inflatepractice D/MainActivity: after:600
2018-11-17 17:28:54.567 4036-4036/com.rimson.c.inflatepractice D/MainActivity: after:1200
说明infalte执行后,把子view的layout参数赋给了父view。
总结
- root是给当前要填充的resource外层再加上的一层ViewGroup;
- 在不设置attachToRoot参数的情况下,这一参数的取值默认为(root != null);
- layout参数指的是layout_xxx,例如background等属性仍然有效;
- 如果root为null,attachToRoot将没有意义,此时返回xml的根节点。且该根节点设置任何layout参数都没有意义。这很好理解,因为layout参数是描述自身在父控件或者容器下的属性,这时候没有容器,当然就失效了。
- 如果root不为null,attachToRoot设为true,则会给resource指定一个父布局root,即执行root.addView(resource, params),然后返回root,且root的layout参数会保留。
- 如果root不为null,attachToRoot设为false,则会将root最外层的layout属性设置为resource最外层的layout属性,即执行resource.setLayoutParams(params),然后返回resource。相当于root只提供布局参数。
- 如果可以传递root的情况下,就选择传递root;避免传入null,否则layout参数会失效。