Android应用程序的界面是View组成的,这些View以树形结构组织,它们存在着父子关系,其中,子view位于父view里面,因此,在绘制一个Android应用程序窗口的View之前,我们首先要确定它里面的各个子view在父view里面的大小以及位置。确定各个子view在父view里面的大小以及位置的过程又称为测量过程和布局过程。测量和布局完后,就可以绘制view了.

参考上一篇文章Android应用程序窗口View的创建过程,可以知道Android应用程序窗口的根View是DecorView,而测量,布局,绘制这三个过程都是从DecorView开始的,然后递归到各个子view.

从Activity的onCreate(),到DecorView开始measure(),中间还经过了一些步骤.

下面先简单说一下中间的过程,然后重点分析measure的过程.

首先Activity的onCreate()函数里,我们创建应用程序的界面,也就是DecorView在这里创建好了.

接着Activity的onStart(),OnResume()相继被调用,我们就从OnResume()被调用之后往下看:

1.

在frameworks/base/core/java/android/app/ActivityThread.java中

final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward,            boolean reallyResume) {       ..        ActivityClientRecord r = performResumeActivity(token, clearHide);//....                    wm.addView(decor, l);             }

performResumeActivity()方法就会去调用activity的OnResume()函数,接着wm.addView(decor, l)就准备将应用程序窗口的View添加到窗口管理器中去了

WindowManager是用来管理应用程序窗口的.

2.

在frameworks/base/core/java/android/view/WindowManagerImpl.java中

public void addView(View view, ViewGroup.LayoutParams params) {        mGlobal.addView(view, params, mDisplay, mParentWindow);    }

3.

frameworks/base/core/java/android/view/ WindowManagerGlobal.java中

public void addView(View view, ViewGroup.LayoutParams params,            Display display, Window parentWindow) {      ...            root = new ViewRootImpl(view.getContext(), display);..            root.setView(view, wparams, panelParentView);     ...    }

4.

frameworks/base/core/java/android/view/ ViewRootImpl.java中

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {        ...                requestLayout();             ...                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,                            getHostVisibility(), mDisplay.getDisplayId(),                            mAttachInfo.mContentInsets, mInputChannel);             ....    }

requestLayout()方法里就开始对应用程序窗口的View进行测量,布局,一系列的准备操作了.

mWindowSession.addToDisplay()就是把界面显示出来.

上面过程涉及了几个重要的类ViewRootImpl,WindowManagerImpl,WindowManagerGlobal,这里没详细说了,这里先了解大致流程.以后有时间再细说.

好了,现在就开始进入正题,进入requestLayout()里面.

requestLayout()里面会执行到performTraversals()

performTraversals()里面又会执行到performMeasure(childWidthMeasureSpec, childHeightMeasureSpec),而这个方法,就是开始测量了(Ask host how big it wants to be)

第一步:performTraversals()

frameworks/base/core/java/android/view/ViewRootImpl.java中

int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);  int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);                // Ask host how big it wants to be   performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

performMeasure()函数有两个参数.

childWidthMeasureSpec和childHeightMeasureSpec.分别表示宽度和高度的测量标准,这两个参数是传给DecorView测量用的.看获取该值的函数名字也很明显:getRootMeasureSpec().因为不单单是DecorView需要用测量标准去测量,它的子view,以及它子view的子view都需要自己的一个测量标准去测量,而它们的测量标准获取方法都是一样的,后面会说。

首先来看一下这两个值是怎么得到的:

int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);

int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

上面有三个变量mWidth,mHeight,lp.说明一下

mWidth和mHeight这两个值就是当前窗口(手机屏幕)的宽度和高度(减去状态栏的高度).其实测量过程不止一次,在performTraversals()函数一开始就会去测量一次,

DisplayMetrics packageMetrics =                    mView.getContext().getResources().getDisplayMetrics();                desiredWindowWidth = packageMetrics.widthPixels;                desiredWindowHeight = packageMetrics.heightPixels;... // Ask host how big it wants to be            windowSizeMayChange |= measureHierarchy(host, lp, res,                    desiredWindowWidth, desiredWindowHeight);

这个时候desiredWindowWidth和desiredWindowHeight是手机屏幕的宽度和高度了。

然后由于是第一次启动该画面,所以会请求WindowManagerService服务重新布局系统中的所有窗口.mWidth和mHeight就是WindowManagerService服务分配给该应用程序窗口的宽度和高度.而这时高度就是手机屏幕的高度减去状态栏的高度了。

这个lp 是WindowManager.LayoutParams类型,相当是DecorView的布局参数,每一个view都有自己的布局参数。

在frameworks/base/core/java/android/view/Window.java中 是Window的成员变量,一路传来到这里的

</pre><pre name="code" class="java" style="font-family: Arial; line-height: 26px;">
// The current window attributes.    private final WindowManager.LayoutParams mWindowAttributes =        new WindowManager.LayoutParams();

看一下它的构造函数

frameworks/base/core/java/android/view/WindowManager.java
public LayoutParams() {            super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);            type = TYPE_APPLICATION;            format = PixelFormat.OPAQUE;        }

它的父类ViewGroup.LayoutParams

frameworks/base/core/java/android/view/ViewGroup.java
public LayoutParams(int width, int height) {            this.width = width;            this.height = height;        }

mWidth, mHeight,lp这三个参数知道了,进入getRootMeasureSpec()函数看看

第二步:getRootMeasureSpec()

在frameworks/base/core/java/android/view/ViewRootImpl.java中

private static int getRootMeasureSpec(int windowSize, int rootDimension) {        int measureSpec;        switch (rootDimension) {        case ViewGroup.LayoutParams.MATCH_PARENT:            // Window can't resize. Force root view to be windowSize.            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);            break;        case ViewGroup.LayoutParams.WRAP_CONTENT:            // Window can resize. Set max size for root view.            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);            break;        default:            // Window wants to be an exact size. Force root view to be that size.            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);            break;        }        return measureSpec;    }

这里简单说一下MeasureSpec:

MeasureSpec代表一个32位的int值,高2位代表SpecMode,低30位代表SpecSize,SpecMode是指测量模式,而SpecSize是指在某种测量模式下的规格大小。

public static class MeasureSpec {        private static final int MODE_SHIFT = 30;        private static final int MODE_MASK  = 0x3 << MODE_SHIFT;        public static final int UNSPECIFIED = 0 << MODE_SHIFT;        public static final int EXACTLY     = 1 << MODE_SHIFT;        public static final int AT_MOST     = 2 << MODE_SHIFT;        public static int makeMeasureSpec(int size, int mode) {            if (sUseBrokenMakeMeasureSpec) {                return size + mode;            } else {                return (size & ~MODE_MASK) | (mode & MODE_MASK);            }        }        public static int getMode(int measureSpec) {            return (measureSpec & MODE_MASK);        }        public static int getSize(int measureSpec) {            return (measureSpec & ~MODE_MASK);        }

MeasureSpec是View的一个内部类,SpecMode和SpecSize是一个int值,一组SpecMode和SpecSize可以打包为一个MeasureSpec,而一个MeasureSpec可以通过解包的形式来得出其原始的SpecMode和SpecSize,需要注意的是这里提到MeasureSpec是指MeasureSpec所代表的int值,而并非MeasureSpec本身。

SpecMode有三类,每一类都表示特殊的含义,如下表示。

UNSPECIFIED

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

EXACTLY

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

AT_MOST

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

继续来看getRootMeasureSpec()函数的代码

上面函数不管是获取DecorView宽度的测量标准,还是高度的测量标准,windowSize的是一个常量值

rootDimension的值根据上面LayoutParams的构造函数,知道都是MATCH_PARENT,因此都会执行:

measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);

这么一看DecorView的测量标准是由两个变量决定的,一个是屏幕的宽度(相当于它的父视图),一个是它自己的期望(MATCH_PARENT)。那么其他View的测量标准是由什么决定呢,后面会看到

好了,DecorView的宽度和高度的测量标准都获得了,继续往下看:

第三步:performMeasure()

在frameworks/base/core/java/android/view/ViewRootImpl.java中

  private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {      ..            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);    ...    }

这里的mView就是DecorView了,也就是根视图了.DecorView没有measure()方法,那么继续看他的父类FrameLayout,FrameLayout里面也没有measure()方法,继续看FrameLayout的父类ViewGroup,ViewGroup里面也没有measure()方法,囧!那只剩下ViewGroup的父类的View了,去View看看,View里面必须有.

第四步:measure()

在frameworks/base/core/java/android/view/View.java

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {        if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||                widthMeasureSpec != mOldWidthMeasureSpec ||                heightMeasureSpec != mOldHeightMeasureSpec) {..            // measure ourselves, this should set the measured dimension flag back            onMeasure(widthMeasureSpec, heightMeasureSpec);       ....    }

一看是final修饰,就知道为什么子类没有了.这里又调用它自己的onMeasure()函数,就是DecorView的onMeasure()函数

第五步:onMeasure()

在frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindow.java

  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {            final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();            final boolean isPortrait = metrics.widthPixels < metrics.heightPixels;            final int widthMode = getMode(widthMeasureSpec);            final int heightMode = getMode(heightMeasureSpec);            boolean fixedWidth = false;            if (widthMode == AT_MOST) {               ...            }            if (heightMode == AT_MOST) {             ...            }            super.onMeasure(widthMeasureSpec, heightMeasureSpec);            int width = getMeasuredWidth();            boolean measure = false;            widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, EXACTLY);            if (!fixedWidth && widthMode == AT_MOST) {             ...            }            // TODO: Support height?            if (measure) {               ..            }        }

final int widthMode = getMode(widthMeasureSpec);

final int heightMode = getMode(heightMeasureSpec);

根据第二步知道,这两个值都是EXACTLY

接着super.onMeasure(widthMeasureSpec, heightMeasureSpec);调用它父类的onMeasure()函数,也就是FrameLayout的onMeasure()函数

第六步:onMeasure()

frameworks/base/core/java/android/widget/FrameLayout.java

 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        int count = getChildCount();//获取decorView有多少个子view        final boolean measureMatchParentChildren =                MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||                MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;        mMatchParentChildren.clear();        int maxHeight = 0;        int maxWidth = 0;        int childState = 0;        for (int i = 0; i < count; i++) {            final View child = getChildAt(i);            if (mMeasureAllChildren || child.getVisibility() != GONE) {                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);                final LayoutParams lp = (LayoutParams) child.getLayoutParams();                maxWidth = Math.max(maxWidth,                        child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);                maxHeight = Math.max(maxHeight,                        child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);                childState = combineMeasuredStates(childState, child.getMeasuredState());                if (measureMatchParentChildren) {                    if (lp.width == LayoutParams.MATCH_PARENT ||                            lp.height == LayoutParams.MATCH_PARENT) {                        mMatchParentChildren.add(child);                    }                }            }        }        // Account for padding too        maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();        maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();        // Check against our minimum height and width        maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());        // Check against our foreground's minimum height and width        final Drawable drawable = getForeground();        if (drawable != null) {            maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());            maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());        }        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),                resolveSizeAndState(maxHeight, heightMeasureSpec,                        childState << MEASURED_HEIGHT_STATE_SHIFT));        count = mMatchParentChildren.size();        if (count > 1) {            for (int i = 0; i < count; i++) {                final View child = mMatchParentChildren.get(i);                final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();                int childWidthMeasureSpec;                int childHeightMeasureSpec;                                if (lp.width == LayoutParams.MATCH_PARENT) {                    childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth() -                            getPaddingLeftWithForeground() - getPaddingRightWithForeground() -                            lp.leftMargin - lp.rightMargin,                            MeasureSpec.EXACTLY);                } else {                    childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,                            getPaddingLeftWithForeground() + getPaddingRightWithForeground() +                            lp.leftMargin + lp.rightMargin,                            lp.width);                }                                if (lp.height == LayoutParams.MATCH_PARENT) {                    childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight() -                            getPaddingTopWithForeground() - getPaddingBottomWithForeground() -                            lp.topMargin - lp.bottomMargin,                            MeasureSpec.EXACTLY);                } else {                    childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,                            getPaddingTopWithForeground() + getPaddingBottomWithForeground() +                            lp.topMargin + lp.bottomMargin,                            lp.height);                }                child.measure(childWidthMeasureSpec, childHeightMeasureSpec);            }        }    } 

这个函数需要注意一下,不同类型的ViewGroup子类,这个函数的实现不尽相同.

但是函数的作用都是一样的:

1.遍历自己的子view,然后测量每一个子view,如果子view也是ViewGroup,则递归下去.

2.设置自己的测量宽度和测量高度.

首先来看一下测量子view的过程.看measureChildWithMargins()函数

第七步:measureChildWithMargins()

frameworks/base/core/java/android/view/ViewGroup.java

 protected void measureChildWithMargins(View child,            int parentWidthMeasureSpec, int widthUsed,            int parentHeightMeasureSpec, int heightUsed) {        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();//首先获取子View的布局参数        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin                        + widthUsed, lp.width);        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin                        + heightUsed, lp.height);        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);    }
在这个情景,这个函数根据DecorView的测量标准和它的子view的布局参数来确定它的子view的测量标准,然后子view开始自己的测量过程。其他情况也就是普通的ViewGroup的测量标准和它的子view的布局参数来确定它的子view的测量标准.

第八步:getChildMeasureSpec()

frameworks/base/core/java/android/view/ViewGroup.java

 public static int getChildMeasureSpec(int spec, int padding, int childDimension) {        int specMode = MeasureSpec.getMode(spec);//这个获取的是decorview的测量标准里面的mode        int specSize = MeasureSpec.getSize(spec);//这个获取的是decorview的测量标准里面的值          int size = Math.max(0, specSize - padding);//这个padding包括decorview的padding值 和它子view的margin值        int resultSize = 0;        int resultMode = 0;        switch (specMode) {        // Parent has imposed an exact size on us        case MeasureSpec.EXACTLY://现在decorview的是这种mode            if (childDimension >= 0) {//如果decorview的子view设置了固定值,那么它的测量标准也就定了                resultSize = childDimension;                resultMode = MeasureSpec.EXACTLY;            } else if (childDimension == LayoutParams.MATCH_PARENT) {                // Child wants to be our size. So be it.                resultSize = size;                resultMode = MeasureSpec.EXACTLY;            } else if (childDimension == LayoutParams.WRAP_CONTENT) {                // Child wants to determine its own size. It can't be                // bigger than us.                resultSize = size;                resultMode = MeasureSpec.AT_MOST;            }            break;        // Parent has imposed a maximum size on us        case MeasureSpec.AT_MOST:            if (childDimension >= 0) {                // Child wants a specific size... so be it                resultSize = childDimension;                resultMode = MeasureSpec.EXACTLY;            } else if (childDimension == LayoutParams.MATCH_PARENT) {                // Child wants to be our size, but our size is not fixed.                // Constrain child to not be bigger than us.                resultSize = size;                resultMode = MeasureSpec.AT_MOST;            } else if (childDimension == LayoutParams.WRAP_CONTENT) {                // Child wants to determine its own size. It can't be                // bigger than us.                resultSize = size;                resultMode = MeasureSpec.AT_MOST;            }            break;        // Parent asked to see how big we want to be        case MeasureSpec.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;        }        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);    }

childDimension值是子view的布局宽度和高度,一般在布局文件里面

android:layout_width=""

android:layout_height=""

设置成固定值或者MATCH_PARENT,WRAP_CONTENT,FILL_PARENT.

FILL_PARENT和MATCH_PARENT值一样.

总的来说,DecorView的子view测量标准也有两个变量决定的,一个是DecorView(父view)的测量标准,一个是DecorView的子view它自己的布局参数.

也就是说一般View的测量标准是由它父容器的测量标准和它本身的LayoutParams决定。

所以根ViewDecorView的测量标准获得,与其他普通View的测量标准获得有一定区别。

总结一下上面函数:

一:如果父View的specMode是MeasureSpec.EXACTLY:

①如果子view的布局宽度或高度是常量值,那么子view的SpecSize就是子view的布局宽度或高度的常量值,specMode为MeasureSpec.EXACTLY.

如果子view的布局宽度或高度是MATCH_PARENT,那么子view的SpecSize就是父view的SpecSizespecModeMeasureSpec.EXACTLY.

如果子view的布局宽度或高度是WRAP_CONTENT,那么子view的SpecSize就是父view的SpecSizespecModeMeasureSpec.AT_MOST。

二:如果父view的specMode是MeasureSpec.AT_MOST:

①如果子view的布局宽度或高度是常量值,那么子view的SpecSize就是子view的布局宽度或高度的常量值,specMode为MeasureSpec.EXACTLY.

②如果子view的布局宽度或高度是MATCH_PARENT,那么子view的SpecSize就是父view的SpecSizespecMode为MeasureSpec.AT_MOST.

③如果子view的布局宽度或高度是WRAP_CONTENT,那么子view的SpecSize就是父view的SpecSizespecModeMeasureSpec.AT_MOST。

三:如果父view的specMode是MeasureSpec.UNSPECIFIED:

①如果子view的布局宽度或高度是常量值,那么子view的SpecSize就是子view的布局宽度或高度的常量值,specMode为MeasureSpec.EXACTLY.

②如果子view的布局宽度或高度是MATCH_PARENT,那么子view的SpecSize就是0,specMode为MeasureSpec.UNSPECIFIED.

③如果子view的布局宽度或高度是WRAP_CONTENT,那么子view的SpecSize就是0,specMode为MeasureSpec.UNSPECIFIED

第三情况可以忽略。

获取子view的测量标准后回到第七步,子view要根据它的测量标准去测量自己宽度和高度了

<span style="font-size:14px;"> child.measure(childWidthMeasureSpec, childHeightMeasureSpec);</span>

这样就递归下去了,如果该child是ViewGroup的话,则又会调用它的onMeasure()函数,然后一个个去获取它子view的测量标准,函数也是getChildMeasureSpec(),接着它的子view调用自己的measure()函数去测量,这样一直递归到一个具体的view,在该view的onMeasure()函数里调用setMeasuredDimension()来设置自己测量宽度和高度了,

第九步:setMeasuredDimension()

frameworks/base/core/java/android/view/view.java

 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));    }

onMeasure()的两个参数也就是该View的宽度和高度的测量标准了.

 protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {        mMeasuredWidth = measuredWidth;        mMeasuredHeight = measuredHeight;        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;    }
view的测量宽度和高度就是getDefaultSize()函数的返回值了

首先来看一下getSuggestedMinimumWidth()函数

第十步:getSuggestedMinimumWidth()

frameworks/base/core/java/android/view/view.java

 protected int getSuggestedMinimumWidth() {        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());    }
如果该View没有设置背景,那么返回mMinWidth,而mMinWidth对应于android:minWidth这个属性所指定的值,这个属性如果不指定,那么mMinWidth为0,如果view指定了背景,则返回mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());那么mBackground.getMinimumWidth()是什么呢?我们看一下Drawable的getMinimumWidth()函数

 public int getMinimumWidth() {        final int intrinsicWidth = getIntrinsicWidth();        return intrinsicWidth > 0 ? intrinsicWidth : 0;    }
可以看出,getMinimumWidth返回的就是Drawable的原始宽度,前提是这个Drawable有原始宽度,否则就返回0.那么Drawable在什么情况下有原始高度呢,这里举个例子说明一下,ShapeDrawable无原始宽/高,而BitmapDrawable有原始宽/高(图片的尺寸),以后有机会再讲。因为获取宽度和高度的原理一样,就没看了
回到第九步,继续看getDefaultSize()函数。

第十一步:getDefaultSize()

frameworks/base/core/java/android/view/view.java

public static int getDefaultSize(int size, int measureSpec) {        int result = size;        int specMode = MeasureSpec.getMode(measureSpec);        int specSize = MeasureSpec.getSize(measureSpec);        switch (specMode) {        case MeasureSpec.UNSPECIFIED://这种情况才与上面第九步有关            result = size;            break;        case MeasureSpec.AT_MOST:        case MeasureSpec.EXACTLY:            result = specSize;            break;        }        return result;    }

可以看出,这个函数的逻辑很简单,对于我们来说,我们只需要看AT_MOST和EXACTLY这两种情况。简单的理解,其实getDefaultSize返回的大小就是MeasureSpec(测量标准)中的specSize,而这个specSize就是具体View测量后的大小,View有测量的宽度和高度,就是测量过程定的。还有最终的宽度和高度,在layout(布局)阶段确定的,一般情况下,测量的宽度高度就等于最终的宽度高度。下一篇讲layout过程的时候会讲到。

具体view在基类View中onMeasure()函数里都有了默认的实现。就是上面第九步第十一步

而像DecorView这样的ViewGroup重写该onMeasure()函数,第五步第六步,这是因为ViewGroup还需要去遍历它的子view,让它子view又去测量它们自己,而且不同类型的ViewGroup子类有不同的布局特性,这导致它们的测量细节各不相同,如LinerLayout和FrameLayout这两者的布局特性显然不同。

但是viewgroup递归测量完自己的子view后,也要通过setMeasuredDimension()的设置自己测量宽度和高度.回到第六步DecorView在递归测量完自己的子view后,要设置自己测量宽度和高度

frameworks/base/core/java/android/view/view.java

 setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),                resolveSizeAndState(maxHeight, heightMeasureSpec,                        childState << MEASURED_HEIGHT_STATE_SHIFT));


frameworks/base/core/java/android/view/ViewGroup.java

 public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {        if (Build.VERSION.SDK_INT >= 11) {            return View.resolveSizeAndState(size, measureSpec, childMeasuredState);        }        int result = size;        int specMode = MeasureSpec.getMode(measureSpec);        int specSize =  MeasureSpec.getSize(measureSpec);        switch (specMode) {        case MeasureSpec.UNSPECIFIED:            result = size;            break;        case MeasureSpec.AT_MOST:            if (specSize < size) {                result = specSize | MEASURED_STATE_TOO_SMALL;            } else {                result = size;            }            break;        case MeasureSpec.EXACTLY:            result = specSize;            break;        }        return result | (childMeasuredState&MEASURED_STATE_MASK);    }

可见一个View或ViewGroup的测量宽度和高度,都是与它的测量标准相关的.只不过ViewGroup还要考虑其他情况.

至于它的onMeasure()其他细节不再分析了,大家有兴趣可以去分析源码。

测量过程可能要重复几次.

上面就是测量的过程了.


















更多相关文章

  1. android解决无法设定listview的item高度
  2. Android为Layout设置最大宽度
  3. Android获取状态栏、标题栏、ActionBar以及屏幕的高度
  4. Android获取屏幕的高度和宽度
  5. 【Android】获取屏幕分辨率和顶栏高度全屏和横屏
  6. android之4.0控件switch自定义开关背景图片和控制宽度
  7. Android获取系统顶部状态栏(Status Bar)和底部导航栏(Navigation
  8. android获取控件宽度
  9. Android--获取标题栏,状态栏,屏幕高度

随机推荐

  1. Grafana-server 6.2安装 1
  2. 美国人眼里的IaaS领导品牌调查(附原文)(20
  3. zabbix监控DELL iRADC风扇+温度+内存(zabb
  4. 最强监控---zabbix-server安装配置教程--
  5. Linux 下数据库定时备份
  6. zabbix设置企业微信群内机器人报警
  7. zabbix-agent安装配置------------------
  8. zabbix server 内存溢出 @第一次遇到
  9. TCP/IP 三次握手 四次握手
  10. weadmin