setContentView方法解析
先贴代码
/**
* Set the activity content from a layout resource. The resource will be
* inflated, adding all top-level views to the activity.
*
* @param layoutResID Resource ID to be inflated.
*
* @see #setContentView(android.view.View)
* @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
*/
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
这是Activity中的setContentView方法的实体,我们可以看到干了两件事情:
- 调用了Window的setContentView方法
- 实处化ActionBar
我们主要关注Window的setContentView方法。
从Activity的attach方法可以知道,mWindow的实体是PhoneWindow:
mWindow = PolicyManager.makeNewWindow(this);
通过一系列的查找,我们可以知道,mWindow的实体其实是PhoneWindow。
该类在framework源码的路径为base/policy/src/com/android/internal/policy/impl/PhoneWindow.java。
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null){
installDecor();
} 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);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
以上为PhoneWindow的setContentView源码,我们关注两个点:
- 当mContentParent为null时,初始化decor
- 调用LayoutInflater的inflate方法将我们的布局加入到mContentParent中
那么mContentParent是什么呢?我们继续看installDecor方法:
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor();
//此处省略一些次要代码
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
//此处省略一些次要代码
}
}
非常清晰,两步走:
1. 初始化mDecor
2. 初始化mContentParent
初始化Decor的代码很简单:
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}
DecorView本质上是一个FrameLayout在此不做太多解析。我们主要看一下generateLayout方法:
protected ViewGroup generateLayout(DecorView decor) {
//此处省略很长很长解析Window的feature以及flag的代码
int layoutResource;
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);
} 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();
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);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
//省略一些次要代码。。。
mDecor.finishChanging();
return contentParent;
}
generateLayout方法从开始到mDecor.startChanging()的部分其实只做了一件事情,就是根据mLocalFeatures变量寻找到一个布局的模板。这些模板我们可以在frameworks/base/core/res/res/layout/目录下面找到。这些布局模板有一个共同的特点,就是它们都包含一个id为com.android.internal.R.id.content的FrameLayout,而这个Layout就是放置我们自定义布局内容的父布局,因此我们可以看到代码中使用了一个变量名为contentParent的局部变量来存储该布局,并在最后赋给了mContentParent。
同时我们还可以看到一个名为mContentRoot的变量,改变量存储着那些布局模板的根布局。通常情况下mContentRoot和mContentParent并不像等,也有例外的情况,如:调用requestWindowFeature(Window.FEATURE_SWIPE_TO_DISMISS)使用了screen_swipe_dismiss.xml的布局模板。在不相等的情况下,mContentRoot会包含一些其他的界面元素,如ActionBar、ToolBar等。
对setContentView方法的解析到这里就分析的差不多了。我们总结一下它干了些什么:
1. 初始化DecorView(对于一个window来说最顶级的view)
2. 根据Window的属性选择合适的布局模板add到DecorView中
3. 将我们的布局add到一个id为com.android.internal.R.id.content的ViewGroup中
以上