理解 View 的绘制流程——measure 过程

从前文知道 View 的测量是从 ViewRootImpl 类的 performMeasure() 方法开始的,performMeasure 方法传入了两个 int 类型的参数,而且是通过 getRootMeasureSpec 方法获取的。

/**
 * Figures out the measure spec for the root view in a window based on it's
 * layout params.
 *
 * @param windowSize
 *            The available width or height of the window
 *
 * @param rootDimension
 *            The layout params for one dimension (width or height) of the
 *            window.
 *
 * @return The measure spec to use to measure the root view.
 */
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
    int measureSpec;
    switch (rootDimension) {
    // rootDimension 值为 ViewGroup.LayoutParams.MATCH_PARENT,即 -1 时
    case ViewGroup.LayoutParams.MATCH_PARENT:
        // Window 不能改变大小,强制根布局为 Window 的大小
        // Window can't resize. Force root view to be windowSize.
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
        break;
    // rootDimension 值为 ViewGroup.LayoutParams.MATCH_PARENT,即 -2 时
    case ViewGroup.LayoutParams.WRAP_CONTENT:
        // Window 可以改变大小,设置根布局最大为 Window 的大小
        // Window can resize. Set max size for root view.
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
        break;
    // rootDimension 为其他具体值时
    default:
        // Window 希望为具体的大小,强制根布局为其具体大小
        // Window wants to be an exact size. Force root view to be that size.
        measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
        break;
    }
    return measureSpec;
}

根据 Window 的宽高 windowSize 和 WindowManager.LayoutParams 的布局参数的宽高参数 rootDimension,构造了 int 类型的 measureSpec 结果。而且具体是通过调用 MeasureSpec.makeMeasureSpec 方法计算的。
这里,我们不得不先了解一下两个重要的东西:MeasureSpec 和 ViewGroup.LayoutParams。

关于 MeasureSpec

相关源码如下:

/**
 * A MeasureSpec encapsulates the layout requirements passed from parent to child.
 * Each MeasureSpec represents a requirement for either the width or the height.
 * A MeasureSpec is comprised of a size and a mode. There are three possible
 * modes:
 * 
*
UNSPECIFIED
*
* The parent has not imposed any constraint on the child. It can be whatever size * it wants. *
* *
EXACTLY
*
* The parent has determined an exact size for the child. The child is going to be * given those bounds regardless of how big it wants to be. *
* *
AT_MOST
*
* The child can be as large as it wants up to the specified size. *
*
* * MeasureSpecs are implemented as ints to reduce object allocation. This class * is provided to pack and unpack the <size, mode> tuple into the int. */ public static class MeasureSpec { private static final int MODE_SHIFT = 30; private static final int MODE_MASK = 0x3 << MODE_SHIFT; /** @hide */ @IntDef({UNSPECIFIED, EXACTLY, AT_MOST}) @Retention(RetentionPolicy.SOURCE) public @interface MeasureSpecMode {} /** * Measure specification mode: The parent has not imposed any constraint * on the child. It can be whatever size it wants. */ public static final int UNSPECIFIED = 0 << MODE_SHIFT; /** * Measure specification mode: The parent has determined an exact size * for the child. The child is going to be given those bounds regardless * of how big it wants to be. */ public static final int EXACTLY = 1 << MODE_SHIFT; /** * Measure specification mode: The child can be as large as it wants up * to the specified size. */ public static final int AT_MOST = 2 << MODE_SHIFT; /** * Creates a measure specification based on the supplied size and mode. * * The mode must always be one of the following: *
    *
  • {@link android.view.View.MeasureSpec#UNSPECIFIED}
  • *
  • {@link android.view.View.MeasureSpec#EXACTLY}
  • *
  • {@link android.view.View.MeasureSpec#AT_MOST}
  • *
* *

Note: On API level 17 and lower, makeMeasureSpec's * implementation was such that the order of arguments did not matter * and overflow in either value could impact the resulting MeasureSpec. * {@link android.widget.RelativeLayout} was affected by this bug. * Apps targeting API levels greater than 17 will get the fixed, more strict * behavior.

* * @param size the size of the measure specification * @param mode the mode of the measure specification * @return the measure specification based on size and mode */ public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size, @MeasureSpecMode int mode) { if (sUseBrokenMakeMeasureSpec) { return size + mode; } else { return (size & ~MODE_MASK) | (mode & MODE_MASK); } } ... /** * Extracts the mode from the supplied measure specification. * * @param measureSpec the measure specification to extract the mode from * @return {@link android.view.View.MeasureSpec#UNSPECIFIED}, * {@link android.view.View.MeasureSpec#AT_MOST} or * {@link android.view.View.MeasureSpec#EXACTLY} */ @MeasureSpecMode public static int getMode(int measureSpec) { //noinspection ResourceType return (measureSpec & MODE_MASK); } /** * Extracts the size from the supplied measure specification. * * @param measureSpec the measure specification to extract the size from * @return the size in pixels defined in the supplied measure specification */ public static int getSize(int measureSpec) { return (measureSpec & ~MODE_MASK); } ... }

阅读官方注释,就能了解到:
1、MeasureSpec 描述了从父 View 对子 View 的布局要求,更精确的说法应该是 MeasureSpec 是由父 View 的 MeasureSpec 和子 View 的 LayoutParams 通过简单的计算得出的一个针对子 View 的测量要求,这个测量要求就是 MeasureSpec。每一个 MeasureSpec 代表了宽度或者高度的要求,它由表示 size 的部分和表示 mode 的部分组成。而存在三种可能的 mode:
1)UNSPECIFIED:未指定尺寸模式。父 View 没有对子 View 有任何限制。
2)EXACTLY:精确值模式。父 View 规定了子 View 的精确尺寸,子 View 无论设置了多大的尺寸,都会被限制在父 View 规定的尺寸边界内。(也就是其宽高属性设置为具体的数值,如 50dp,或者设置为 match_parent,设置为 match_parent 时也就明确为和父布局有同样的尺寸。当明确为精确的尺寸后,其也就被给定了一个精确的边界)
3)AT_MOST:最大值模式。子 View 可以一直大到指定的值。(也就是其宽高属性设置为wrap_content,那么它的最大值也不会超过父布局给定的值,所以称为最大值模式)
2、MeasureSpec 被实现为 int 型来减少对象分配,它也实现了将 size 和 mode 装包和拆包到 int 中。MeasureSpec 的 int 长度为 32 位,高两位表示 mode,低 30 位用于表示 size。UNSPECIFIED 模式高两位为 00,EXACTLY 模式高两位为 01,AT_MOST 模式高两位为 10。
3、makeMeasureSpec(int mode, int size) 方法用于将 mode 和 size 打包成一个 int 型的 MeasureSpec
4、getSize(int measureSpec) 方法用于从指定的 measureSpec 值中获取其 size
5、getMode(int measureSpec) 方法用户从指定的 measureSpec 值中获取其 mode。

关于 ViewGroup.LayoutParams

相关源码如下:

    /**
     * LayoutParams are used by views to tell their parents how they want to be
     * laid out. See
     * {@link android.R.styleable#ViewGroup_Layout ViewGroup Layout Attributes}
     * for a list of all child view attributes that this class supports.
     *
     * 

* The base LayoutParams class just describes how big the view wants to be * for both width and height. For each dimension, it can specify one of: *

    *
  • FILL_PARENT (renamed MATCH_PARENT in API Level 8 and higher), which * means that the view wants to be as big as its parent (minus padding) *
  • WRAP_CONTENT, which means that the view wants to be just big enough * to enclose its content (plus padding) *
  • an exact number *
* There are subclasses of LayoutParams for different subclasses of * ViewGroup. For example, AbsoluteLayout has its own subclass of * LayoutParams which adds an X and Y value.

* *
*

Developer Guides

*

For more information about creating user interface layouts, read the * XML Layouts developer * guide.

* * @attr ref android.R.styleable#ViewGroup_Layout_layout_height * @attr ref android.R.styleable#ViewGroup_Layout_layout_width */ public static class LayoutParams { /** * Special value for the height or width requested by a View. * FILL_PARENT means that the view wants to be as big as its parent, * minus the parent's padding, if any. This value is deprecated * starting in API Level 8 and replaced by {@link #MATCH_PARENT}. */ @SuppressWarnings({"UnusedDeclaration"}) @Deprecated public static final int FILL_PARENT = -1; /** * Special value for the height or width requested by a View. * MATCH_PARENT means that the view wants to be as big as its parent, * minus the parent's padding, if any. Introduced in API Level 8. */ public static final int MATCH_PARENT = -1; /** * Special value for the height or width requested by a View. * WRAP_CONTENT means that the view wants to be just large enough to fit * its own internal content, taking its own padding into account. */ public static final int WRAP_CONTENT = -2; /** * Information about how wide the view wants to be. Can be one of the * constants FILL_PARENT (replaced by MATCH_PARENT * in API Level 8) or WRAP_CONTENT, or an exact size. */ @ViewDebug.ExportedProperty(category = "layout", mapping = { @ViewDebug.IntToString(from = MATCH_PARENT, to = "MATCH_PARENT"), @ViewDebug.IntToString(from = WRAP_CONTENT, to = "WRAP_CONTENT") }) @InspectableProperty(name = "layout_width", enumMapping = { @EnumEntry(name = "match_parent", value = MATCH_PARENT), @EnumEntry(name = "wrap_content", value = WRAP_CONTENT) }) public int width; /** * Information about how tall the view wants to be. Can be one of the * constants FILL_PARENT (replaced by MATCH_PARENT * in API Level 8) or WRAP_CONTENT, or an exact size. */ @ViewDebug.ExportedProperty(category = "layout", mapping = { @ViewDebug.IntToString(from = MATCH_PARENT, to = "MATCH_PARENT"), @ViewDebug.IntToString(from = WRAP_CONTENT, to = "WRAP_CONTENT") }) @InspectableProperty(name = "layout_height", enumMapping = { @EnumEntry(name = "match_parent", value = MATCH_PARENT), @EnumEntry(name = "wrap_content", value = WRAP_CONTENT) }) public int height; /** * Used to animate layouts. */ public LayoutAnimationController.AnimationParameters layoutAnimationParameters; ... }

LayoutParams 被 View 用于告诉它们的父 View 它们想要被如何布局。该 LayoutParams 基类仅仅描述了此 View 期望的宽高。对于每个宽或者高,可以指定以下值中的一种:
1、MATCH_PARENT:表示和父布局尺寸一样大,如果父布局有 padding,则要减去该 padding 值。
2、WRAP_CONTENT:表示它的大小为仅仅足够包裹住其内容即可,如果自己有 padding ,则要加上该 padding 值。
3、an exact number:精确大小。

View 测量的基本流程和重要方法分析

我们知道,View 体系的测量是从 DecorView 这个根 view 开始递归遍历的,而这个 View 体系树中包含了众多的叶子 View 和 ViewGroup 的子类容器。而整个测量过程是从 ViewRootImpl 类的 performMeasure() 方法开始。

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
    // mView 是在 ViewRootImpl 类的 setView 方法中被赋值的
    // 而这里 setView 的参数 view 和 attrs 是从 ActivityThread 类中 addView 方法传递过来的
    // 所以这里的 mView 指的是 DecorView
    if (mView == null) {
        return;
    }
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
    try {
        // 调用 DecorView 的 measure 方法
        mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
}

这里其实最后就是 DecorView 在执行 measure() 操作。由于 measure() 方法是 final 型的,View 的子类都不能重写该方法,所以直接进入到 View 类,调用了 View 的 measure 方法。

/**
 * 

* This is called to find out how big a view should be. The parent * supplies constraint information in the width and height parameters. *

* *

* The actual measurement work of a view is performed in * {@link #onMeasure(int, int)}, called by this method. Therefore, only * {@link #onMeasure(int, int)} can and must be overridden by subclasses. *

* * * @param widthMeasureSpec Horizontal space requirements as imposed by the * parent * @param heightMeasureSpec Vertical space requirements as imposed by the * parent * * @see #onMeasure(int, int) */ public final void measure(int widthMeasureSpec, int heightMeasureSpec) { ... // measure ourselves, this should set the measured dimension flag back onMeasure(widthMeasureSpec, heightMeasureSpec); ... }

根据注释可以得知,这个方法负责测量出出 View 的尺寸,父 View 在宽高参数中提供了限制信息。View 的源码中,这个方法的代码不少,但是实际的测量工作都是在方法中的 onMeasure 方法里实现的,所以只要 onMeasure 方法被子类重写,就可以让子类实现自己的测量工作。值得说明的是,ViewGroup 的子类必须重写该方法,才能绘制该容器内的子 View。而如果是 View 的子类,那么并不是必须重写该方法。

接下来可以暂时不用关注当前具体的 DecorView 类,先来了解一下父类 View 和 ViewGroup 中 onMeasure 的实现。
1、View 的 onMeasure 方法。

/**
 * 

* Measure the view and its content to determine the measured width and the * measured height. This method is invoked by {@link #measure(int, int)} and * should be overridden by subclasses to provide accurate and efficient * measurement of their contents. *

* *

* CONTRACT: When overriding this method, you * must call {@link #setMeasuredDimension(int, int)} to store the * measured width and height of this view. Failure to do so will trigger an * IllegalStateException, thrown by * {@link #measure(int, int)}. Calling the superclass' * {@link #onMeasure(int, int)} is a valid use. *

* *

* The base class implementation of measure defaults to the background size, * unless a larger size is allowed by the MeasureSpec. Subclasses should * override {@link #onMeasure(int, int)} to provide better measurements of * their content. *

* *

* If this method is overridden, it is the subclass's responsibility to make * sure the measured height and width are at least the view's minimum height * and width ({@link #getSuggestedMinimumHeight()} and * {@link #getSuggestedMinimumWidth()}). *

* * @param widthMeasureSpec horizontal space requirements as imposed by the parent. * The requirements are encoded with * {@link android.view.View.MeasureSpec}. * @param heightMeasureSpec vertical space requirements as imposed by the parent. * The requirements are encoded with * {@link android.view.View.MeasureSpec}. * * @see #getMeasuredWidth() * @see #getMeasuredHeight() * @see #setMeasuredDimension(int, int) * @see #getSuggestedMinimumHeight() * @see #getSuggestedMinimumWidth() * @see android.view.View.MeasureSpec#getMode(int) * @see android.view.View.MeasureSpec#getSize(int) */ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // 涉及到几个重要的方法: // 1、getSuggestedMinimumWidth 和 getSuggestedMinimumHeight // 2、getDefaultSize 方法 // 3、setMeasuredDimension 方法 setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }

1、该方法用来测量该 View 以及它的内容来得到宽高的测量值。该方法应该被子类重写来提供准确而且有效的测量值。
2、当重写该类方法时,必须调用 setMeasuredDimension(int, int) 方法来存储该 view 测量出的宽和高,如果没有这么做的 measure(int, int) 方法话会抛出 IllegalStateException 异常。
3、测量方法的基类实现默认为背景的 size,除非 MeasureSpec 允许了更大的尺寸。子类应该重写 onMeasure(int, int) 方法来提供对内容更好的测量值。
4、如果该方法被重写,子类负责确保宽高的测量值至少是该 view 的宽高 minimum 值。
5、方法中的两个参数,widthMeasureSpec 是父布局加入的水平空间要求,heightMeasureSpec 是父布局加入的垂直空间要求。
6、如果容器类控件都是 ViewGroup 的子类,如 FrameLayout、LinearLayout 等,都会重写 onMeasure 方法,根据自己的特性来进行测量;如果是叶子节点 View,即最里层控件,如 TextView 等,也可能会重写 onMeasure 方法,所以当流程走到 onMeasure 方法时,流程可能就会切到那些重写的 onMeasure 方法中去。最后通过从根 View 到叶子节点的遍历和递归,最终还是会在叶子 View 中通过调用 setMeasuredDimension 来实现最终的测量。

onMeasure 中调用的 getSuggestedMinimumWidth 和 getSuggestedMinimumHeight 方法源码如下。

/**
 * Returns the suggested minimum width that the view should use. This
 * returns the maximum of the view's minimum width
 * and the background's minimum width
 *  ({@link android.graphics.drawable.Drawable#getMinimumWidth()}).
 * 

* When being used in {@link #onMeasure(int, int)}, the caller should still * ensure the returned width is within the requirements of the parent. * * @return The suggested minimum width of the view. */ protected int getSuggestedMinimumWidth() { return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth()); } /** * Returns the suggested minimum height that the view should use. This * returns the maximum of the view's minimum height * and the background's minimum height * ({@link android.graphics.drawable.Drawable#getMinimumHeight()}). *

* When being used in {@link #onMeasure(int, int)}, the caller should still * ensure the returned height is within the requirements of the parent. * * @return The suggested minimum height of the view. */ protected int getSuggestedMinimumHeight() { return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight()); }

这两个方法返回该 View 建议使用的最小宽度和高度值,返回了 View 和其背景的宽度或高度中最大的一个值。当在 onMeasure(int, int) 方法中被使用到时,调用者应该仍然确保返回的宽度值在父布局的要求之内。这其中提到的最小的宽度或高度指的是在 xml 布局文件中该 view 的“android:minWidth”或“android:minHeight”属性值,背景的最小宽度或高度值是指“android:background”的宽度或高度。该方法的返回值就是两者之间较大的那一个值,用来作为该 view 的最小宽度值,很容易理解了,当一个 view 在 xml 文件中同时设置了这两个属性时,为了两个条件都满足,自然要选择值大一点的那个了。

再来看看 onMeasure 方法中调用的 getDefaultSize 方法。

/**
 * Utility to return a default size. Uses the supplied size if the
 * MeasureSpec imposed no constraints. Will get larger if allowed
 * by the MeasureSpec.
 *
 * @param size Default size for this view
 * @param measureSpec Constraints imposed by the parent
 * @return The size this view should be.
 */
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:
        // 如果父布局没有施加任何限制
        // 那么返回值为参数中提供的 size 值,即建议使用的最小宽度和高度值
        result = size;
        break;
    case MeasureSpec.AT_MOST:
    case MeasureSpec.EXACTLY:
        // 如果父布局施加了限制,不论是 AT_MOST 还是 EXACTLY
        // 则返回值为保存在参数 measureSpec 中的 specSize 值
        result = specSize;
        break;
    }
    return result;
}

所以到目前为止,需要绘制的宽和高值就被确定下来了。

下面是 View 的 setMeasuredDimension 方法。

/**
 * 

This method must be called by {@link #onMeasure(int, int)} to store the * measured width and measured height. Failing to do so will trigger an * exception at measurement time.

* * @param measuredWidth The measured width of this view. May be a complex * bit mask as defined by {@link #MEASURED_SIZE_MASK} and * {@link #MEASURED_STATE_TOO_SMALL}. * @param measuredHeight The measured height of this view. May be a complex * bit mask as defined by {@link #MEASURED_SIZE_MASK} and * {@link #MEASURED_STATE_TOO_SMALL}. */ 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(measuredWidth, measuredHeight); }

最后调用了 setMeasuredDimensionRaw 方法,并传递了该 view 被测量出的宽度和高度值 measuredWidth 和 measuredHeight 作为参数。在这一步,“父布局加入的水平空间和垂直空间的要求”转变为了“该 view 的宽度和高度”。

下面是调用的 setMeasuredDimensionRaw 方法。

    /**
     * Sets the measured dimension without extra processing for things like optical bounds.
     * Useful for reapplying consistent values that have already been cooked with adjustments
     * for optical bounds, etc. such as those from the measurement cache.
     *
     * @param measuredWidth The measured width of this view.  May be a complex
     * bit mask as defined by {@link #MEASURED_SIZE_MASK} and
     * {@link #MEASURED_STATE_TOO_SMALL}.
     * @param measuredHeight The measured height of this view.  May be a complex
     * bit mask as defined by {@link #MEASURED_SIZE_MASK} and
     * {@link #MEASURED_STATE_TOO_SMALL}.
     */
    private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
        mMeasuredWidth = measuredWidth;
        mMeasuredHeight = measuredHeight;

        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
    }

setMeasuredDimensionRaw 方法中,View 中的成员变量 mMeasureWidth 和 mMeasureHeight 就被赋值了,这也就意味着,View 的测量结束了。前面讲 onMeasure() 方法时介绍过,View 子类(包括ViewGroup子类)通常会重写onMeasure(),当阅读 FrameLayout、LinearLayout、TextView 等重写的 onMeasure() 方法时,会发现它们最终都会调用 setMeasuredDimension() 方法,从而完成测量。

值得注意的是 getMeasuredWidth 和 getMeasuredHeight 方法。

/**
 * Like {@link #getMeasuredWidthAndState()}, but only returns the
 * raw width component (that is the result is masked by
 * {@link #MEASURED_SIZE_MASK}).
 *
 * @return The raw measured width of this view.
 */
public final int getMeasuredWidth() {
    return mMeasuredWidth & MEASURED_SIZE_MASK;
}

/**
 * Like {@link #getMeasuredHeightAndState()}, but only returns the
 * raw height component (that is the result is masked by
 * {@link #MEASURED_SIZE_MASK}).
 *
 * @return The raw measured height of this view.
 */
public final int getMeasuredHeight() {
    return mMeasuredHeight & MEASURED_SIZE_MASK;
}

获取前面被赋值的 mMeasureWidth 和 mMeasureHeight,即原始的测量宽度值,一般会拿这个方法和 layout 执行后 getWidth() 和 getHeight() 方法做比较。从这里可以知道,getMeasuredWidth() 和 getMeasuredHeight() 方法需要在 setMeasuredDimension() 方法执行后才有效,否则返回值为0。

2、ViewGroup 的 onMeasure 方法
ViewGroup 并没有重写 onMeasure 方法,所以需要查看具体的测量过程是否在 ViewGroup 的各种子类中。
现在回来具体查看 DecorView 的 onMeasure 方法。

// DecorView.java
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
    // 获取到当前是否为竖屏状态
    final boolean isPortrait =
            getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT;

    final int widthMode = getMode(widthMeasureSpec);
    final int heightMode = getMode(heightMeasureSpec);

    boolean fixedWidth = false;
    mApplyFloatingHorizontalInsets = false;
    if (widthMode == AT_MOST) {
        ...
    }

    mApplyFloatingVerticalInsets = false;
    if (heightMode == AT_MOST) {
        ...
    }
    ...
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    int width = getMeasuredWidth();
    boolean measure = false;
    ...
    // TODO: Support height?

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

方法中对横竖屏状态下的 widthMeasureSpec 和 heightMeasureSpec 进行了预处理,然后调用了父类的 onMeasure 方法完成实际的测量,我们知道 DecorView 的父类为 FrameLayout,下面就进入到 FrameLayout 的 onMeasure 方法中。

下面是 FrameLayout 的 onMeasure 方法。

// FrameLayout.java
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int count = getChildCount();

    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++) {
        // 遍历子 View
        final View child = getChildAt(i);
        // 当 mMeasureAllChildren 设置为 true,或者子 View 未被设置为 GONE 时
        // 执行 if 代码块内的逻辑
        // mMeasureAllChildren 标志测量是是否将所有子 View 进行测量,包括设置为 GONE 的 View
        if (mMeasureAllChildren || child.getVisibility() != GONE) {
            // 调用了 measureChildWithMargins 方法来进行子 View 的测量
            measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            // 子 View 测量完成后,获取子 View 宽和高的测量结果,加上 margin 值
            // 这里可以看出,maxWidth 和 maxHeight 是所有子 View 中最大的计算值
            maxWidth = Math.max(maxWidth,
                    child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
            maxHeight = Math.max(maxHeight,
                    child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
            ... 
        }
    }
    ...
    // 调用 setMeasuredDimension 方法设置测量值
    setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
            resolveSizeAndState(maxHeight, heightMeasureSpec,
                    childState << MEASURED_HEIGHT_STATE_SHIFT));
    ...
    }
}

FrameLayout 中的 onMeasure 方法将所有的子 View 进行测量。然后经过一系列包括 padding 值、宽高最小值的计算之后通过 setMeasuredDimension 设置自己宽高的测量值。对于 FrameLayout 可能用最大的子 View 中最大的宽和高的值,对于 LinearLayout,可能是高度的累加。总的来说,父 View 是等所有的子 View 测量结束之后,再来测量自己。
其中,对所有子 View 进行测量是通过 measureChildWithMargins 方法完成的,下面介绍一下 measureChildWithMargins 和其他几种测量相关的方法:

(1)measureChild() 方法和 measureChildWithMargins() 方法

// ViewGroup.java
/**
 * Ask one of the children of this view to measure itself, taking into
 * account both the MeasureSpec requirements for this view and its padding.
 * The heavy lifting is done in getChildMeasureSpec.
 *
 * @param child The child to measure
 * @param parentWidthMeasureSpec The width requirements for this view
 * @param parentHeightMeasureSpec The height requirements for this view
 */
protected void measureChild(View child, int parentWidthMeasureSpec,
        int parentHeightMeasureSpec) {
    final LayoutParams lp = child.getLayoutParams();

    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
            mPaddingLeft + mPaddingRight, lp.width);
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
            mPaddingTop + mPaddingBottom, lp.height);

    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

/**
 * Ask one of the children of this view to measure itself, taking into
 * account both the MeasureSpec requirements for this view and its padding
 * and margins. The child must have MarginLayoutParams The heavy lifting is
 * done in getChildMeasureSpec.
 *
 * @param child The child to measure
 * @param parentWidthMeasureSpec The width requirements for this view
 * @param widthUsed Extra space that has been used up by the parent
 *        horizontally (possibly by other children of the parent)
 * @param parentHeightMeasureSpec The height requirements for this view
 * @param heightUsed Extra space that has been used up by the parent
 *        vertically (possibly by other children of the parent)
 */
protected void measureChildWithMargins(View child,
        int parentWidthMeasureSpec, int widthUsed,
        int parentHeightMeasureSpec, int heightUsed) {
    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

    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);
}

对比这两个方法可以发现,它们非常相似,从注释上来看,后者在前者的基础上增加了已经使用的宽高和 margin 值。其实它们的功能都是一样的,最后都是生成子 View 的 MeasureSpec,并传递给子 View 继续测量,即最后一句代码 child.measure(childWidthMeasureSpec, childHeightMeasureSpec)。一般根据容器自身的需要来选择其中一个,比如,在 FrameLayout 和 LinearLayout 中重写的 onMeasure 方法中调用的就是后者,而 AbsoluteLayout 中就是间接地调用的前者。而 RelativeLayout 中,两者都没有调用,而是自己写了一套方法,不过该方法和后者方法仅略有差别,但基本功能还是一样的。

(2)getChildMeasureSpec() 方法

// ViewGroup.java
/**
 * 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) {
    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) {
    // Parent has imposed an exact size on us
    case MeasureSpec.EXACTLY:
        if (childDimension >= 0) {
            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 = 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
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}

前两个方法中都用到了这个方法,它很重要,它用于将父布局传递来的 MeasureSpec 和其子 view 的 LayoutParams,整合为一个最有可能的子 View 的 MeasureSpec。
处理 measureChildren 的困难部分:计算出 MeasureSpec 并传递给指定的 child。该方法计算出子 view 正确的宽高 MeasureSpec。
其目的是根据父布局的 MeasureSpec 的信息和 child 的 LayoutParams 来得到 child 的 measureSpec 结果。
a. 假如父布局的 specMode 定义为 MeasureSpec.EXACTLY,即父布局对子 view 的尺寸要求是一个精确值,比如父布局中 layout_width 属性值被设置为具体值或者 match_parent 时:
如果 childDimension 也为具体值,此时 resultSize 为 childDimension 的具体值,resultMode 理所当然为 MeasureSpec.EXACTLY。(此时不考虑子 view 的 size 比父布局大的情况,因为当前步骤中的测量值和最后布局后绘制出来的结果是有差别的)
如果 childDimension 值为 LayoutParams.MATCH_PARENT 时,它的尺寸和父布局一样,也是个精确值,所以 resultSize 为前面通过父布局的 specSize 和 padding 值求出的 size 值,由父布局决定,resultMode 为 MeasureSpec.EXACTLY。
如果 childDimension 值为 LayoutParams.WRAP_CONTENT 时,此时 resultSize 为前面求出的 size 值,resultMode 为 MeasureSpec.AT_MOST。
b. 假如父布局的 specMode 定义为 MeasureSpec.AT_MOST,即要求子 view 不超过父布局指定的大小即可。
如果 childDimension 为具体值,此时 resultSize 为 childDimension 的具体值,resultMode 为 MeasureSpec.EXACTLY。
如果 childDimension 值为 LayoutParams.MATCH_PARENT 时,它的尺寸由父布局决定,resultSize 为前面通过父布局的 specSize 和 padding 值求出的 size 值,resultMode 为 MeasureSpec.AT_MOST。
如果 childDimension 值为 LayoutParams.WRAP_CONTENT 时,它的尺寸由父布局决定,resultSize 为前面通过父布局的 specSize 和 padding 值求出的 size 值,resultMode 为 MeasureSpec.AT_MOST。
c. 假如父布局的 specMode 定义为 MeasureSpec.UNSPECIFIED,平时很少用,一般用在系统中,暂不细究。

后续的流程就是进入子 View 的 measure 方法,重复上面的过程,区分直接继承自 View 和继承自 ViewGroup 的容器类两种测量过程。整个测量过程也就如此了。

performMeasure() 方法中 childWidthMeasureSpec 和 childHeightMeasureSpec() 参数来源分析

根据 View 体系的不断往下遍历和递归中,前面流程中传入 getDefaultSize() 方法中的值是根据上一次的值变动的,所以去找最初参数值,即 DecorView 的 MeasureSpec。
根据代码往回看,childWidthMeasureSpec 来自于 getRootMeasureSpec 方法。

// ViewRootImpl.java
/**
 * Figures out the measure spec for the root view in a window based on it's
 * layout params.
 *
 * @param windowSize
 *            The available width or height of the window
 *
 * @param rootDimension
 *            The layout params for one dimension (width or height) of the
 *            window.
 *
 * @return The measure spec to use to measure the root view.
 */
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;
}

该方法基于 window 的 layout 参数,在 window 中为 root view 找出 measure spec,也就是找出 DecorView 的 MeasureSpec,这里的 Window 也就是 PhoneWindow。
其中,参数 windowSize 为 window 的可用宽高值。参数 rootDimension 为 window 的宽高 layout 参数。返回值为用于测量 root view 的 MeasureSpec。
方法中,根据 rootDimension 值,当其为 ViewGroup.LayoutParams.MATCH_PARENT 时,构建的 measureSpec size 为 windowSize,mode 为 MeasureSpec.EXACTLY;当其为 ViewGroup.LayoutParams.WRAP_CONTENT 时,size 为 windowSize,mode 为 MeasureSpec.AT_MOST;默认为 size 为 rootDimension,mode 为 MeasureSpec.EXACTLY。

再看看 windowSize 和 rootDimension 的来源,比如看看和宽度相关的 mWidth 和 lp.width。

// ViewRootImpl.java
......
final Rect mWinFrame; // frame given by window manager.
......
private void performTraversals() {
    ......
    Rect frame = mWinFrame;
    ......
    // !!FIXME!! This next section handles the case where we did not get the
    // window size we asked for. We should avoid this by getting a maximum size from
    // the window session beforehand.
    if (mWidth != frame.width() || mHeight != frame.height()) {
        mWidth = frame.width();
        mHeight = frame.height();
    }
}

mWidth 为 frame 矩形的宽,而 mWinFrame 由 WindowManager 提供。在窗口布局阶段,WindowManagerService 服务计算 Activity 窗口的大小,并将 Activity 窗口的大小保存在成员变量 mWinFrame 中。

// ViewRootImpl.java
......
final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams();
......
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    ......
    mWindowAttributes.copyFrom(attrs);
    ......
}
private void performTraversals() {
     ......
     WindowManager.LayoutParams lp = mWindowAttributes;
     ......
}

lp 为 mWindowAttributes,而 mWindowAttributes 的值通过 copyFrom 方法取自于 setView 方法传入的 attrs 参数,而这个参数就是从 ActivityThread 类中传过来的,attrs 是 PhoneWindow 的 LayoutParams 值,其 width 和 height 属性值均为LayoutParams.MATCH_PARENT,所以此时:
childWidthMeasureSpec 的 mode 为MeasureSpec.EXACTLY,size 为屏幕宽。
childHeightMeasureSpec 的 mode 为MeasureSpec.EXACTLY,size 为屏幕高。
此时,我们就得到了 DecorView 的 MeasureSpec 了,后面的递归操作就是在此基础上不断将测量要求从父布局传递到子 view。

当 DecorView 第一次调用到 measure() 方法后,代码逻辑就走到重写方法 onMeasure() 中了,然后调用 DecorView 的父类 FrameLayout 的 onMeasure 方法中,先把子 view 测量完成后,最后才去调用 setMeasuredDimension 来测量自己的。事实上,整个测量过程就是从子 view 开始测量,然后一层层往上再测量父布局,直到 DecorView 为止的。

下面是描述整个测量过程的简单流程图:

View 的 measure 过程的流程图.png

值得提醒的是,在理解 View 的相关源码流程的过程中,一定要注意不要混淆“父类”和“父 View”的概念。
文章推荐:https://www.jianshu.com/p/5a71014e7b1b

你可能感兴趣的:(理解 View 的绘制流程——measure 过程)