我们一般写界面都是从xml布局文件开始,写完布局,预览感觉差不多了,然后就在Activity的onCreate()里面调用setContentView(R.layout.xxx),其他的什么都不用写,项目跑起来,我们的布局就可以显示了。那么,为什么我们写的布局能够显示出来呢?可能我们不会想为什么,因为写习惯了,就像1+1=2 一样太正常了。但是要想更清楚的理解View的绘制过程,我们必须要知道为什么。
首先看看setContentView()的源码(Activity的和AppCompatActivity不一样):
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
还有两个重载方法:
public void setContentView(View view) {
getWindow().setContentView(view);
initWindowDecorActionBar();
}
public void setContentView(View view, ViewGroup.LayoutParams params) {
getWindow().setContentView(view, params);
initWindowDecorActionBar();
}
这三个方法都是Window类的抽象方法,而且前两个其实最后都是调用第三个方法,getWinDow()在Activity的事件分发已经说到,其实就是PhoneWindow.所以接下来直接看PhoneWindow的setContentView(View view, ViewGroup.LayoutParams params):
public void setContentView(View view, ViewGroup.LayoutParams params) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
//mContentParent 就是我们自己写的布局文件的根布局的父布局
if (mContentParent == null) {
//初始化DecorView,mContentParent
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
//移除所有的子View
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
view.setLayoutParams(params);
final Scene newScene = new Scene(mContentParent, view);
transitionTo(newScene);
} else {
//添加我们自己定义的布局到mContentParent
mContentParent.addView(view, params);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
看一下 PhoneWindow的installDecor():
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
//创建一个DecorView
mDecor = generateDecor(-1);
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
} else {
//绑定window
mDecor.setWindow(this);
}
if (mContentParent == null) {
//给mContentParent 赋值
mContentParent = generateLayout(mDecor);
.......
}
}
看一下generateLayout(mDecor):
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
//根据我们设置的主题,设置样式和标记
TypedArray a = getWindowStyle();
.......
//theme中是否透明状态栏
if (a.getBoolean(R.styleable.Window_windowTranslucentStatus,
false)) {
setFlags(FLAG_TRANSLUCENT_STATUS, FLAG_TRANSLUCENT_STATUS
& (~getForcedWindowFlags()));
}
//theme中是否透明导航栏
if (a.getBoolean(R.styleable.Window_windowTranslucentNavigation,
false)) {
setFlags(FLAG_TRANSLUCENT_NAVIGATION, FLAG_TRANSLUCENT_NAVIGATION
& (~getForcedWindowFlags()));
}
.......
//如果没有修改StatusBar颜色,设置默认值不透明
if (!mForcedStatusBarColor) {
mStatusBarColor = a.getColor(R.styleable.Window_statusBarColor, 0xFF000000);
}
//如果没有修改NavigationBar颜色,设置默认值不透明
if (!mForcedNavigationBarColor) {
mNavigationBarColor = a.getColor(R.styleable.Window_navigationBarColor, 0xFF000000);
}
......
// Inflate the window decor.
//根据不同的主题和样式,选择不同的系统内置布局文件
int layoutResource;
//拿到我们在onCreate()里面,setContentView之前通过requestWindowFeature()设置的features
int features = getLocalFeatures();
// System.out.println("Features: 0x" + Integer.toHexString(features));
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
} 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);
//FEATURE_NO_TITLE样式
} 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.
//没有设置任何装饰(feature)时选用的布局
layoutResource = R.layout.screen_simple;
// System.out.println("Simple!");
}
mDecor.startChanging();
//添加内置的布局到DecorView
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
//找到内置布局里面的id为ID_ANDROID_CONTENT的ViewGroup,并用contentParent 变量记录
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
.......
mDecor.finishChanging();
//返回contentParent ,也就是 mContentParent为id为ID_ANDROID_CONTENT的ViewGroup
return contentParent;
}
在我的目录:C:\Users\Administrator\AppData\Local\Android\Sdk\platforms\android-27\data\res\layout,可以找到screen_simple.xml文件:
<?xml version="1.0" encoding="UTF-8"?>
<LinearLayout android:orientation="vertical" android:fitsSystemWindows="true"
android:layout_height="match_parent"
android:layout_width="match_parent"
xmlns:android="http://schemas.android.com/apk/res/android">
<ViewStub android:layout_height="wrap_content" android:layout_width="match_parent"
android:theme="?attr/actionBarTheme"
android:layout="@layout/action_mode_bar"
android:inflatedId="@+id/action_mode_bar"
android:id="@+id/action_mode_bar_stub"/>
<FrameLayout android:layout_height="match_parent"
android:layout_width="match_parent"
android:id="@android:id/content"
android:foreground="?android:attr/windowContentOverlay"
android:foregroundGravity="fill_horizontal|top"
android:foregroundInsidePadding="false"/>
</LinearLayout>
可以看出,我们如果没有在setContentView之前调用requestWindowFeature(),系统默认就是会帮我们选择screen_simple.xml这个布局。从前面的generateLayout()源码可以看出,DecorView是整个View树的根布局,一般情况下,DecorView里面只有一个LinearLayout,LinearLayout里面包换一个toolbar(ViewStub )和contentParent( FrameLayout ),也就是我们的自定义布局的父布局,然后才是我们通过setContentView添加进去的自定义布局.
说到这里有个问题,DecorView是整个View树的根布局,那么它包不包括StatusBar和NavigationBar呢?
从上面的分析来看应该是不包含的,因为没有看到在哪里添加这两个View.但是通过AndroidStudio的Layout Inspector分析布局竟然是这样的:
可以看到DecorView有一个系统内置的screen_simple.xml布局,它里面的content下面就是我们的自定义布局.但是除了这个内置的Linearlayout以外,还有navigationBarBackground和statusBarBackground这两个view.从名字可以看出意思是给navigationBar和statusBar绘制背景的View.那这么说,DecorView是不包含navigationBar和statusBar的,只包含绘制navigationBar和statusBar背景的View,但是这两个view在哪里添加进DecorView,暂时我还没找到.
DecorView.onResourcesLoaded
在generateLayout()中DecorView调用了onResourcesLoaded(mLayoutInflater, layoutResource)来添加内置的screen_simple.xml等布局,看看它 的源码:
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
mStackId = getStackId();
if (mBackdropFrameRenderer != null) {
loadBackgroundDrawablesIfNeeded();
mBackdropFrameRenderer.onResourcesLoaded(
this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable,
mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState),
getCurrentColor(mNavigationColorViewState));
}
//多窗口自由模式下才有DecorCaptionView
mDecorCaptionView = createDecorCaptionView(inflater);
//解析screen_simple.xml等内置布局
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();
}
Activity.initWindowDecorActionBar();
再回头看看Activity的setContentView()里面的第二行:initWindowDecorActionBar()
private void initWindowDecorActionBar() {
Window window = getWindow();
// Initializing the window decor can change window feature flags.
// Make sure that we have the correct set before performing the test below.
window.getDecorView();
if (isChild() || !window.hasFeature(Window.FEATURE_ACTION_BAR) || mActionBar != null) {
return;
}
mActionBar = new WindowDecorActionBar(this);
mActionBar.setDefaultDisplayHomeAsUpEnabled(mEnableDefaultActionBarUp);
mWindow.setDefaultIcon(mActivityInfo.getIconResource());
mWindow.setDefaultLogo(mActivityInfo.getLogoResource());
}
就是初始化Actionbar而已
总结:Activity的setContentView()主要用于Window,DecorView,contentParent一些系统级别的view,界面主题样式的初始化,确定viewTree的层次结构的,把我们自定义的xml布局添加到系统的ViewTree.