View的显示(1)——从setContentView()开始

记录view是如何添加至界面显示出来。

先记录几个相关类:

1)Window:是一个抽象类,提供了绘制窗口的一组通用API。

2)PhoneWindow:是Window的具体继承实现类。而且该类内部包含了一个DecorView对象,该DectorView对象是所有应用窗口(Activity界面)的根View。

3)DecorView:是PhoneWindow的内部类,是FrameLayout的子类,是对FrameLayout进行功能的修饰(所以叫DecorXXX),是所有应用窗口的根View 。

在activity的初始化过程中,会调用activity的attach方法,在该方法中创建一个PhoneWindow实例,将其作为Activity的成员变量mWindow。之后会调用onCreate方法,只要在onCreate方法里面,调用setContentView()将布局文件(layout)id传递进去。setContentView()做了什么事呢?

Activity的源码中提供了三个重载的setContentView方法(api 23):

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

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

首先通过getWindow()得到Window实例mWindow,mWindow是PhoneWindow对象,在attach中初始化了。然后调用PhoneWindow的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();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
    }

该方法传入layout的id,该资源代表了activity中的content内容。首先判断mContentParent是否为null,也就是第一次调用);如果是第一次调用,则调用installDecor()方法,否则判断是否设置FEATURE_CONTENT_TRANSITIONS Window属性(默认false),如果没有设置,就移除该mContentParent内所有的所有子View;接着通过mLayoutInflater.inflate(layoutResID, mContentParent);将我们的资源文件通过LayoutInflater对象转换为View树,并且添加至mContentParent视图中(其中mLayoutInflater是在PhoneWindow的构造函数中得到实例对象的LayoutInflater.from(context);)。

其中,mContentParent就是content的parent,是个viewgroup,包裹layout文件。

接下来,通过getCallBack()拿到了一个CallBack对象,事实上获取到的这个CallBack就是Activity自己,因为Activity实现了CallBack接口。

这个Callback是一个回调,当PhoneWindow接收到系统分发给它的触摸、IO、菜单等相关的事件时,可以回调相应的Activity进行处理。在这里,Callback只回调了onContentChanged,而onContentChanged在Activity中是空实现。

installDecor()的代码如下:

private void installDecor() {  
            if (mDecor == null) {  
                mDecor = generateDecor();  
                mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);  
                mDecor.setIsRootNamespace(true);  
                //...  
                }  
            }  
            if (mContentParent == null) {  
                mContentParent = generateLayout(mDecor);  
                mTitleView = (TextView)findViewById(com.android.internal.R.id.title);  
                if (mTitleView != null) {  
                   //根据FEATURE_NO_TITLE隐藏,或者设置mTitleView的值  
                    //...  
                } else {  
                    mActionBar = (ActionBarView) findViewById(com.android.internal.R.id.action_bar);  
                    if (mActionBar != null) {  
                        //设置ActionBar标题、图标神马的;根据FEATURE初始化Actionbar的一些显示  
                        //...  
                    }  
                }  
            }  
    }  

省略例部分代码。主要做了这些事:
调用generateDecor()得到mDecor,mDecor是DecorView对象。在得到mDecor以后设置其焦点的获取方式为:当其子孙都不需要时,自己才获取。
然后调用generateLayout(mDecor);把mDecor做为参数传入,获取到mContentParent。
接下里通过findViewById进行获取控件,这里的findViewById的代码是这样的:

public View findViewById(int id) {  
       return getDecorView().findViewById(id);  
   }  

getDecorView返回的就是mDecor,即DecorView。

generateDecor()代码:

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

public DecorView(Context context, int featureId) {  
          super(context);  
          mFeatureId = featureId;  
      }  

该方法初始化一个DecorView对象。

generateLayout(mDecor)代码:

protected ViewGroup generateLayout(DecorView decor) {  
                    // Apply data from current theme.  
                    TypedArray a = getWindowStyle();  
                    //...Window_windowIsFloating,Window_windowNoTitle,Window_windowActionBar...  
                    //首先通过WindowStyle中设置的各种属性,对Window进行requestFeature或者setFlags  

                    if (a.getBoolean(com.android.internal.R.styleable.Window_windowNoTitle, false)) {  
                        requestFeature(FEATURE_NO_TITLE);  
                    }  
                    //...  
                    if (a.getBoolean(com.android.internal.R.styleable.Window_windowFullscreen, false)) {  
                        setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));  
                    }  
                    //...根据当前sdk的版本确定是否需要menukey  
                    WindowManager.LayoutParams params = getAttributes();  
                    //通过a中设置的属性,设置  params.softInputMode 软键盘的模式;  
                    //如果当前是浮动Activity,在params中设置FLAG_DIM_BEHIND并记录dimAmount的值。  
                    //以及在params.windowAnimations记录WindowAnimationStyle  

                    // Inflate the window decor.  
                    int layoutResource;  
                    int features = getLocalFeatures();  
                    // System.out.println("Features: 0x" + Integer.toHexString(features));  
                    if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {  
                        if (mIsFloating) {  
                            TypedValue res = new TypedValue();  
                            getContext().getTheme().resolveAttribute(  
                                    com.android.internal.R.attr.dialogTitleIconsDecorLayout, res, true);  
                            layoutResource = res.resourceId;  
                        } else {  
                            layoutResource = com.android.internal.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 = com.android.internal.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(  
                                    com.android.internal.R.attr.dialogCustomTitleDecorLayout, res, true);  
                            layoutResource = res.resourceId;  
                        } else {  
                            layoutResource = com.android.internal.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(  
                                    com.android.internal.R.attr.dialogTitleDecorLayout, res, true);  
                            layoutResource = res.resourceId;  
                        } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {  
                            layoutResource = com.android.internal.R.layout.screen_action_bar;  
                        } else {  
                            layoutResource = com.android.internal.R.layout.screen_title;  
                        }  
                        // System.out.println("Title!");  
                    } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {  
                        layoutResource = com.android.internal.R.layout.screen_simple_overlay_action_mode;  
                    } else {  
                        // Embedded, so no decoration is needed.  
                        layoutResource = com.android.internal.R.layout.screen_simple;  
                        // System.out.println("Simple!");  
                    }  


                    View in = mLayoutInflater.inflate(layoutResource, null);  
                    decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));  

                    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);  
                    //...  

                    return contentParent;  
                }  
         }  

首先getWindowStyle在当前的Window的theme中获取我们的Window中定义的属性。参考:\frameworks\base\core\res\res\values\attrs.xml

<!-- The set of attributes that describe a Windows's theme. -->  
   <declare-styleable name="Window">  
       <attr name="windowBackground" />  
       <attr name="windowContentOverlay" />  
       <attr name="windowFrame" />  
       <attr name="windowNoTitle" />  
       <attr name="windowFullscreen" />  
       <attr name="windowOverscan" />  
       <attr name="windowIsFloating" />  
       <attr name="windowIsTranslucent" />  
       <attr name="windowShowWallpaper" />  
       <attr name="windowAnimationStyle" />  
       <attr name="windowSoftInputMode" />  
       <attr name="windowDisablePreview" />  
       <attr name="windowNoDisplay" />  
       <attr name="textColor" />  
       <attr name="backgroundDimEnabled" />  
       <attr name="backgroundDimAmount" />  

然后就根据这些属性的值,对Window各种requestFeature,setFlags等等。接下来,通过对features和mIsFloating的判断,为layoutResource进行赋值,值可以为R.layout.screen_custom_title;R.layout.screen_action_bar;等等。至于features,特性,除了theme中设置的,我们也可以在Activity的onCreate的setContentView之前进行requestFeature,也解释了,为什么需要在setContentView前调用requestFeature设置全屏等。
得到了layoutResource以后,通过LayoutInflater把布局转化成view,然后加入到decor中,即传入的DecorView中。之后通过mDecor.findViewById传入R.id.content,返回mDecor(布局)中的id为ID_ANDROID_CONTENT(R.id.content)的View,一般是FrameLayout。

mDecor是一个FrameLayout,会根据theme去选择系统中的布局文件,将布局文件通过inflate转化为view,加入到mDecor中;

当generateLayout(mDecor)执行完后,installDecor()也就执行完了。然后执行mLayoutInflater.inflate(layoutResID, mContentParent);这里的layoutResID就是activity的布局文件id,将他放入到mContentParent(id为content的FrameLayout对象)ViewGroup中。中。
这样 ,activity的setContentView()就执行完了。

总结就是:
1.创建一个DecorView的对象mDecor,该mDecor对象将作为整个应用窗口的根视图。

2.根据theme中的属性值,选择合适的布局,通过infalter.inflater放入到mDecor中。
在这些布局中,一般会包含ActionBar,Title,和一个id为content的FrameLayout,这就是Activity布局文件该存放的地方。

3.将Activity的布局文件添加至id为content的FrameLayout内。

(PhoneWindow中还有一个成员变量叫mContentRoot,代表content的root,content的根节点。一般情况下,mContentParent是放在mContentRoot中。)

最后总结activity上的视图层次:最下面是Activity,上一层是PhoneWindow,PhoneWindow里面是DecorView,DecorView一般上面是一个ActionBar,下面是id是content的FrameLayout,这个FrameLayout就是放activity布局文件的地方。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值