原文链接: Android应用setContentView与LayoutInflater加载解析机制源码分析
1.Android 的setContentView大致流程
对于setContentView这个方法,真的是可以说习以为常,但是却没有真正的理解过这个方法,今天看了这篇文章,也觉得受益匪浅。
首先说下如何将xml文件加入activity当中,首先认识一下几个重要的对象。
1.window:是一个抽象类,提供了绘制窗口的一组通用API
2.phonewindow :继承自window,内部包含一个decorview,用于加载xml视图文件
3.decorView:activity的rootView,本身继承FramLayout,其内部包含2个子view,一个stubView,用于展示actionbar,另一个是id为content的FramLayout,用于加载传递进来的xml视图文件。
如图所示(此图甚好,套下原作者的图):
简化下源码中的代码,大致流程:
1、当activity生成的时候,会初始化一个phoneWindow,继承自Window类。
2、生成phonewindow的时候会去判断是否有decorView存在,若不存在则去new一个对象,同时会去查找xml文件定义的主题,如(android:theme="@android:style/Theme.NoTitleBar")和在代码中设置的例如(requestWindowFeature(Window.FEATURE_NO_TITLE);),用于修饰decorView的窗口如何展示。
3.然后利用( decor.addView(xmlView, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)))加入到decorView中。
注意点:
不要以为当decorView被填充之后,视图便被展示在activity,以上过纯属于一个添加操作。其显示操作是在ActivityThread的handleResumeActivity方法中将它激活并显示。
2.LayoutInflater加载
对于layoutInflater,最常见的莫过于在适配器和自定义view中为了加载xml视图文件的时候出现。
常见方法:
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {}
其实第一中方法,在内部还是经过处理后调用了第二个方法,第一个只是加了一层封装而已。
参数解读:第一个参数是xml视图资源id;第二个参数是第一个resource生成view的父view;第三个参数是是否将resource视图加入到root中,若true,返回加入resource视图的root,若false,返回root。
inflate(xmlId, null); 只创resource建的View,然后直接返回resource。
inflate(xmlId, parent); 创建resource的View,然后执行root.addView(resource, params);最后返回root。
inflate(xmlId, parent, false); 创建resource的View,然后执行resource.setLayoutParams(params);然后再返回resource。
inflate(xmlId, parent, true); 创建resource的View,然后执行root.addView(resource, params);最后返回root。
inflate(xmlId, null, false); 只创建resource的View,然后直接返回temp。
inflate(xmlId, null, true); 只创建resource的View,然后直接返回temp。
典型案例如适配器中加载xml文件:
public View getView(int position, View convertView, ViewGroupparent){
if (convertView ==null) {
convertView =LayoutInflater.from(mContext).inflate(R.layout.item_view, parent,false);
}
//dosomething…
return converView;
}
此处设置root为parent,attachToRoot为false,代表listview的item将以xmlId作为itemView,不需要返回添加了resorceView的parent,这里要知道parent是listview,若item是listview那不是乱套了。
还有要注意的一点:如listview_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical
android:layout_width="match_parent"
android:layout_height="100dp">
<TextView
android:id="@+id/textView"
android:textSize="30sp"
android:gravity="center"
android:background="#ffcccc"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
此为adapter的一个item的xml文件,在父view中,我们设置了 android:layout_width="match_parent",android:layout_height="100dp",我们理解一下android:layout_width和android:layout_height这2个属性,其实这2个属性并非是为当下的view赋值,而是为其父view赋值,就如layout_gravity与gravity的区别,前者是相对于父view的位置状态,而后者是相对于自身的子view的位置状态。如此可想而知,我们的android:layout_width和android:layout_height这2个属性是相对于父view的一种设置。
查看源码也可以看到:
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
····
// Temp is the root view that was found in the xml
//xml文件中的root view,根据tag节点创建view对象
final View temp = createViewFromTag(root, name, attrs, false);
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
//根据root生成合适的LayoutParams实例
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
//如果attachToRoot=false就调用view的setLayoutParams方法
temp.setLayoutParams(params);
}
}
···
}
此处可以看到类中有一个temp,此参数便是listview_item,params便是listview_item的属性对象,当root不为null的时候,就生成params,并复制给temp,这样一来listview_tem就可以正常显示,当root为null的时候,就不能正常显示。
总结一波:
想要正常显示xml时,设置root不为null,对于xml不需要设定固定宽高的时候,可选择设置root为null。