1.简介
看源码时我们会经常遇到DecorView,那么这个DecorView到底是什么呢,我们来研究一下。
本文源码基于android 27。
2.代码分析
通常,我们在Activity的onCreate()
中都有这么一句代码:
setContentView(R.layout.main_activity);
那么这代码到底是干了啥呢,我们点进去看下。
2.1 Activity的setContentView
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);//调用window的setContentView
initWindowDecorActionBar();//初始化ActionBar
}
private Window mWindow;
public Window getWindow() {
return mWindow;
}
这个mWindow
会在Activity
的attach
方法中被赋值:
2.2 Activity的attach
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window, ActivityConfigCallback activityConfigCallback) {
//...
mWindow = new PhoneWindow(this, window, activityConfigCallback);
//...
}
可以看到mWindow
的具体实现为PhoneWindow
,那么我们来看下PhoneWindow
中的setContentView
:
2.3 PhoneWindow的setContentView
// This is the view in which the window contents are placed. It is either
// mDecor itself, or a child of mDecor where the contents go.
//翻译:这是放置窗口内容的view。它是mDecor本身或者是mDecor的一个子元素。
//这句话后面解释
ViewGroup mContentParent;
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {//如果mContentParent为空
installDecor();//安装DecorView
} 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
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
可以看到,我们传进来的布局将会加载到mContentParent
中去,如果mContentParent
为空,则首先会安装DecorView
。我们来看下installDecor()
:
2.4 PhoneWindow的installDecor
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
mDecor = generateDecor(-1);//创建DecorView
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);//mContentParent再这里被赋值
}
}
我们来看看generateDecor()
:
2.5 PhoneWindow的generateDecor
protected DecorView generateDecor(int featureId) {
//...
return new DecorView(context, featureId, this, getAttributes());
}
可以看到,DecorView
就被创建出来了。DecorView
继承自FrameLayout
,所以它本质是一个FrameLayout
。
然后,我们看看generateLayout()
:
2.6 PhoneWindow的generateLayout
protected ViewGroup generateLayout(DecorView decor) {
//根据主题样式设置各种属性特征,代码略
//根据特征设置不同的布局
int layoutResource;
int features = getLocalFeatures();
//...
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;//设置布局
}
if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {//特征值判断
layoutResource = R.layout.screen_simple_overlay_action_mode;//设置布局
} else {
layoutResource = R.layout.screen_simple;//设置布局
}
//...
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);//加载资源布局
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);//这里获取的就是mContentParent
//...
return contentParent;
我们挑其中一个布局来看下,就看下screen_title
这个布局:
2.7 screen_title.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:fitsSystemWindows="true">
<!-- Popout bar for action modes -->
<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: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>
来张截图说明吧:
3.去除标题栏
我们可以通过如下方式来去除标题栏:
- 在
setContentView
方法之前调用requestWindowFeature(Window.FEATURE_NO_TITLE)
。
为什么要在
setContentView
方法之前呢?
从上面的代码分析可以看出,设置特征是在setContentView
方法中设置的,setContentView
会根据不同的特征来加载不同的布局。
-
AndroidManifest.xml
中指定的主题添加NoTitleBar
,如android:theme="@android:style/Theme.NoTitleBar"
去除标题栏的布局实际上就是布局去掉了id为title
的FrameLayout
,那么DecorView
中就只有content
这个FrameLayout
了。所以mContentParent
是mDecor
本身或者是mDecor
的一个子元素,这句话就好理解了。