View的工作原理一(setContentView)

我们一般写界面都是从xml布局文件开始,写完布局,预览感觉差不多了,然后就在Activity的onCreate()里面调用setContentView(R.layout.xxx),其他的什么都不用写,项目跑起来,我们的布局就可以显示了。那么,为什么我们写的布局能够显示出来呢?可能我们不会想为什么,因为写习惯了,就像1+1=2 一样太正常了。但是要想更清楚的理解View的绘制过程,我们必须要知道为什么。

首先看看setContentView()的源码(Activity的和AppCompatActivity不一样):

public void setContentView(@LayoutRes int layoutResID) {
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}

还有两个重载方法:

public void setContentView(View view) {
    getWindow().setContentView(view);
    initWindowDecorActionBar();
}

public void setContentView(View view, ViewGroup.LayoutParams params) {
    getWindow().setContentView(view, params);
    initWindowDecorActionBar();
}

这三个方法都是Window类的抽象方法,而且前两个其实最后都是调用第三个方法,getWinDow()在Activity的事件分发已经说到,其实就是PhoneWindow.所以接下来直接看PhoneWindow的setContentView(View view, ViewGroup.LayoutParams params):

public void setContentView(View view, ViewGroup.LayoutParams params) {
    // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
    // decor, when theme attributes and the like are crystalized. Do not check the feature
    // before this happens.
    //mContentParent 就是我们自己写的布局文件的根布局的父布局
    if (mContentParent == null) {
       //初始化DecorView,mContentParent
        installDecor();
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        //移除所有的子View
        mContentParent.removeAllViews();
    }

    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        view.setLayoutParams(params);
        final Scene newScene = new Scene(mContentParent, view);
        transitionTo(newScene);
    } else {
        //添加我们自己定义的布局到mContentParent 
        mContentParent.addView(view, params);
    }
    mContentParent.requestApplyInsets();
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        cb.onContentChanged();
    }
    mContentParentExplicitlySet = true;
}

看一下 PhoneWindow的installDecor():

 private void installDecor() {
    mForceDecorInstall = false;
    if (mDecor == null) {
        //创建一个DecorView
        mDecor = generateDecor(-1);
        mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
        mDecor.setIsRootNamespace(true);
        if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
            mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
        }
    } else {
      //绑定window
        mDecor.setWindow(this);
    }
    if (mContentParent == null) {
        //给mContentParent 赋值
        mContentParent = generateLayout(mDecor);
        .......
     }
}

看一下generateLayout(mDecor):

 protected ViewGroup generateLayout(DecorView decor) {
    // Apply data from current theme.
    //根据我们设置的主题,设置样式和标记
    TypedArray a = getWindowStyle();

      .......      
    //theme中是否透明状态栏
    if (a.getBoolean(R.styleable.Window_windowTranslucentStatus,
            false)) {
          setFlags(FLAG_TRANSLUCENT_STATUS, FLAG_TRANSLUCENT_STATUS
                  & (~getForcedWindowFlags()));
    }

    //theme中是否透明导航栏
    if (a.getBoolean(R.styleable.Window_windowTranslucentNavigation,
            false)) {
        setFlags(FLAG_TRANSLUCENT_NAVIGATION, FLAG_TRANSLUCENT_NAVIGATION
                & (~getForcedWindowFlags()));
    }

    .......
    //如果没有修改StatusBar颜色,设置默认值不透明
    if (!mForcedStatusBarColor) {
        mStatusBarColor = a.getColor(R.styleable.Window_statusBarColor, 0xFF000000);
    }

    //如果没有修改NavigationBar颜色,设置默认值不透明
    if (!mForcedNavigationBarColor) {
        mNavigationBarColor = a.getColor(R.styleable.Window_navigationBarColor, 0xFF000000);
    }

    ......

    // Inflate the window decor.
    //根据不同的主题和样式,选择不同的系统内置布局文件
    int layoutResource;
    //拿到我们在onCreate()里面,setContentView之前通过requestWindowFeature()设置的features
    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);
    //FEATURE_NO_TITLE样式
    } 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.
        //没有设置任何装饰(feature)时选用的布局
        layoutResource = R.layout.screen_simple;
        // System.out.println("Simple!");
    }

    mDecor.startChanging();
    //添加内置的布局到DecorView
    mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
    //找到内置布局里面的id为ID_ANDROID_CONTENT的ViewGroup,并用contentParent 变量记录
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    if (contentParent == null) {
        throw new RuntimeException("Window couldn't find content container view");
    }

    .......

    mDecor.finishChanging();
    //返回contentParent ,也就是 mContentParent为id为ID_ANDROID_CONTENT的ViewGroup
    return contentParent;
}

在我的目录:C:\Users\Administrator\AppData\Local\Android\Sdk\platforms\android-27\data\res\layout,可以找到screen_simple.xml文件:

<?xml version="1.0" encoding="UTF-8"?>
<LinearLayout android:orientation="vertical" android:fitsSystemWindows="true"         
   android:layout_height="match_parent" 
   android:layout_width="match_parent" 
   xmlns:android="http://schemas.android.com/apk/res/android"> 
  <ViewStub android:layout_height="wrap_content" android:layout_width="match_parent"     
            android:theme="?attr/actionBarTheme" 
            android:layout="@layout/action_mode_bar" 
            android:inflatedId="@+id/action_mode_bar"
            android:id="@+id/action_mode_bar_stub"/> 
  <FrameLayout android:layout_height="match_parent" 
            android:layout_width="match_parent" 
            android:id="@android:id/content" 
            android:foreground="?android:attr/windowContentOverlay"         
            android:foregroundGravity="fill_horizontal|top" 
            android:foregroundInsidePadding="false"/>
</LinearLayout>

可以看出,我们如果没有在setContentView之前调用requestWindowFeature(),系统默认就是会帮我们选择screen_simple.xml这个布局。从前面的generateLayout()源码可以看出,DecorView是整个View树的根布局,一般情况下,DecorView里面只有一个LinearLayout,LinearLayout里面包换一个toolbar(ViewStub )和contentParent( FrameLayout ),也就是我们的自定义布局的父布局,然后才是我们通过setContentView添加进去的自定义布局.

说到这里有个问题,DecorView是整个View树的根布局,那么它包不包括StatusBar和NavigationBar呢?
从上面的分析来看应该是不包含的,因为没有看到在哪里添加这两个View.但是通过AndroidStudio的Layout Inspector分析布局竟然是这样的:


布局层次结构.png

可以看到DecorView有一个系统内置的screen_simple.xml布局,它里面的content下面就是我们的自定义布局.但是除了这个内置的Linearlayout以外,还有navigationBarBackground和statusBarBackground这两个view.从名字可以看出意思是给navigationBar和statusBar绘制背景的View.那这么说,DecorView是不包含navigationBar和statusBar的,只包含绘制navigationBar和statusBar背景的View,但是这两个view在哪里添加进DecorView,暂时我还没找到.

DecorView.onResourcesLoaded

在generateLayout()中DecorView调用了onResourcesLoaded(mLayoutInflater, layoutResource)来添加内置的screen_simple.xml等布局,看看它 的源码:

void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
    mStackId = getStackId();

    if (mBackdropFrameRenderer != null) {
        loadBackgroundDrawablesIfNeeded();
        mBackdropFrameRenderer.onResourcesLoaded(
                this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable,
                mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState),
                getCurrentColor(mNavigationColorViewState));
    }
  //多窗口自由模式下才有DecorCaptionView
    mDecorCaptionView = createDecorCaptionView(inflater);
  //解析screen_simple.xml等内置布局
    final View root = inflater.inflate(layoutResource, null);
    if (mDecorCaptionView != null) {
        if (mDecorCaptionView.getParent() == null) {
            addView(mDecorCaptionView,
                    new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        }
        mDecorCaptionView.addView(root,
                new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
    } else {

        // Put it below the color views.
        //添加内置布局
        addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    }
    mContentRoot = (ViewGroup) root;
    initializeElevation();
}
Activity.initWindowDecorActionBar();

再回头看看Activity的setContentView()里面的第二行:initWindowDecorActionBar()

private void initWindowDecorActionBar() {
    Window window = getWindow();

    // Initializing the window decor can change window feature flags.
    // Make sure that we have the correct set before performing the test below.
    window.getDecorView();

    if (isChild() || !window.hasFeature(Window.FEATURE_ACTION_BAR) || mActionBar != null) {
        return;
    }

    mActionBar = new WindowDecorActionBar(this);
    mActionBar.setDefaultDisplayHomeAsUpEnabled(mEnableDefaultActionBarUp);

    mWindow.setDefaultIcon(mActivityInfo.getIconResource());
    mWindow.setDefaultLogo(mActivityInfo.getLogoResource());
}

就是初始化Actionbar而已

总结:Activity的setContentView()主要用于Window,DecorView,contentParent一些系统级别的view,界面主题样式的初始化,确定viewTree的层次结构的,把我们自定义的xml布局添加到系统的ViewTree.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 161,326评论 4 369
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,228评论 1 304
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,979评论 0 252
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,489评论 0 217
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,894评论 3 294
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,900评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,075评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,803评论 0 205
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,565评论 1 249
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,778评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,255评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,582评论 3 261
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,254评论 3 241
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,151评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,952评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,035评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,839评论 2 277

推荐阅读更多精彩内容