在Android 系统中,所有的控件父类都是android.view.View这个类,

android 判断viewgroup中是否存在某View_as

可以看到View的直接子类和非直接子类是非常的多,而我们所熟悉的android.view.ViewGroup

android 判断viewgroup中是否存在某View_android群英传_02

可以看到ViewGroup还是继承View的,而它的子类也是非常常见的各种XXLayout 类

所以在Android系统中,控件大致可以分为两种,View 和 ViewGroup ,它们在屏幕上都是占据一块矩形。ViewGroup根据其名字就能猜到它是View的一组集合,也就是它可以包含多个View ,当然 它也可以包含ViewGroup,所以我们平常开发时候,就会在屏幕上形成一棵控件树

 

android 判断viewgroup中是否存在某View_android开发_03

上层控件负责下层控件的测量和绘制,并传递交互事件,我们经常使用的findViewById()就是在控件树中以树的深度优先遍历来查找.。每一棵控件树的顶部都有一个顶级的ViewGroup,是控制树的核心,所有的交互事件都有它统一调度和分配。

那么android系统是如何将我们写的布局文件xxx.xml放置到屏幕上的尼?

先看一张android系统的界面控件架构

android 判断viewgroup中是否存在某View_as_04

先大致说一下上面这张架构图,

在Activity.java中有这么一段注释

* An activity is a single, focused thing that the user can do.  Almost all
* activities interact with the user, so the Activity class takes care of
* creating a window for you in which you can place your UI with
* {@link #setContentView}.

大致意思就是Activity是用于与用户交互的,每一个Activity都是独立的,所以每一个Activity都会创建一个Window 对象来让你通过setContentView 放置你的UI ,然后我们

在Activity .java 真的发现了这么一个变量

private Window mWindow;

但是查看WIndow类却发现它是一个抽象类,不能new 一个对象出来,那么它是如何创建出一个窗口给我们的尼?

在Activity类中查找发现

android 判断viewgroup中是否存在某View_android开发_05

而PhoneWindow类

public class PhoneWindow extends Window implements MenuBuilder.Callback {

是继承鱼Window类的,到此为止,我们知道一个Activity 中包含了一个一个PhoneWindow对象,那么我们现在回到最初的问题,xxx.xml如何加载到屏幕上

简单的列子

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
}

我们通过Activity的setContentView()方法将我们的布局文件传了上去,点击进去进一步查看Activity的setContentView方法

Activity.java中

/**
 * Set the activity content from a layout resource.  The resource will be
 * inflated, adding all top-level views to the activity.
 *
 * @param layoutResID Resource ID to be inflated.
 *
 * @see #setContentView(android.view.View)
 * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
 */
public void setContentView(@LayoutRes int layoutResID) {
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}

发现这个方法是调用了getWindow()方法拿到一个对象,然后再用这个对象的setContentView方法,将布局文件inflate,

* @return Window The current window, or null if the activity is not
 *         visual.
 */
public Window getWindow() {
    return mWindow;
}

返回的是一个名叫mWindow 的 Window对象,通过前面我们知道这个mWindow其实是一个PhoneWIndow对象,所以我们下一步去到PhoneWindow中看看setContentView方法

在介绍PhoneWindow的setContentView方法之前我先介绍一下三个比较重要的变量,

// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;

// 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.
private ViewGroup mContentParent;

private ViewGroup mContentRoot;

DecorView 是PhoneWindow的一个内部类,继承于FrameLayout

private final class DecorView extends FrameLayout implements RootViewSurfaceTaker

再看看下面三张图

android 判断viewgroup中是否存在某View_app_06

android 判断viewgroup中是否存在某View_android_07

android 判断viewgroup中是否存在某View_app_08

可以发现这三个变量都只有一处赋值,mDecor ,mContentParent 是在installDecor方法内被赋值,而mContentRoot是在generateLayout(DecorView)方法内被赋值,也就是说

当这个Activity第一次调用setContentView方法,在这些方法执行之前mDecor, mContentParent ,mContentRoot的值都是为null的(本笔记也是基于Activity第一次调用setContentView方法)

好,现在回到介绍PhoneWindow.java文件中的setContentView方法中

public void setContentView(int layoutResID) {
    // 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.
    if (mContentParent == null) {
        installDecor();
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        mContentParent.removeAllViews();
    }

可以看到先是判断mContentParent是否为空,根据上面所说,mContentParent为空,进入installDecor(),这个方法就是mDecor, mContentParent赋值的方法(上面提到)

private void installDecor() {
    if (mDecor == null) {
        mDecor = generateDecor();//return new DecorView(getContext(), -1);
        mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
        mDecor.setIsRootNamespace(true);
        if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
            mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
        }
    }
    if (mContentParent == null) {
        mContentParent = generateLayout(mDecor);

在installDecor方法中,判断mDecor是否为空,为空,然后

mDecor = generateDecor();

mDecor就是在此被赋值,

protected DecorView generateDecor() {
    return new DecorView(getContext(), -1);
}

可以发现返回的 只是简单的new出来的一个DecorView对象,到此之后mDecor就被赋值了不再为空

那么再回到这里(上面再一次复制过来方便看)

private void installDecor() {
    if (mDecor == null) {
        mDecor = generateDecor();//return new DecorView(getContext(), -1);
        mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
        mDecor.setIsRootNamespace(true);
        if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
            mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
        }
    }
    if (mContentParent == null) {
        mContentParent = generateLayout(mDecor);

之后便是对mDecor进行各种属性的设置,先不用管

来到

if (mContentParent == null) {
        mContentParent = generateLayout(mDecor);

mContentParent 还没赋值,所以进入这个if语句中

看看generateLayout执行了什么,(mContentRoot在这个方法被赋值!!!!!!)

protected ViewGroup generateLayout(DecorView decor) {
    // Apply data from current theme.
    //根据主题设定相应的参数
    TypedArray a = getWindowStyle();//获得window 的 style

    mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false);
    int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
            & (~getForcedWindowFlags());
    if (mIsFloating) {
        setLayout(WRAP_CONTENT, WRAP_CONTENT);
        setFlags(0, flagsToUpdate);
    } else {
        setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
    }
.。。。。。。这里省略一大部分主题设置的相关代码
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContentRoot = (ViewGroup) in;

ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
    throw new RuntimeException("Window couldn't find content container view");
}

可以看到这里将一个layoutResource 参数inflate 那么这个layoutResource

android 判断viewgroup中是否存在某View_as_09

可以看到layoutResource 赋值是根据不同的features ,这里就是我主题里平常设置的,比如 NO_TITLE / FULL_SREEN 什么的 它就根据这个将相应的系统预设xml文件赋值给layoutResource(int整形)  比如我们打开screen_simple.xml

<LinearLayout xmlns:android="http:///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>

其实都是简单的布局文件

回到这里

View in = mLayoutInflater.inflate(layoutResource, null);//获取layoutResource布局,将xml布局转换成VIEW
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));将XML资源为layoutResource的布局添加到decor容器里面,
mContentRoot = (ViewGroup) in;

ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
    throw new RuntimeException("Window couldn't find content container view");
}

看到这个

mContentRoot = (ViewGroup) in;

layoutResource 最底层的ViewGroup ,如 screen_simple.xml

ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);

 这里就是在这个

layoutResource 中找一个id为 ID_ANDROID_CONTENT  每个

layoutResource

   

至此,就完成了xml加载到Activity中工作

至于

requestWindowFeature(Window.FEATURE_NO_TITLE);

这个方法,

public class MainActivity extends Activity {

时,能成功设置

public class MainActivity extends AppCompatActivity

通过

this.supportRequestWindowFeature(Window.FEATURE_NO_TITLE);

来设置

因为在AppCompatActivity中

/**
 * Enable extended window features.  This should be called instead of
 * {@link .Activity#requestWindowFeature(int)} or
 * {@link android.view.Window#requestFeature getWindow().requestFeature()}.
 *

还有就是想到一个问题,通过代码

requestWindowFeature(Window.FEATURE_NO_TITLE);

和通过主题NoTitle 到底有什么不一样,最后查资料和看源代码,发现其实最后调用的都是Window的requestFeature方法,

至于为什么要在setContentView之前requestFeature ,在源码有解释,

@Override
public boolean requestFeature(int featureId) {
    if (mContentParent != null) {
        throw new AndroidRuntimeException("requestFeature() must be called before adding content");
    }

前面说到 mContentParent 是在setContentView中 被赋值的(id为content的ViewGroup),当在setContentView之后再设置requestFeature的话mContentParent 就不是为空了,那时候就会抛出异常了