从setContentView揭开DecorView

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会在Activityattach方法中被赋值:

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>

来张截图说明吧:
DecorView、ContentView、标题栏图解.png

3.去除标题栏

我们可以通过如下方式来去除标题栏:

  • setContentView方法之前调用requestWindowFeature(Window.FEATURE_NO_TITLE)

    为什么要在setContentView方法之前呢?
    从上面的代码分析可以看出,设置特征是在setContentView方法中设置的,setContentView会根据不同的特征来加载不同的布局。

  • AndroidManifest.xml 中指定的主题添加NoTitleBar,如android:theme="@android:style/Theme.NoTitleBar"

去除标题栏的布局实际上就是布局去掉了id为titleFrameLayout,那么DecorView中就只有content这个FrameLayout了。所以mContentParentmDecor本身或者是mDecor的一个子元素,这句话就好理解了。

猜你喜欢

转载自blog.csdn.net/u011810352/article/details/79379935