美文网首页
View的工作原理一(setContentView)

View的工作原理一(setContentView)

作者: 有兴不虚昧 | 来源:发表于2018-05-09 19:28 被阅读0次

我们一般写界面都是从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.

相关文章

网友评论

      本文标题:View的工作原理一(setContentView)

      本文链接:https://www.haomeiwen.com/subject/brrwrftx.html