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

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);。它们几个的关系,可以参考下图:

Android View的工作原理(一)之 View的三大过程 和 认识MeasureSpec_第1张图片

ViewRoot的工作

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

Android View的工作原理(一)之 View的三大过程 和 认识MeasureSpec_第2张图片

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, int padding, int childDimension) {
    int specMode= MeasureSpec.getMode(spec);
    int specSize = MeasureSpec.getSize(spec);
 
    int size = Math.max(0, specSize - padding);
 
    int resultSize = 0;
    int resultMode = 0;
 
    switch(specMode) {
 
    // 父元素是精确模式,大小就是窗口的大小(MATCH_PARENT或指定大小)。
    case MeasureSpec.EXACTLY:
 
        if(childDimension >= 0) {
           //子元素是它自己设定的数值大小
           resultSize = childDimension;
           resultMode = MeasureSpec.EXACTLY;
 
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
            // 子元素要成为跟父元素一样的尺寸
           resultSize = size;
           resultMode = MeasureSpec.EXACTLY;
 
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            // 子元素要根据内容认定自己的大小,但不能比父元素大
           resultSize = size;
           resultMode = MeasureSpec.AT_MOST;
        }
        break;
 
    // 父元素是最大模式,大小不定,但是不能超过窗口的大小(WRAP_CONTENT)。
    caseMeasureSpec.AT_MOST:
        if(childDimension >= 0) {
            // 子元素是它自己设定的数值大小
           resultSize = childDimension;
           resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
            // 子元素要成为跟父元素一样的尺寸,但父元素不是固定的,而且子元素不会比父元素大
           resultSize = size;
           resultMode = MeasureSpec.AT_MOST;
        } else if (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;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
            //Child wants to be our size... find out how big it should
            //be
           resultSize = 0;
           resultMode = MeasureSpec.UNSPECIFIED;
        } else if (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开发艺术探索》

 

 

你可能感兴趣的:(Android进阶与总结,measure,layout,MeasureSpec,EXACTLY)