官方释义
Inflate a new view hierarchy from the specified xml resource
大概意思就是从给定的xml中加载view树。
1. public View inflate(@LayoutRes int resource, @Nullable ViewGroup root);
2. public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot);
3. public View inflate(XmlPullParser parser, @Nullable ViewGroup root)
4. public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot);
对外暴露的有四种重载,但是另外两种需要自行传入XmlPullParser,也就是说需要自行解析xml dom,属于高端操作,一般使用较少。
其中,方法1也是使用了方法2,只不过在root非空的情况下,默认attachToRoot=true,故本文主要对方法2进行分析。
resource顾名思义,资源,从给定的注解来看,要求为layout资源。故参数一般为R.layout.my_layout。
root顾名思义,根布局,这里的根布局可以为空。
当根部局为空时,attachToRoot不生效且生成的view树根节点的LayoutParams为空,即你在layout中定义的根部局的什么宽啊高啊内边距外边距相对位置等一切以layout_为前缀的属性都会失效。为什么呢?我们来看看LayoutParam的定义:
LayoutParams are used by views to tell their parents how they want to be laid out
意思是,LayoutParams是被用来告诉父布局他想要怎么被摆放的。
如果给定的root为空,那么加载出来的view就没有父布局,当然也没必要拥有LayoutParams了!
attachToRoot,即是否要将创建的view树附加(连接)到给定的根布局下。
当不附加到根部局下时,返回的为给定layout的根节点。
当附加到根部局下时,返回的为根部局。
所谓实践出真知,下面来看一组栗子。
首先是一个根布局,我们叫他root_layout
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fl_root"
android:layout_width="300dp"
android:layout_height="300dp"
android:background="@android:color/holo_red_light">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
android:layout_gravity="center" />
</FrameLayout>
接着是一个我们想要加载的布局inflate_layout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/ll_inflate_root"
android:layout_width="200dp"
android:layout_height="200dp"
android:background="@android:color/holo_blue_light">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="im inflated" />
</LinearLayout>
之后我们找个Activity简单实验一下,这里只给出了onCreate方法
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "onCreate");
// activity content部分的根部局,实际上也是使用了LayoutInflater.inflate
// 将布局加载到了contentParent下,感兴趣的可以看下PhoneWindow下的实现
setContentView(R.layout.root_layout);
FrameLayout rootView = findViewById(R.id.fl_root); // 首先拿到root_layout的view树根
// 之后我们对inflateView进行测试,对应下面三种情况
View inflateView = LayoutInflater.from(this).inflate(R.layout.inflate_layout, rootView, true);
// View inflateView = LayoutInflater.from(this).inflate(R.layout.inflate_layout, rootView, false);
// View inflateView = LayoutInflater.from(this).inflate(R.layout.inflate_layout, rootView);
ViewGroup.LayoutParams rootLayoutParams = rootView.getLayoutParams();
ViewGroup.LayoutParams inflateLayoutParams = inflateView.getLayoutParams();
Log.d(TAG, "rootView=" + rootView + " rootView LayoutParams - " + rootLayoutParams);
Log.d(TAG, "inflateView=" + inflateView + "inflateView LayoutParams -" + inflateLayoutParams);
}
D/MainActivity: rootView=android.widget.FrameLayout{6f919a1 V.E...... ......ID 0,0-0,0 #7f07003c app:id/fl_root} rootView LayoutParams - android.widget.FrameLayout$LayoutParams@16527c6
D/MainActivity: inflateView=android.widget.FrameLayout{6f919a1 V.E...... ......ID 0,0-0,0 #7f07003c app:id/fl_root}inflateView LayoutParams -android.widget.FrameLayout$LayoutParams@16527c6
当attachToRoot为true时,返回的为root根节点。且将我们的inflate_layout解析出的view树连接到了给的root下。
D/MainActivity: rootView=android.widget.FrameLayout{6f919a1 V.E...... ......ID 0,0-0,0 #7f07003c app:id/fl_root} rootView LayoutParams - android.widget.FrameLayout$LayoutParams@16527c6
D/MainActivity: inflateView=android.widget.LinearLayout{3547387 V.E...... ......ID 0,0-0,0 #7f07004f app:id/ll_inflate_root}inflateView LayoutParams -android.widget.FrameLayout$LayoutParams@79282b4
当attachToRoot为false时,返回的为inflate_layout的根view,且根据给定的rootView生成了自己的LayoutParam。但view并未被插入到给定根节点的view树中。
D/MainActivity: rootView=android.widget.FrameLayout{6f919a1 V.E...... ......ID 0,0-0,0 #7f07003c app:id/fl_root} rootView LayoutParams - android.widget.FrameLayout$LayoutParams@16527c6
D/MainActivity: inflateView=android.widget.LinearLayout{3547387 V.E...... ......ID 0,0-0,0 #7f07004f app:id/ll_inflate_root}inflateView LayoutParams -null
根部局为空时,也未将生成的view插入到view树中(也不知道要往哪插)。并且与之前不同的是,LayoutParams为空!
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
// .....
if (TAG_MERGE.equals(name)) {
// merge标签的解析
// 必须要attach到根节点
if (root == null || !attachToRoot) {
throw new InflateException(" can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
// 递归的将当前merge标签下的孩子连接到根节点上
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// 其他标签的解析, temp是当前解析的layout的根节点
// Temp is the root view that was found in the xml
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
// 根节点非空时
if (root != null) {
// Create layout params that match root, if supplied
// 从解析出来的attr中获取宽高,创建layoutparam
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
// 如果不attach到root下才进行设置,不然等到addView的时候再进行设置
temp.setLayoutParams(params);
}
}
// Inflate all children under temp against its context.
// 递归的inflate当前解析布局根view下的孩子们
rInflateChildren(parser, temp, attrs, true);
// We are supposed to attach all the views we found (int temp)
// to root. Do that now.
// 如果root不为空且要添加到根布局下时使用addView
if (root != null && attachToRoot) {
// 将temp插入到根节点的最后一个孩子的位置,并设置他的layoutParam为params
root.addView(temp, params);
}
// Decide whether to return the root that was passed in or the
// top view found in xml.
// 如果根为空或者不连接到根节点时返回的是解析的layout.xml的根节点
// 否则返回的为root
if (root == null || !attachToRoot) {
result = temp;
}
}
// .....
return result;
}
}
/**
* Adds a child view with the specified layout parameters.
*
* Note: do not invoke this method from
* {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
* {@link #dispatchDraw(android.graphics.Canvas)} or any related method.
*
* @param child the child view to add
* @param index the position at which to add the child or -1 to add last
* @param params the layout parameters to set on the child
*/
public void addView(View child, int index, LayoutParams params) {
// .....
addViewInner(child, index, params, false);
}
private void addViewInner(View child, int index, LayoutParams params,
boolean preventRequestLayout) {
// .....
// 如果给定的params是空,则为其创建一个,这个具体的实现在各个父亲那,看他们想咋摆孩子
if (!checkLayoutParams(params)) {
params = generateLayoutParams(params);
}
if (preventRequestLayout) {
// 设置布局时不进行requestLayout,即只设置参数不进行刷新,一般用在正在onLayout时
child.mLayoutParams = params;
} else {
child.setLayoutParams(params);
}
// .....
}
大概就是酱紫,简单来说就是有无root决定加载出来的孩子是否是健全(知不知道自己多大应该在哪儿)的孩子,是否attachToRoot就是这孩子是要跟着他爹还是要自己单飞。
最后推荐一篇讲LayoutParams讲的不错的博客自定义控件知识储备-LayoutParams的那些事