View的工作原理(一)之 View的三大过程 和 认识MeasureSpec

来源:互联网 发布:r语言数据挖掘 薛薇 编辑:程序博客网 时间:2024/04/30 10:07

View的三大过程

一个View出现在屏幕要经过三个过程:measure(测量)、layout(布局)、draw(绘制)。这三个过程均是通过ViewRoot来完成,ViewRoot对应ViewRootImpl类,它是连接WindowManager和DecorView(顶级View)的纽带。当Activity对象被创建完毕后,会将DecorView添加到Window中,同时会创建ViewRootImpl对象,并将ViewRootImpl对象和DecorView建立关联。 


measure过程决定了View的宽/高,Measure完成以后,可以通过getMeasuredWidth和getMeasuredHeight方法来获取到View测量后的宽/高,在几乎所有情况下它都等于View最终的宽/高,但特殊情况除外。


layout过程决定了View的四个顶点的坐标和实际的View的宽/高,完成以后,可通过getTop、getBottom、getLeft和getRight来拿到View的四个顶点的位置,并可通过getWidth和getHeight方法来拿到View的最终宽/高。


draw过程决定了View的显示。


DecorView就是顶级View或叫根View,一般情况下是一个竖直方向的LinearLayout,分Title和Content两部分(具体看Android版本及主题),我们在Activity的onCreate方法中调用的setContentView就是设置它的Content内容。Content这块的id是content,所以,如果想获得它的对象,可以这样:

ViewGroup contentView = (ViewGroup)findViewById(android.R.id.content);

然而我们通过setContentView设置的View,可以这样获得:contentView.getChildAt(0);。它们几个的关系,可以参考下图:


ViewRoot的工作

ViewRootImpl类中有方法performTraversals,performTraversals会依次调用performMeasureperformLayoutperformDraw三个方法,它们分别完成DecorView的measure、layout 和 draw三大流程。从源码可以看到:perormMeasure方法 调用了 measure方法,measure 方法又调用onMeasure 方法。在onMeasure方法中则会对所有子元素进行measure过程,这时measure流程就从父容器传递到子元素中,这样就完成一次measure过程。接着子元素会重复父容器的过程。同理performLayout和performDraw类似。可以参考下图:




MeasureSpec

一般情况下,我们在做界面布局时会LayoutParams有三种参数,分别是:match_parentwrap_content 和 数值大小(例如:10dp)。其实这三种参数对应了两种模式,这种模式叫SpecMode(测量模式)。要了解SpecMode是什么就要先来说说MeasureSpec。MeasureSpec代表一个32位int值,高2位代表SpecMode,低30位代表SpecSize(测量模式下的规格大小)。MeasureSpec至所以通过SpecModeSpecSize打包成一个int值,想必是为了避免过多的对象内存分配。

View的尺寸规格是受父容器影响和MeasureSpec的决定。在measure过程中,系统会将View的Layoutparams根据父容器所施加的规则转换成对应的MeasureSpec值,此值就是用于测量出View的宽和高。注意测量的宽和高不一定等于最终宽和高。

 

说回刚才的三种参数对应两种SpecMode,其实SpecMode共有三类,每一类表示的含义如下所示:

UNSPECIFIED   父容器不对View有任何限制,要多大给多大,这种情况一般用于系统内部,表示一种测量的状态。

EXACTLY               父容器已检测出View所需要的精确大小,这个时候View的最终大小就是SpecSize所指定的值。它对应LayoutParams中的match_parent和具体的数值这两种模式。

AT_MOST           父容器指定了一个可用大小即SpecSize,View的大小不能大于这个值,具体是什么值要看不同View的具体实现。它对应于LayoutParams中的wrap_content

DecorView获得MeasureSpec过程

DecorView(顶级View)其MeasureSpec由窗口的尺寸和其自身的LayoutParams来共同确定。可见在ViewRootImpl中的measureHierarchy方法中有如下一段代码展示了DecorView的MeasureSpec的创建过程,其中desiredWindowWidth和desiredWindowHeight是屏幕的尺寸:

private boolean measureHierarchy(View host,LayoutParams lp, Resources res, int desiredWindowWidth, intdesiredWindowHeight) {         ……         childWidthMeasureSpec= getRootMeasureSpec (desiredWindowWidth, lp.width);         childHeightMeasureSpec= getRootMeasureSpec (desiredWindowHeight, lp.height);         performMeasure(childWidthMeasureSpec,childHeightMeasureSpec);         ……}

getRootMeasureSpec方法的实现:

private static int getRootMeasureSpec(int windowSize,int rootDimension) {    intmeasureSpec;    switch(rootDimension) {     caseViewGroup.LayoutParams.MATCH_PARENT:        // 精确模式,大小就是窗口的大小。       measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);        break;    caseViewGroup.LayoutParams.WRAP_CONTENT:        // 最大模式,大小不定,但是不能超过窗口的大小。       measureSpec = MeasureSpec.makeMeasureSpec(windowSize,MeasureSpec.AT_MOST);        break;    default:        // 精确模式,大小为LayoutParams中指定的大小。       measureSpec = MeasureSpec.makeMeasureSpec(rootDimension,MeasureSpec.EXACTLY);        break;    }    returnmeasureSpec;}

View获得MeasureSpec过程

View的measure过程由ViewGroup传递而来,先看一下ViewGroup的measureChildWithMargins(在实现类的onMeasure中调用,如:LinearLayout->measureVertical-> measureChildBeforeLayout-> measureChildWithMargins)方法:

protected void measureChildWithMargins(View child,int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, intheightUsed) {    finalMarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();     final intchildWidthMeasureSpec = getChildMeasureSpec (parentWidthMeasureSpec,mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin + widthUsed,lp.width);    final intchildHeightMeasureSpec = getChildMeasureSpec (parentHeightMeasureSpec,mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin + heightUsed,lp.height);    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);}

在调用子元素的measure方法之前会先通过getChildMeasureSpec方法来得到子元素的MeaureSpec。所以说,子元素的MeasureSpec的创建与父容器的MeasureSpec和子元素本身的LayoutParams有关,此外还和View的margin及padding有关。我们来看看getChildMeasureSpec方法的实现:

public static int getChildMeasureSpec(int spec, intpadding, int childDimension) {    int specMode= MeasureSpec.getMode(spec);    intspecSize = MeasureSpec.getSize(spec);     int size =Math.max(0, specSize - padding);     intresultSize = 0;    intresultMode = 0;     switch(specMode) {     // 父元素是精确模式,大小就是窗口的大小(MATCH_PARENT或指定大小)。    case MeasureSpec.EXACTLY:         if(childDimension >= 0) {                   //子元素是它自己设定的数值大小           resultSize = childDimension;           resultMode = MeasureSpec.EXACTLY;         } elseif (childDimension == LayoutParams.MATCH_PARENT) {            // 子元素要成为跟父元素一样的尺寸           resultSize = size;           resultMode = MeasureSpec.EXACTLY;         } elseif (childDimension == LayoutParams.WRAP_CONTENT) {            // 子元素要根据内容认定自己的大小,但不能比父元素大           resultSize = size;           resultMode = MeasureSpec.AT_MOST;        }        break;     // 父元素是最大模式,大小不定,但是不能超过窗口的大小(WRAP_CONTENT)。    caseMeasureSpec.AT_MOST:        if(childDimension >= 0) {            // 子元素是它自己设定的数值大小           resultSize = childDimension;           resultMode = MeasureSpec.EXACTLY;        } elseif (childDimension == LayoutParams.MATCH_PARENT) {            // 子元素要成为跟父元素一样的尺寸,但父元素不是固定的,而且子元素不会比父元素大           resultSize = size;           resultMode = MeasureSpec.AT_MOST;        } elseif (childDimension == LayoutParams.WRAP_CONTENT) {            // 子元素要根据内容认定自己的大小,但不能比父元素大           resultSize = size;           resultMode = MeasureSpec.AT_MOST;        }        break;     // Parentasked to see how big we want to be(这模式主要用于系统内部多次Measure情形,一般来说,我们不需要关注此模式)    caseMeasureSpec.UNSPECIFIED:        if(childDimension >= 0) {            //Child wants a specific size... let him have it           resultSize = childDimension;           resultMode = MeasureSpec.EXACTLY;        } elseif (childDimension == LayoutParams.MATCH_PARENT) {            //Child wants to be our size... find out how big it should            //be           resultSize = 0;           resultMode = MeasureSpec.UNSPECIFIED;        } elseif (childDimension == LayoutParams.WRAP_CONTENT) {            //Child wants to determine its own size.... find out how            //big it should be           resultSize = 0;           resultMode = MeasureSpec.UNSPECIFIED;        }        break;    }    returnMeasureSpec.makeMeasureSpec(resultSize, resultMode);}


——本博文部分内容参考自《Android开发艺术探索》

阅读全文
0 0
原创粉丝点击