转载请说明出处 https://www.jianshu.com/p/3872219cc07a
我们经常在Activity的onCreate中的调用setCotnentView(layoutRes)方法, 这里我们从Activity入手去看看它具体做了哪些操作
从 onCreate 中的 setContentView 开始分析
/**
* Activity.setContentView
*/
public void setContentView(@LayoutRes int layoutResID) {
// 很显然getWindow拿到的是PhoneWidow的实例, 我们直接进去看
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
/**
* PhoneWindow.setContentView
*/
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
// 1. (重点)根据 mContentParent 是否为空来判断是否需要调用 installDecor() 方法
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 {
// 2. (非重点) 这里将我们的布局文件 inflate 到 mContentParent 中
// 所以我们必须搞清楚 mContentParent 是什么
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
好, setContentView 这个方法主要做了三件事情
- 根据 mContentParent 是否为空来判断是否需要调用 installDecor() 方法
- 将 layoutResID inflate 进 mContentParent 中
这里我们主要关注 installDecor 做了哪些事情
/**
* PhoneWindow.installDecor
* 省略了大量无关代码
*/
private void installDecor() {
if (mDecor == null) {
// 1. 创建了 DecorView 的对象
mDecor = generateDecor(-1); // new DecorView(context, featureId, this, getAttributes())
} else {
// 2. 若不为null, 则直接与当前的 Window 对象绑定
mDecor.setWindow(this);
}
// 3. 构建 mContentParent
if (mContentParent == null) {
// generateLayout方法比较重要, 接下来我们分析这个方法
// 看看它是如何构造我们的mContentParent的
mContentParent = generateLayout(mDecor);
}
}
/**
* PhoneWindow.generateLayout
* 省略了大量无关代码
*/
protected ViewGroup generateLayout(DecorView decor) {
......// 这里省略一堆代码
int layoutResource;
int features = getLocalFeatures();
......// 3.1 这里的代码是根据 fetaures 给 layoutResource 赋值
// mDecor要改变的标记位
mDecor.startChanging();
// 3.2 这个方法比较有价值, 它将 layoutResource 布局文件解析成 View 添加到了 DecorView 之中
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
// 3.3 通过findViewById给contentParent赋值
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
......// 这里省略一堆代码
// mDecor改变结束的标记位
mDecor.finishChanging();
return contentParent;
}
3.1看看其中几个 features 所对应的 layoutResource 的布局文件的样式
//screen_simple.xml
<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>
// screen_simple_overlay_action_mode.xml
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<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" />
<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>
无一例外, 无论看几个都有一个id为content 的 FrameLayout
3.2 看看 mDecor.onResourcesLoaded(mLayoutInflater, layoutResource) 做了什么
/**
* DecorView.onResourcesLoaded
*/
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
// Decor的标题View, 这个类表示用于控制自由格式窗口的特殊屏幕元素环境
mDecorCaptionView = createDecorCaptionView(inflater);
// 1 通过 LayoutInflate 来创建这个layoutResource 的 View 实例对象 root
final View root = inflater.inflate(layoutResource, null);
// 2 若不 null 则先添加 mDecorCaptionView, 再向 mDecorCaptionView 中添加 root
if (mDecorCaptionView != null) {
// 将 mDecorCaptionView 添加进 DecorView
if (mDecorCaptionView.getParent() == null) {
addView(mDecorCaptionView, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
// 将从系统的资源文件获取的 root 加到这个 mDecorCaptionView 中
mDecorCaptionView.addView(root,new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
} else {
// 3 若 mDecorCaptionView 为 null, 则直接将 root 加到 DecorView 中
addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
// 4. 强转成 ViewGroup, 传递给 mContentRoot
mContentRoot = (ViewGroup) root;
initializeElevation();
}
/**
* DecorView.inflateDecorCaptionView
*/
private DecorCaptionView inflateDecorCaptionView(LayoutInflater inflater){
final Context context = getContext();
// We make a copy of the inflater, so it has the right context associated with it.
inflater = inflater.from(context);
final DecorCaptionView view = (DecorCaptionView) inflater.inflate(R.layout.decor_caption,
null);
setDecorCaptionShade(context, view);
return view;
}
<com.android.internal.widget.DecorCaptionView
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:descendantFocusability="beforeDescendants" >
<LinearLayout
android:id="@+id/caption"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="end"
android:background="@drawable/decor_caption_title"
android:focusable="false"
android:descendantFocusability="blocksDescendants" >
<Button
android:id="@+id/maximize_window"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_margin="5dp"
android:padding="4dp"
android:layout_gravity="center_vertical|end"
android:contentDescription="@string/maximize_button_text"
android:background="@drawable/decor_maximize_button_dark" />
<Button
android:id="@+id/close_window"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_margin="5dp"
android:padding="4dp"
android:layout_gravity="center_vertical|end"
android:contentDescription="@string/close_button_text"
android:background="@drawable/decor_close_button_dark" />
</LinearLayout>
</com.android.internal.widget.DecorCaptionView>
可以看到 mDecor.onResourcesLoaded 主要做了三件事情
- 通过 LayoutInflate 来创建这个layoutResource 的 View 实例对象 root
- 若不 null 则先添加 mDecorCaptionView, 再向 mDecorCaptionView 中添加 root
- 若 mDecorCaptionView 为 null, 则直接将 root 加到 DecorView 中
- 将加载系统资源文件创建的 View 的实例赋给 mContentRoot
==通过上面的源码可以知道, 系统创建的 DecorView 中只会存在一个子 View==
- mDecorCaptionView(它内部也包含了mContentRoot)
- mContentRoot
3.3 最终的 ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
/**
* Window.ID_ANDROID_CONTENT
* 这个 ID_ANDROID_CONTENT 即 mDecor 中的 mContentRoot 中的 FrameLayout 的 Id
* 也就是我们 setContentView 的地方 layoutRes 最终加载进去的地方
*/
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
表格
容器 | childCount | child |
---|---|---|
Window | 1 | DecorView |
DecorView(只有一个子孩子, 两种可能) | 1 |
may1(一般都是这个): mContentRoot (根据feature拿到的layoutResource 例如: screen_simple.xml) may2: DecorCaptionView |
DecorCaptionView(作为了解) | 2 | child1: LinearLayout(@+id/caption) child2: mContentRoot (根据feature拿到的layoutResource 例如: screen_simple.xml) |
mContentRoot(以screen_simple.xml为例) | 2 | child1: ViewStub(@+id/action_mode_bar_stub) child2: FrameLayout(@android:id/content), 看到这个ID就放心了, 它就是mCotnentParent |
结论
- 这里我们定位到mContentRoot的child2: FrameLayout(@android:id/content)
- FrameLayout(@android:id/content)与Window.ID_ANDROID_CONTENT一致
- FrameLayout(@android:id/content)即我们的上面源码中看到的mContentParent
- 注意:
- 如果继承了AppCompatActivity的话, 它会对这个FrameLayout(@android:id/content)进行处理, 会在其内部添加两个子View分别为ViewStubCompat和ContentFrameLayout
- 这个ContentFrameLayout将会代替我们的FrameLayout(@android:id/content)成为我们setContentView()中设置的layoutRes的直接父容器
层级图
![](https://img.haomeiwen.com/i4147272/00b5f60232885fac.jpg)
网友评论