Android UI绘制流程(一)----布局的加载

在activity里面如果想加载布局并显示的话,可以用setContentView来设置;如果想动态添加控件,可以用addView来添加(其实setContentView方法内部也通过addView方法来实现的),那么View是如何被放在视图上面并且显示的呢,这篇博客我们来了解一下布局的加载过程。

activity是如何加载布局的 setContentView(layoutResID)

setContentView()有三个方法,三个方法的除了参数不同其他调用都是一样的,我们以最常用的看setContentView(layoutResID)为例:

 /**
     * 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();
    }

setContentView()首先调用getWindow()方法返回一个Window,然后我们去看Window类,发现Window类是一个抽象类,setContentView()方法也是一个抽象方法,而Window的具体实现类是PhoneWindow,所以调用的setContentView()方法实际上也就是PhoneWindow的setContentView()方法。

但是在源码中按住Ctrl却发现无法进入到PhoneWindow类里面,这是因为安卓把这个类给隐藏掉了。想要查看的话可以去SDK的安装目录中找到\sources\文件夹下面搜索查看,我们以android-24为例:
Android UI绘制流程(一)----布局的加载_第1张图片
这样就能找到PhoneWindow类并查看源码了。

我们继续往下看PhoneWindow的setContentView()方法:

 @Override
    public void setContentView(int layoutResID) {

        if (mContentParent == null) {
            installDecor();
        } 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.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
    }

setContentView()主要步骤为两步:

  1. installDecor()方法初始化DecorView,向DecorView中添加系统布局,并获取其中的帧布局
  2. mLayoutInflater.inflate(layoutResID, mContentParent)将我们自己写的Layout布局添加到帧布局中。

当第一次加载的时候mContentParent为null,会去调用installDecor()方法,installDecor()方法里面又是什么呢?

  private void installDecor() {
        if (mDecor == null) {
            mDecor = generateDecor();
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        }
         if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);

            ......
         }

        ......
   }    

首先判断mDecor为null,调用generateDecor()获取mDecor,

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

我们看到这里返回的是一个DecorView,这是PhoneWindow类当中的一个内部类,同时也是屏幕显示的内容最外层。下面同样判断mContentParent如果为null,就会执行generateLayout()方法,并且把我们创建好的DecorView布局当作参数传进去;

/**
 *generateLayout方法的作用就是设置一些窗体的属性值,然后窗体布局添加到DecorView中,并返回窗体布局中ID为content的帧布局
 */
protected ViewGroup generateLayout(DecorView decor) {
        //获取窗口的属性            
        TypedArray a = getWindowStyle();

        //... (设置窗口style属性)

          // Inflate the window decor.

        int layoutResource;
        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);
        } 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.
            layoutResource = R.layout.screen_simple;
            // System.out.println("Simple!");
        }

        mDecor.startChanging();

        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");
        }

       ......

        mDecor.finishChanging();

        return contentParent;
    }

这个方法比较长,我们总结一下这个方法都做了什么:
1. 获取窗口的各种style属性,设置title是否显示,窗口是否浮动等
2. 根据获取到的feature值来选择不同的窗口布局文件(窗口修饰类型包括有全屏FullScreen,不含标题栏NoTitleBar等)以screen_simple.xml为例。


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

3、上面的填充到DecorView里面的, 即为标题栏,即我们的布局区域。 DecorView会通过addView的方式将上面的布局添加进去,并通过findViewById将content控件绑定到contentParent(contentParent实际上是一个FrameLayout),最后作为返回值提供给方法调用处为mContentView赋值。

 @Override
    public void setContentView(int layoutResID) {
        if (mContentParent == null) {
            installDecor();
        } 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.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
    }

返回来再看PhoneWindow中的setContentView(int layoutResID)方法。
LayoutInflater.inflate(layoutResID, mContentParent)会将传入的布局填充到mContentParent中(将我们自己写的Layout布局添加到帧布局中),这样即可以将我们的布局加载到Activity中了。

加载流程图为:
Android UI绘制流程(一)----布局的加载_第2张图片

布局结构图为:
Android UI绘制流程(一)----布局的加载_第3张图片

总结

本篇我们总结了布局的加载过程,如有错误,恳请大家批评指正。在下篇文章中我们会继续探索UI的绘制流程。

你可能感兴趣的:(Android风格UI相关)