Android View的工作流程总结分析(二)-Measure

接上文,下面讲讲View工作的三大流程之一:
Measure流程

一.Measure 过程说明:

  • ViewRootImpl的performTraversals方法开始View的工作流程,里面有一系列的判断,当前是否需要Measure,如果需要,则执行ViewRootImpl的performMeasure方法开始测量。performMeasure方法会先调用DecorView的measure方法,实际上就是调用View的measure(这个方法是final的,不允许子类重写),然后View的measure方法会判断是否需要测量(3.3分析),如果需要测量回执行View的onMeasure方法,同时ViewGroup(如果是ViewGroup的话)的宽高限制(widthMeasureSpec,heightMeasureSpec)也会传递到onMeasure,onMeasure方法会真正完成View的测量工作,ViewGroup的onMeasure方法又会遍历调用子View的measure方法,完成子View的测量过程,这样一层层传递详情,完成View树上所有View的测量,最后通过setMeasureDimension方法保存最后的测量宽高到mMeasureWidth,mMeasureHeight

二.MeasureSpec

  • Measure流程是三个流程里面最复杂的,先说下一个概念MeasureSpec MeasureSpec
    封装了父View传递给子View的布局要求,它是根据子View的layoutParams属性(layout_windth,layout_height等等)和父View自身的宽高生成的一个尺寸需求。当前View会根据这个值来设置自己的测量宽高(当然自定义View的时候你也可以不用它,直接设置宽高,当然最好不要这么做)。
    MeasureSpec
    本身不代表任何值,它相当于一个工具类,用来将specMode测量模式和specSize规格大小打包生成一个32位的int值,它的高两位代表了specMode规格模式和低30为数值对应specSize具体的尺寸值。MeasureSpec也提供了解包的方法,获得specMode和specSize的对应的具体的int值
    specMode 有三种取值 MeasureSpec.EXACTLY 父View希望子VIew的大小是确定的,就是specSize的大小
    MeasureSpec.AT_MOST 父View希望子View的大小最大是specSIze对应的值
    MeasureSpec.UNSPECIFIED 父View希望子View大小完全按照自己设置的值来决定

具体代码如下:

      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;
      //size和mode位于产生一个32位int值MeasureSpec
      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);
        }

三.源码分析:

3.1 performMeasure方法 开始Measure流程

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

3.2 getRootMeasureSpec方法 计算ViewRoot对根视图DecorView的宽高限制
这个mView实际就是DecorView的对象,上面说过了,这是Activity界面的根视图
childWithMeasureSpec 和childHightMeasureSpec是调用下面这个方法获取的

//windowSize 
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
        int measureSpec;
        switch (rootDimension) {
        //默认情况下是MACTCH_PARENT(这也是我们平常在一个Activity里什么都不设置,就默认显示全屏的原因)
        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;
    }

如果我们没有修改窗口属性,默认情况的windowSize是屏幕宽高,rootDimension是LayoutParams.MACTH_PARENT
已屏幕宽度1080,设置竖屏,默认情况下得到的childWithMeasureSpec=1073742904 ( 二进制:01000000000000000000010000111000);
高两位01表示specMode EXACITY 根View的parent ViewRootImpl希望根View DecorView的大小是确定的就是低30位(000000000000000000010000111000)=1080

3.3 View的measure方法
mView.measure就是调用view的measure方法
看一下view的measure源码

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
//判断是否是视觉边界布局模式,这样layoutmode为LAYOUT_MODE_OPTICAL_BOUNDS为true,默认是clipBound模式
        boolean optical = isLayoutModeOptical(this);
        //当前视图和父视图只能有一个是边界布局模式才会执行下面的方法。
        //视觉边界布局模式只对设置了9patch图片背景的View的有效果,其他情况和默认相
        //Insets.NONE,left right top bottom都是0.
        if (optical != isLayoutModeOptical(mParent)) {
            Insets insets = getOpticalInsets();
            int oWidth  = insets.left + insets.right;
            int oHeight = insets.top  + insets.bottom;
            widthMeasureSpec  = MeasureSpec.adjust(widthMeasureSpec,  optical ? -oWidth  : oWidth);
            heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
        }
//key保存了父View传递下来的宽高限制long型8个字节(高4个字节代表widthMeasureSpec ,低4个字节代表heightMeasureSpec )
        // Suppress sign extension for the low bytes
        long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
        //mMeasureCache LongSparseLongArray容器对象,存储View在不同的widthMeasureSpec 和heightMeasureSpec 的情况下的测量结果。
        if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
//forceLayout 当前View是否需要执行强制layout mPrivateFlags 标记了View的工作状态。
//调用View的requestLayout方法会标记这个PFLAG_FORCE_LAYOUT
//执行View layout最后会标记~PFLAG_FORCE_LAYOUT,标记不在强制布局状态
        final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;

        // Optimize layout by avoiding an extra EXACTLY pass when the view is
        // already measured as the correct size. In API 23 and below, this
        // extra pass is required to make LinearLayout re-distribute weight.
        //宽高限制是否变更mOldWidthMeasureSpec,mOldHeightMeasureSpec默认为Integer的最小值0x80000000 
        //specChanged代表ViewGroup传递下来的MeasureSpec与上一次相比是否发生变化,变化了为true
        final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
                || heightMeasureSpec != mOldHeightMeasureSpec;
                //isSpecExactly 测量宽高限制是否都是EXACTLY模式 ,都是EXACTLY模式为true
        final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
                && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
                //当前View的测量宽高是否都对于MeasureSpec传递下来的宽高size,都相等matchesSpecSize 为true
        final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
                && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
                //needsLayout 是否需要重新测量
                //(如果ViewGroup传递下来的MeasureSpec发生变化并且sAlwaysRemeasureExactly 为true,isSpecExactly 为false,matchesSpecSize为false有一个成立时
                //needsLayout 为true);sAlwaysRemeasureExactly 在6.0以下均为false,6.0及以上为true。
        final boolean needsLayout = specChanged
                && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);
//如果View设置了强制测量或者当前View需要测量
        if (forceLayout || needsLayout) {
            // first clears the measured dimension flag
            //mPrivateFlags View 状态,将标记View已经测量的位(PFLAG_MEASURED_DIMENSION_SET)置为0,清空measured dimension标识
            mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
//对阿拉伯语、希伯来语等从右到左书写、布局的语言进行特殊处理(一般情况下不需要考虑)
            resolveRtlPropertiesIfNeeded();
//是否能从mMeasureCache中读取测量值,如果是设置forceLayout cacheIndex =-1,否则根据MeasureSpec值计算缓存索引。
            int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
            //sIgnoreMeasureCache 忽略测量缓存,API<19时为true,即在API<19时,肯定会调用onMeasure方法
            //cacheIndex < 0 forceLayout或者在测量缓存中没有找到对应的测量Spec,则重新测量。调用onMeasure方法
            if (cacheIndex < 0 || sIgnoreMeasureCache) {
                // measure ourselves, this should set the measured dimension flag back
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                //重置标志位mPrivateFlags3 标识layout之前不需要重新测量了
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            } else {
            //可以从测量缓存中直接读到测量过的尺寸
                long value = mMeasureCache.valueAt(cacheIndex);
                // Casting a long to int drops the high 32 bits, no mask needed
                //直接多View的mMeasureWidth和mMeasureHeight赋值
                setMeasuredDimensionRaw((int) (value >> 32), (int) value);
                mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            }

            // flag not set, setMeasuredDimension() was not invoked, we raise
            // an exception to warn the developer
            //如果View没有调用setMeasuredDimension方法(即mPrivateFlags 没有标记PFLAG_MEASURED_DIMENSION_SET状态,已经设置了测量尺寸,实际调用setMeasuredDimensionRaw方法时会操作mPrivateFlags 标记)会抛出异常
            if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
                throw new IllegalStateException("View with id " + getId() + ": "
                        + getClass().getName() + "#onMeasure() did not set the"
                        + " measured dimension by calling"
                        + " setMeasuredDimension()");
            }

            mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
        }

        mOldWidthMeasureSpec = widthMeasureSpec;
        mOldHeightMeasureSpec = heightMeasureSpec;
//将新的测量值mMeasuredWidth,mMeasuredHeight 打包一个long对象存储到mMeasureCache里
        mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
                (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
    }

说明:
a.View 的measure方法不是一调用就会进行测量的
b.View 会根据当前传递下来的MeasureSpec宽高限制是否发生变化(模式改变或者size变化),是否调用了forceLayout方法强制重新测量来判断是否重新测量,重新对测量宽高赋值。
c.如果MeasureSpec发生变化,View会在根据当前的MeasureSpec从测量缓存mMeasureCache中查询是否已经测量过了,如果测量过了,直接调用setDimensionRaw方法对View的mMeasureWidth和mMeasureHeight属性赋值
d.如果是forceLayout模式,或者API<19,或者当前传递下来的MeasureSpec没有进行测量过,那么View只能调用onMeasure方法测量一次了。

3.4 View的onMeasure方法
View 默认的onMeasure方法很简单,调用setMeasuredDimension方法

  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  //View的onMeasure默认调用setMeasureDimension方法设置View的测量宽高。
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

 protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
        boolean optical = isLayoutModeOptical(this);
        //视觉边界布局模式效果,忽略
        if (optical != isLayoutModeOptical(mParent)) {
            Insets insets = getOpticalInsets();
            int opticalWidth  = insets.left + insets.right;
            int opticalHeight = insets.top  + insets.bottom;

            measuredWidth  += optical ? opticalWidth  : -opticalWidth;
            measuredHeight += optical ? opticalHeight : -opticalHeight;
        }
        //最后实际调用setMeasuredDimensionRaw方法
        setMeasuredDimensionRaw(measuredWidth, measuredHeight);
    }

private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
//对mMeasuredWidth ,mMeasuredHeight 属性赋值,mPrivateFlags 标记为已经测量了。
        mMeasuredWidth = measuredWidth;
        mMeasuredHeight = measuredHeight;

        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
    }

看一下getDefaultSize方法,默认的测量宽高计算方法

//获得最小宽度
protected int getSuggestedMinimumWidth() {
//先判断是否有背景,无的话取mMinWidth值 ,默认为0
//如果有背景的话,取图片背景的宽度和mMinWidth的最大值作为最大宽度。
        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
    }
    //同最小宽度的获取
protected int getSuggestedMinimumHeight() {
        return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());

    }
    //计算测量宽高值
public static int getDefaultSize(int size, int measureSpec) {
//View的最小宽高值
        int result = size;
        //ViewGroup对View的宽高限制模式,具体怎么算的看下面的流程(3.7)
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
        //UNSPECIFIED 模式取最小宽高
            result = size;
            break;
            //AT_MOST EXACTLY模式取ViewGroup对子View的限制宽高
            //这里就是我们设置自定义View时不重写onMeasure方法设置MACTH和WRAP属性完全一样的一样,所以一般情况下自定义View时我们都需重写onMeasure方法。
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }

3.5 DecorView的onMeasure 方法
接着View的Measure流程,刚开始View Measure肯定调用onMeasure方法的,DecorView重写了View的onMeasure方法,看一下DecorView的onMeasure方法源码:

  @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
            //是否是竖屏
            final boolean isPortrait = metrics.widthPixels < metrics.heightPixels;
    //获取ViewRoot传递下来的宽高规格模式
            final int widthMode = getMode(widthMeasureSpec);
            final int heightMode = getMode(heightMeasureSpec);

            boolean fixedWidth = false;
            //设置窗口的属性是WARP_CONTENT的话,就会走下面代码,重新计算widthMeasureSpec 和heightMeasureSpec 
            if (widthMode == AT_MOST) {
                final TypedValue tvw = isPortrait ? mFixedWidthMinor : mFixedWidthMajor;
                if (tvw != null && tvw.type != TypedValue.TYPE_NULL) {
                    final int w;
                    if (tvw.type == TypedValue.TYPE_DIMENSION) {
                        w = (int) tvw.getDimension(metrics);
                    } else if (tvw.type == TypedValue.TYPE_FRACTION) {
                        w = (int) tvw.getFraction(metrics.widthPixels, metrics.widthPixels);
                    } else {
                        w = 0;
                    }

                    if (w > 0) {
                        final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
                        widthMeasureSpec = MeasureSpec.makeMeasureSpec(
                                Math.min(w, widthSize), EXACTLY);
                        fixedWidth = true;
                    }
                }
            }

            if (heightMode == AT_MOST) {
                final TypedValue tvh = isPortrait ? mFixedHeightMajor : mFixedHeightMinor;
                if (tvh != null && tvh.type != TypedValue.TYPE_NULL) {
                    final int h;
                    if (tvh.type == TypedValue.TYPE_DIMENSION) {
                        h = (int) tvh.getDimension(metrics);
                    } else if (tvh.type == TypedValue.TYPE_FRACTION) {
                        h = (int) tvh.getFraction(metrics.heightPixels, metrics.heightPixels);
                    } else {
                        h = 0;
                    }
                    if (h > 0) {
                        final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
                        heightMeasureSpec = MeasureSpec.makeMeasureSpec(
                                Math.min(h, heightSize), EXACTLY);
                    }
                }
            }
      //下面这代码没看出来是干啥的,window属性下面是有个windowOutsetBottom,但是没有找到设置值的主题,也知道的同学麻烦告诉一下
            if (mOutsetBottom != null) {
                int mode = MeasureSpec.getMode(heightMeasureSpec);
                if (mode != MeasureSpec.UNSPECIFIED) {
                    int outset = (int) mOutsetBottom.getDimension(metrics);
                    int height = MeasureSpec.getSize(heightMeasureSpec);
                    heightMeasureSpec = MeasureSpec.makeMeasureSpec(height + outset, mode);
                }
            }
    //调用FrameLayout的onMeasure方法
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);

            int width = getMeasuredWidth();
            boolean measure = false;

            widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, EXACTLY);
     //非全屏情况,设置window attribute WARP_CONTENT情况(我理解的下面这里就是让WARP_CONTENT情况下宽度不会太小,不会正好等于子控件的宽度,具体需要在研究一下,高度没有这个,使用WARP_CONTENT情况下高度最大为子空间的显示宽度)
            if (!fixedWidth && widthMode == AT_MOST) {
                final TypedValue tv = isPortrait ? mMinWidthMinor : mMinWidthMajor;
                if (tv.type != TypedValue.TYPE_NULL) {
                    final int min;
                    if (tv.type == TypedValue.TYPE_DIMENSION) {
                        min = (int)tv.getDimension(metrics);
                    } else if (tv.type == TypedValue.TYPE_FRACTION) {
                        min = (int)tv.getFraction(metrics.widthPixels, metrics.widthPixels);
                    } else {
                        min = 0;
                    }

                    if (width < min) {
                        widthMeasureSpec = MeasureSpec.makeMeasureSpec(min, EXACTLY);
                        measure = true;
                    }
                }
            }

            // TODO: Support height?

            if (measure) {
                super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            }
        }

最后还是调用了FrameLayout的onMeasure方法。
3.6 FrameLayout的onMeasure方法

  • 3.6.1
    onMeasure源码分析
 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    //获取子控件的数量
        int count = getChildCount();
//widthMeasureSpec heightMeasureSpec是否都是MeasureSpec.EXACTLY模式,false的时候都是EXACTLY模式
        final boolean measureMatchParentChildren =
                MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
                MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
                //清空mMatchParentChildren集合
        mMatchParentChildren.clear();
//局部变量 maxHeight maxWidth 最大宽度  childState 子View的状态
        int maxHeight = 0;
        int maxWidth = 0;
        int childState = 0;
//遍历所有的子View
        for (int i = 0; i < count; i++) {
        //获取子控件
            final View child = getChildAt(i);
            //mMeasureAllChildren  boolean值默认为false ,控制是否测量所有的子View(true是测量Visibility为GONE的子View,false不测量),可以通过setMeasureAllChildren(boolean measureAll)方法更改属性值
            if (mMeasureAllChildren || child.getVisibility() != GONE) {
            //测量子View的宽高--3.6.2(ViewGroup的重要方法)
                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);         //子View向FrameLayout要求的布局属性
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                //给maxWidth和maxHeight赋值,【重要】这里就是FrameLayout的实际宽度的一个重要指标了,maxWidth和maxHeight的最终值为FrameLayout的所有孩子的(子View的测量宽高+子View的Margin)的最大值,
                maxWidth = Math.max(maxWidth,
                        child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
                maxHeight = Math.max(maxHeight,
                        child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
                        //获取子View宽高State信息,这个State是标识是子View告诉父View,当前的测量完的宽高是否满足我的需求
                childState = combineMeasuredStates(childState, child.getMeasuredState());
                //父View的传递下来的MeasureSpec宽高需至少有一个不是EXACTLY模式时
                if (measureMatchParentChildren) {
                    if (lp.width == LayoutParams.MATCH_PARENT ||
                            lp.height == LayoutParams.MATCH_PARENT) {
                            //MeasureSpec Mode不为EXACTLY时,同时子View宽高属性为MATCH_PARENT时,将这个View加入到mMatchParentChildren集合中。
                            //子View为MACTH_PARENT时,FrameLayout的ViewGroup传递下来的specMode不为EXACTLY,将子View加入到mMatchParentChildren集合中,重新测量。因为到目前为止FrameLayout的最终测量宽高还没有赋值,所有MACTH的时候,子View不好确定自身的测量宽高。如果EXACLTY的话,他的测量宽高肯定是specSize的值,已确定。
                        mMatchParentChildren.add(child);
                    }
                }
            }
        }

        // Account for padding too
        //计算FrameLayout的padding
        maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
        maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();

        // Check against our minimum height and width
        //得到View的最大宽高,取子View宽高+margin和最小宽高的最大值
        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());
        }
//mMeasureWidth,mMeasureHeight赋值
//resolveSizeAndState方法计算最终的测量值
//setMeasuredDimension View的方法,前面说过,最后调用setMeasuredDimensionRaw方法直接对View的属性mMeasureWidth,mMeasureHeight赋值
//执行完这个方法,实际View的测量宽高已经确定了
        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                resolveSizeAndState(maxHeight, heightMeasureSpec,
                        childState << MEASURED_HEIGHT_STATE_SHIFT));
//因为FrameLayout宽或者高不确定,导致子View宽高不确定的,重新计算MeasureSpec再调用子View的Measure方法测量一次
        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();
//计算新的childWidthMeasureSpec,下面的Height类似
                final int childWidthMeasureSpec;
                if (lp.width == LayoutParams.MATCH_PARENT) {
                //子View是MATCH_PARENT这个时候要和FrameLayout宽度减去左右padding一样
              //排除FrameLayout最终的测量宽度为0的情况
                    final int width = Math.max(0, getMeasuredWidth()
                            - getPaddingLeftWithForeground() - getPaddingRightWithForeground()
                            - lp.leftMargin - lp.rightMargin);
             //调用makeMeasureSpec组合成childWidthMeasureSpec ,specMode为EXACTLY
                    childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
                            width, MeasureSpec.EXACTLY);
                } else {
                //其他情况不变(和正常的计算方法一样--3.6.2)
                    childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                            getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
                            lp.leftMargin + lp.rightMargin,
                            lp.width);
                }

                final int childHeightMeasureSpec;
                if (lp.height == LayoutParams.MATCH_PARENT) {
                    final int height = Math.max(0, getMeasuredHeight()
                            - getPaddingTopWithForeground() - getPaddingBottomWithForeground()
                            - lp.topMargin - lp.bottomMargin);
                    childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
                            height, MeasureSpec.EXACTLY);
                } else {
                    childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
                            getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
                            lp.topMargin + lp.bottomMargin,
                            lp.height);
                }
        //重新测量
                child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
            }
        }
    }
  • 3.6.2
    measureChildWithMargins方法,ViewGroup计算子View的measureSpec主要方法之一
    (还有个measureChild方法,区别就是没有计算子View的Margin)
protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
//实际是调用ViewGroup的getChildMeasureSpec方法,根据父View的measureSpec和子View的layoutParams来计算自己的measureSpec.具体怎么计算的见3.7。
//传递的参数有:1,父View的Spec ,2.padding(width是左右padding+左右Margin++其他子View已经使用的宽度(FrameLayout withUsed和heightUsed都是0) height是上下类似),。3.view的宽度,这个值可能是具体的值(EXACTLY)可能是MATCH_PARENT(ECACTLY或者AT_MOST),可能是WRAP_CONTENT(AT_MOST)
        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);
//测量子View,看到了吗,这里View的测量继续向下传递了
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }
  • 3.6.3
    看一下View的resolveSizeAndState方法,这个是生成当前View的实际测量值的方法
public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
//解包,获得specMode specSize
        final int specMode = MeasureSpec.getMode(measureSpec);
        final int specSize = MeasureSpec.getSize(measureSpec);
        //定义局部变量,最后返回result
        final int result;
        switch (specMode) {
        //AT_MOST模式,父View要求当前View最大为specSize
            case MeasureSpec.AT_MOST:
            //父View允许的specSize 的小于View的最小宽度需求
                if (specSize < size) {
                //result的高1个字节为 MEASURED_STATE_TOO_SMALL 0x01000000 state告诉ViewGroup分配的尺寸太小了
                //低三个字节为实际的尺寸大小
                    result = specSize | MEASURED_STATE_TOO_SMALL;
                } else {
                    result = size;
                }
                break;
            case MeasureSpec.EXACTLY:
            //注意这里,当为ECACTLY模式的时候,View的测量值就是specSize,不论多大或者多小
                result = specSize;
                break;
                //其他情况直接等于View当前计算的最小宽高(这种情况一般不用考虑,DecorView是没有传递这种模式,一般是系统的测量和我们关系不大)
            case MeasureSpec.UNSPECIFIED:
            default:
                result = size;
        }
        return result | (childMeasuredState & MEASURED_STATE_MASK);
    }

3.7 ViewGroup计算子View的MeasureSpec,对子View的宽高需求
getChildMeasureSpec也是ViewGroup的重要方法 计算其子View对应的SPEC

    /**
     * Does the hard part of measureChildren: figuring out the MeasureSpec to
     * pass to a particular child. This method figures out the right MeasureSpec
     * for one dimension (height or width) of one child view.
     *
     * The goal is to combine information from our MeasureSpec with the
     * LayoutParams of the child to get the best possible results. For example,
     * if the this view knows its size (because its MeasureSpec has a mode of
     * EXACTLY), and the child has indicated in its LayoutParams that it wants
     * to be the same size as the parent, the parent should ask the child to
     * layout given an exact size.
     *
     * @param spec The requirements for this view
     * @param padding The padding of this view for the current dimension and
     *        margins, if applicable
     * @param childDimension How big the child wants to be in the current
     *        dimension
     * @return a MeasureSpec integer for the child
     */
    public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
    //规格模式--EXACTLY AT_MOST UNSPECIFIED
        int specMode = MeasureSpec.getMode(spec);
    //测量大小
        int specSize = MeasureSpec.getSize(spec);
    //padding+子View的Margin(部分ViewGrop不会计算Margin,常用的都会计算)+已经使用的宽或者高
    //计算View还有多少可用的尺寸,不能小于0
        int size = Math.max(0, specSize - padding);
//最终的spec值由Size和Mode组成
        int resultSize = 0;
        int resultMode = 0;

        switch (specMode) {
        // Parent has imposed an exact size on us
        //父View传递下来的specMode是EXACTLY
        //ViewRoot传递下来的默认情况就是这个模式
        case MeasureSpec.EXACTLY:
        //子View的尺寸是固定值
            if (childDimension >= 0) {
            //size==子View的大小
                resultSize = childDimension;
                //Mode是EXACTY
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size. So be it.
                //子View的大小是Match_PARENT size等于View剩余的尺寸,模式是EXACTLY
                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.
                //子View的大小是WARP_CONTENT 模式是AT_MOST
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // Parent has imposed a maximum size on us
        //当父View要求子View最大不能超过specSize
        case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {
                // Child wants a specific size... so be it
                //子View设置了固定的尺寸,和ECACTLY模式下一样
                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.
                //子View是MACTCH_PARENT时,size是View的可用尺寸,mode是AT_MOST
                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.
                //子View的尺寸是WRAP_CONTENT时,size是View的可用尺寸,mode是AT_MOST
                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 = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size.... find out how
                // big it should be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        //noinspection ResourceType
        //打包生成子View的measureSpec
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

结论:不考虑UNSPECIFIED的情况下,Android系统自带的控件的测量宽高一般符合以下规则:

  1. 如果View设置了固定的尺寸,则不管父View的测量模式是什么,View的specMode均为ECACTLY
    ,最后的测量尺寸(mMeasureWidth ,mMeasureHeight)也一定的子View的设置的尺

  2. 如果View的尺寸是MATCH,则如果父View的specMode是ECACTLY
    则View的specMode也为EXACTLY,最后的测量尺寸(mMeasureWidth,mMesureHeight)一定是父View剩余的可用尺寸大小(可能是0);如果父View的specMode是AT_MOST View的specMode也是AT_MOST ,最后的测量尺寸(mMesureWidth,mMeasureHeight) 是View自身计算的最小内容显示大小和View剩余可用尺寸的最小值

  3. 如果View的尺寸的WRAP,则不管父View的specMode ,View 的specMode均为AT_MOST ,最后的测量尺寸(mMesureWidth,mMeasureHeight)是View自身计算的最小内容显示大小(默认最小大小)和View剩余可用尺寸的最小值

3.8

  • 继续下面就是测量DecorView的子类,这个根据我们设置的Activity的主题来确定的(no_title
    什么的),一般情况下是一个垂直线性布局(我们用的最多的),里面会用一个id为cotent的FrameLayout
    ,我们setContentView
    就是设置到这个View的View,测量就是再从DecorView的onMeasure方法,遍历调用调用子View的measure方法,调用ContentView的measure方法又会调用View的onMeasure方法,再开始遍历调用所有子View的measure方法,一层层的传递下去,最终调用setMeasureDimension方法设置View的测量宽高。

四.mesure流程总结

1.View的测量时从ViewRootImpl的performTraversals的开始,performTraversals方法判断是否需要测量,再调用performMeasure方法开始View的测量流程

2.ViewGroup的子类会重写View的onMeasure方法,对所有子View执行便利,计算子View的measureSpec,并调用子View的measure的方法开始子View的测量工程。View的measure方法(不能重写)又会调用onMeasure方法,如果是ViewGroup又会继续遍历下去,这样一层层的传递下去,直到便利到View树的底部,设置了View树上所有View的测量宽高为止。

3.自定义View需要重写onMeasure方法,自定义View的测量宽高的设置值(默认情况下MACTH和WRAP值的一样大的)。

4.自定义ViewGroup必须重写onMeasure 方法,自定义其所有子View的测量值,决定ViewGroup的布局特性。

5.getMeasureWidth和getMeasureHeight方法必须在View的onMeasure方法执行完毕后才能获得具体的值,所以在Activity的onCreate,onResume方法无法准确的获取View的测量宽高,前一篇文章讲过因为View的measure流程和Activity的生命周期不是完全同步的,所以我们要在Activity获得View的测量宽高可用通过View.post方法,在View绘制完成回调,或者是在ViewTreeObserver 设置onGlobalLayoutListener监听的方法。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值