这个方法,其实会最终调用PhoneWindow的setContentView方法,下面看看PhoneWindow的源码:(PhoneWindow.java)
@Override public void setContentView(int layoutResID) { //当mContentParent为null时,则执行installDecor() if (mContentParent == null) { installDecor(); // 1 } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) { mContentParent.removeAllViews(); } if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) { final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID, getContext()); transitionTo(newScene); } else { mLayoutInflater.inflate(layoutResID, mContentParent); //2 } mContentParent.requestApplyInsets(); final Callback cb = getCallback(); if (cb != null && !isDestroyed()) { cb.onContentChanged(); } mContentParentExplicitlySet = true; }
从这个方法可以看出,第一次在Activity的onCreate方法中执行setContentView时,mContentParent是为null的,这是会执行installDecor()方法,下面我们看看,installDecor方法的源码:(PhoneWindow.java)
private void installDecor() { mForceDecorInstall = false; if (mDecor == null) { mDecor = generateDecor(-1);// 3 mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); mDecor.setIsRootNamespace(true); if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures !=0){ mDecor.postOnAnimation(mInvalidatePanelMenuRunnable); } } else { mDecor.setWindow(this); } if (mContentParent == null) { mContentParent = generateLayout(mDecor); // 4 // Set up decor part of UI to ignore fitsSystemWindows if appropriate. mDecor.makeOptionalFitsSystemWindows(); //省略部分代码 // ... } } protected DecorView generateDecor(int featureId) { Context context; if (mUseDecorContext) { Context applicationContext = getContext().getApplicationContext(); if (applicationContext == null) { context = getContext(); } else { context = new DecorContext(applicationContext, getContext()); if (mTheme != -1) { context.setTheme(mTheme); } } } else { context = getContext(); } return new DecorView(context, featureId, this, getAttributes()); // 5 }
从这个方法可以看出,这个方法中,通过generateDecor(-1)方法创建一个mDecor对象,generateDecor方法中,是通过new DecorView(context, featureId, this, getAttributes())的方式来创建DecorView对象的。这里注意一下第三个参数this,这个this,就是代表了当前PhoneWindow,所以,DecorView对象内部是持有PhoneWindow的引用的。看完generateDecor(-1)方法后,我们继续看注释4处,由于第一次加载布局,所以,
mContentParent肯定是为null的,这样就会执行generateLayout(mDecor)方法,下面跟进到这个方法内部,看看具体实现:(PhoneWindow.java)
protected ViewGroup generateLayout(DecorView decor) { // Apply data from current theme. TypedArray a = getWindowStyle(); // 省略部分代码,这些代码都是根据window的样式来进行一些设置 // ... //下面这部分代码是加载window的decor // Inflate the window decor. int layoutResource; int features = getLocalFeatures(); //下面代码是根据features,来确定相应的布局文件被添加到DecorView中 // System.out.println("Features: 0x" + Integer.toHexString(features)); if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) { layoutResource = R.layout.screen_swipe_dismiss; setCloseOnSwipeEnabled(true); } else if((features & ((1<< FEATURE_LEFT_ICON)|(1<< FEATURE_RIGHT_ICON))) != 0) { if (mIsFloating) { TypedValue res = new TypedValue(); getContext().getTheme().resolveAttribute( R.attr.dialogTitleIconsDecorLayout, res, true); layoutResource = res.resourceId; } else { layoutResource = R.layout.screen_title_icons; } // XXX Remove this once action bar supports these features. removeFeature(FEATURE_ACTION_BAR); // System.out.println("Title Icons!"); } else if ((features & ((1<< FEATURE_PROGRESS)|(1<< FEATURE_INDETERMINATE_PROGRESS))) !=0 && (features & (1 << FEATURE_ACTION_BAR)) == 0) { // Special case for a window with only a progress bar (and title). // XXX Need to have a no-title version of embedded windows. layoutResource = R.layout.screen_progress; // System.out.println("Progress!"); } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) { // Special case for a window with a custom title. // If the window is floating, we need a dialog layout if (mIsFloating) { TypedValue res = new TypedValue(); getContext().getTheme().resolveAttribute( R.attr.dialogCustomTitleDecorLayout, res, true); layoutResource = res.resourceId; } else { layoutResource = R.layout.screen_custom_title; } // XXX Remove this once action bar supports these features. removeFeature(FEATURE_ACTION_BAR); } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) { // If no other features and not embedded, only need a title. // If the window is floating, we need a dialog layout if (mIsFloating) { TypedValue res = new TypedValue(); getContext().getTheme().resolveAttribute( R.attr.dialogTitleDecorLayout, res, true); layoutResource = res.resourceId; } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) { layoutResource = a.getResourceId( R.styleable.Window_windowActionBarFullscreenDecorLayout, R.layout.screen_action_bar); } else { layoutResource = R.layout.screen_title; } // System.out.println("Title!"); } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) { layoutResource = R.layout.screen_simple_overlay_action_mode; } else { // Embedded, so no decoration is needed. layoutResource = R.layout.screen_simple; // System.out.println("Simple!"); } mDecor.startChanging(); mDecor.onResourcesLoaded(mLayoutInflater, layoutResource); // 6 ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); // 7 if (contentParent == null) { throw new RuntimeException("Window couldn't find content container view"); } mDecor.finishChanging(); return contentParent; }
generateLayout这个方法内部,是根据widow的一些样式来进行一些设置,根据features来确定添加到DecorView的布局。确定完要被添加的布局文件的id后, 就通过DecorView的onResourcesLoaded方法来将布局文件的view添加到DecorView中。下面来看看DecorView的onResourcesLoaded(LayoutInflater inflater, int layoutResource)
方法的源码:(DecorView.java)
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) { if (mBackdropFrameRenderer != null) { loadBackgroundDrawablesIfNeeded(); mBackdropFrameRenderer.onResourcesLoaded( this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable, mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState), getCurrentColor(mNavigationColorViewState)); } mDecorCaptionView = createDecorCaptionView(inflater); final View root = inflater.inflate(layoutResource, null); if (mDecorCaptionView != null) { if (mDecorCaptionView.getParent() == null) { addView(mDecorCaptionView, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); } mDecorCaptionView.addView(root, new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT)); } else { // Put it below the color views. addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); } mContentRoot = (ViewGroup) root; initializeElevation(); }
这个方法内部会先创建一个mDecorCaptionView,在将传入布局文件加载成view,
接着判断,如果mDecorCaptionView不为null,并且mDecorCaptionView的父控件也不为null,则将mDecorCaptionView添加到其父控件中,并且,将传入的布局文件的view添加到
mDecorCaptionView中,如果mDecorCaptionView为null,则直接将传入的布局文件的view,直接添加到DecorView中。说的这里,大家可能会奇怪,DecorView的addView方法是怎么添加view的,其实大家也不必奇怪,因为DecorView其实是继承FrameLayout的,它也是一个ViewGroup,所以,它的添加view的方法其实就是使用的ViewGroup的添加view的方法。下面是DecorView的类的继承关系:
public class DecorView extends FrameLayout implements RootViewSurfaceTaker,WindowCallbacks{ //省略代码 }
完成了将传入的布局文件的view添加到DecorView中后,将这个布局文件的viwe赋值给了mContentRoot变量。回到PhoneWindow类的generateLayout方法中,看看注释7的位置,将布局文件的view添加到DecorView中以后,通过findViewById的方法将这个ViewGroup赋值给了contentParent,ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT)
最后,返回这个contentParent。到这里,PhoneWindow的installDecor()方法就分析完了,
下面接着看PhoneWindow的setContentView方法的注释2处,mLayoutInflater.inflate(layoutResID, mContentParent); 这里将我们在Activity中的布局文件的id加载到了mContentParent中。这样就完成了将我们自己在Activity的setContetView
方法中设置的布局文件添加到了mContentParent这个ViewGroup中。由于mContentParent是DecorView的子控件,所以,相当于是添加到DecorView中了。
前面分析PhoneWindow的generateLayout方法的注释7处,可能有些人会有疑问,怎么DecorView将一个布局文件作为自己的子view后,怎么就可以通过findViewById(ID_ANDROID_CONTENT)就能找到contentParent呢?这个findViewById方法中传入的id为什么就是ID_ANDROID_CONTENT这个常量呢?我们回到上面的PhoneWindow的ViewGroup generateLayout(DecorView decor) 这个方法内部看看,layoutResource这个变量被赋值的情况,可以看到这个layoutResource可能有如下几种赋值:
R.layout.screen_swipe_dismiss
R.layout.screen_title_icons;
R.layout.screen_progress;
R.layout.screen_custom_title
R.layout.screen_action_bar
R.layout.screen_title
R.layout.screen_simple_overlay_action_mode
R.layout.screen_simple;
这些布局文件位于 frameworks/base/core/res/res/layout/ (参照9.0源码查看)
下面介绍一下常见的系统提供的布局文件
R.layout.screen_title <!-- This is an optimized layout for a screen, with the minimum set of features enabled. --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:fitsSystemWindows="true"> <FrameLayout android:layout_width="match_parent" android:layout_height="?android:attr/windowTitleSize" style="?android:attr/windowTitleBackgroundStyle"> <TextView android:id="@android:id/title" style="?android:attr/windowTitleStyle" android:background="@null" android:fadingEdge="horizontal" android:gravity="center_vertical" android:layout_width="match_parent" android:layout_height="match_parent" /> </FrameLayout> <FrameLayout android:id="@android:id/content" android:layout_width="match_parent" android:layout_height="0dip" android:layout_weight="1" android:foregroundGravity="fill_horizontal|top" android:foreground="?android:attr/windowContentOverlay" /> </LinearLayout>
R.layout.screen_simple(全屏窗口布局文件) <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" android:orientation="vertical"> <ViewStub android:id="@+id/action_mode_bar_stub" android:inflatedId="@+id/action_mode_bar" android:layout="@layout/action_mode_bar" android:layout_width="match_parent" android:layout_height="wrap_content" android:theme="?attr/actionBarTheme" /> <FrameLayout android:id="@android:id/content" android:layout_width="match_parent" android:layout_height="match_parent" android:foregroundInsidePadding="false" android:foregroundGravity="fill_horizontal|top" android:foreground="?android:attr/windowContentOverlay" /> </LinearLayout>
其它布局文件,大家可以看看源码,这些布局文件中,都有一个id为 @android:id/content 的FrameLayout,我们在PhoneWindow的generateLayout的注释7处的ID_ANDROID_CONTENT其实就是指的 @android:id/content这个id,所以,能够通过ID_ANDROID_CONTENT来查找其代表的控件FrameLayout。到这里,也可以明白,为什么这个ID_ANDROID_CONTENT的控件取名叫mContentParent了,因为它就是我们传入的布局的父控件,我们传入的
布局都是被添加到它里面的。
到这里,其实只是完成了Decorview的初始化,并将我们自己的布局添加到了Decorview的mContentParent中,但是DecorView还未显示,也不能接收外界的输入,在完成ActivityThread的handleResumeActivity方法的调用后,回调了Activity的onReusme方法后,在接着调用Activity的makeVisible()方法才将DecorView添加到WindowManager,并将DecorView显示。
通过上面整个流程的分析,我们可以总结一下几点:
1.要去除标题栏是,要在setContentView方法前调用requestWindowFeature(Window.FEATURE_NO_TITLE);这是因为,我们DecorView添加布局,是根据feautres来决定添加的布局文件的id的。只有先设置了feature,DecorView才知道去添加哪个布局作为自己的子view。
2.DecorView中,只有一个直接的子view,这个子view要么是DecorCaptionView要么是根据feature拿到的layoutResource,比如R.layout.screen_title 或 R.layout.screen_simple等,上面列出的几种布局中的一种。
系统的状态栏和底部导航栏是不包含在DecorView中,他们是SystemUI的一部分
发表评论 取消回复