码迷,mamicode.com
首页 > 移动开发 > 详细

Android应用程序窗口View的measure过程

时间:2016-07-19 10:07:29      阅读:231      评论:0      收藏:0      [点我收藏+]

标签:

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决定。

所以根View DecorView的测量标准获得,与其他普通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()其他细节不再分析了,大家有兴趣可以去分析源码。

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

上面就是测量的过程了.


















Android应用程序窗口View的measure过程

标签:

原文地址:http://blog.csdn.net/hehe26/article/details/51910845

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!