在Activity中,我们都会使用该方法设置自己需要的xml布局,比如:
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(getLayoutId());
}
下面分析一下,自己写的布局,是怎么通过setContentView()
来生效的。
首先放一张Android布局图:
Android窗口布局.png
其实我们平常写的layout布局,是加载到上图红色的框(ContentView)中。
- Window是一个抽象类,定义了一些规范(API),其中就有
setContentView方法
public abstract class Window{
...
/**
* Convenience for
* {@link #setContentView(View, android.view.ViewGroup.LayoutParams)}
* to set the screen content from a layout resource. The resource will be
* inflated, adding all top-level views to the screen.
*
* @param layoutResID Resource ID to be inflated.
* @see #setContentView(View, android.view.ViewGroup.LayoutParams)
*/
public abstract void setContentView(@LayoutRes int layoutResID);
...
}
- PhoneWindow 继承自 Window,属于window的具体实现类。
- DecorView 继承自 FrameLayout ,是所有应用窗口的根View。其中包含一个显示标题的TitleView,和一个包含内容的ContentView
public class DecorView extends FrameLayout
源码分析
1,首先从我们经常设置布局的setContentView()
分析:
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(getLayoutId());
}
这里的setContentView
最终调用的是Activity
的setContentView
。
2,Activity.java 中的setContentView
public void setContentView(@LayoutRes int layoutResID) {
//这里的getWindow() , 就是拿到了mWindow 对象
//并调用了window的setContentView方法
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
在Activity中,mWindow 是由下面的的代码创建:
final void attach(Context context, ActivityThread aThread,
...
//在这里创建了PhoneWindow,
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
...
因为PhoneView是Window的具体实现类,所以其实调用的是PhoneView中的setContentView
3,PhoneWindow.java 中的 setContentView
方法:
@Override
public void setContentView(int layoutResID) {
// mContentParent,也就是我们设置Layout的父布局
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 {
//在这里进行布局初始化,mContentParent 是我们传入布局的父布局!
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
跟到 installDecor()方法(注意:该方法其实就是在mContentParent == null
的时候,调用,也就是为了创建mContentParent
):
注意: generateLayout(mDecor);
private void installDecor() {
.......
if (mContentParent == null) {
//注意这个方法
mContentParent = generateLayout(mDecor);
mDecor.makeOptionalFitsSystemWindows();
final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
R.id.decor_content_parent);
if (decorContentParent != null) {
mDecorContentParent = decorContentParent;
mDecorContentParent.setWindowCallback(getCallback());
if (mDecorContentParent.getTitle() == null) {
mDecorContentParent.setWindowTitle(mTitle);
}
final int localFeatures = getLocalFeatures();
for (int i = 0; i < FEATURE_MAX; i++) {
if ((localFeatures & (1 << i)) != 0) {
mDecorContentParent.initFeature(i);
}
}
mDecorContentParent.setUiOptions(mUiOptions);
.............
}
跟到generateLayout()方法中:
protected ViewGroup generateLayout(DecorView decor) {
....
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;
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);
//ID_ANDROID_CONTENT 为:com.android.internal.R.id.content;
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
......
}
在这个方法中,生成了contentParent
/**
* The ID that the main layout in the XML layout file should have.
*/
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
这里的R.id.content
也就是上面generateLayout
中layoutResource
布局文件中的id,例如打开R.layout.screen_simple
可以看到:
所以,其实我们写的布局,最后是添加到了id为content的FrameLayout中。
总结:
- Activity 包含了一个PhoneWindow
- PhoneWindow 继承自 Window
- Activity 通过
setContentView
将布局文件设置到了PhoneWindow上 - PhoneWindow 中又包含了 DecorView ,也就是布局最终添加到了 DecorView 上。
网友评论