请尊重原创,转载请注明出处【tianyl_Melody】的博客
在Android中,我们的布局通常是通过先写在xml文件中,然后通过setContentView这个方法,就能将布局加载到对应的Activity中的,那么这个xml的布局文件到底在加载到Activity中,做了哪些事情呢,今天,就从setContentView这个方法出发,看看xml文件是如何加载到屏幕上的。
首先,从activity的setContentView出发
public void setContentView(int layoutResID) { getWindow().setContentView(layoutResID); initWindowDecorActionBar(); } public Window getWindow() { return mWindow; }通过这两个方法,我们可以看到它首先是拿到了一个mWindow对象,然后调用了这个mWindow对象的setContentView方法。而Window是一个抽象类,它仅有一个实现类,那就是PhoneWindow。
可见,在PhoneWindow中,首先会判断mContentParent是否为null,如果为null,那么会调用installDecor,可以猜测这是一个创建mContentParent的方法,如果不为null,那么就移除掉所有的子view进行复用。
在installDecor方法中,首先会判断mDecor是否为null,如果mDecor对象也为null,那么就会调用generateDecor方法。
if (mDecor == null) { mDecor = generateDecor(); mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); mDecor.setIsRootNamespace(true); if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) { mDecor.postOnAnimation(mInvalidatePanelMenuRunnable); } }在generateDecor方法中,会创建一个PhoneWindow的内部类DecorView对象;
protected DecorView generateDecor() { return new DecorView(getContext(), -1); }在创建完DecorView之后,会对DecorView的属性做一些设置;然后再判断mContentParent是否为null,如果mContentParent为null,那么把刚刚创建的mDecor作为参数传入,调用generateLayout方法;
mContentParent = generateLayout(mDecor)generateLayout这个方法主要就是对window的一些属性进行设定。首先,它会拿到我们在xml中设置的window属性;
TypedArray a = getWindowStyle(); public final TypedArray getWindowStyle() { synchronized (this) { if (mWindowStyle == null) { mWindowStyle = mContext.obtainStyledAttributes( com.android.internal.R.styleable.Window); } return mWindowStyle; } }然后再对这些属性进行实现,比如我们经常使用的Window_windowNoTitle:
if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) { requestFeature(FEATURE_NO_TITLE); }它会通过requestFeature(int featureId)这个方法来将我们设置的window属性保存起来,将所有属性保存完之后,就会通过这个方法进行取出:
int features = getLocalFeatures();然后加载对应的xml布局文件,比如:
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) { layoutResource = R.layout.screen_swipe_dismiss; }做完这些事情之后,就开始给DecorView添加布局了:
mDecor.startChanging(); View in = mLayoutInflater.inflate(layoutResource, null); decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); mContentRoot = (ViewGroup) in; ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);首先,mDecor.startChanging():
public void startChanging() { mChanging = true; }然后,会将我们之前拿到的xml文件填充出来,并且通过addView添加到decor中:
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));其中,LayoutParams我们可以看到都是MATCH_PARENT。然后填充出来是view会当成mContentRoot,而contentParent就是我们之前的xml文件中,id为content的布局。
最后,会将我们构建的contentParent返回出去:
return contentParent;这些就是我们installDecor做的主要过程了,接下来就是这段代码:
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) { final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID, getContext()); transitionTo(newScene); } else { mLayoutInflater.inflate(layoutResID, mContentParent); }首先会判断FEATURE_CONTENT_TRANSITIONS这个值,FEATURE_CONTENT_TRANSITIONS是Android中的过渡,简单理解就是界面跳转的一些动画,如果没有,那么就调用inflate方法,并且把外界传入的layoutResID和自己构建出来的mContentParent作为对象传入。
在上面的installDecor中,最后调用到了inflate方法,而inflate最终会调用到这个方法:
public View inflate(int resource, 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(); } }这里,会将Resources传入,拿到一个对应的xml的解析器parser,然后再调用inflate方法:
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) { //首先会拿到xml布局中的属性 final AttributeSet attrs = Xml.asAttributeSet(parser); ...... //然后不断的循环,找到xml布局中的根节点 while ((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) { // Empty } //然后拿到这个跟节点的名字 final String name = parser.getName(); ...... //如果这个跟节点不是merge,那么就会生成对应的根节点 final View temp = createViewFromTag(root, name, attrs, false); ...... //然后通过root的generateLayoutParams方法,拿到对应的属性,设置到刚生成的根节点布局中 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); } }其中root就是在之前的方法installDecor中创建并且返回出来的contentParent对象,而根节点就是我们自己写的布局文件的最外层布局,比如LinearLayout。然后通过contentParent来解析出LinearLayout的对应属性,通过setLayoutParams给LinearLayout设置上。
1、在setContentView中,首先,会判断mDecor是否为null,如果为null,那么就会new一个DecorView给mDecor赋值; 2、然后在getWindowStyle方法中拿到xml中设置的window属性通过requestFeature方法保存到对象中。 3、接下来通过getLocalFeatures取出window的属性对象,并且根据对象的值,加载对应的布局文件。 4、通过mLayoutInflater.inflate填充出window对应的布局文件,并且通过addview添加到mDecor中,作为mDecor的child对象。而填充出来的布局文件就是contentParent,返回出来给外面使用。 5、在inflate中,首先会拿到从setContentView中传入的布局id,并且通过这个id,得到对应的xml解析器。 6、在xml解析器中,拿到跟布局,然后创建出跟布局的根节点,通过contentParent的generateLayoutParams方法,将从xml解析器中拿到的属性对象attrs传入,拿到对应的属性参数,最后将这个属性参数设置给布局的根节点temp。
