Android进阶面试Android自定义View

Android高级进阶——View的工作原理(一)Measure

2018-04-11  本文已影响33人  aKaiC

开篇

本篇主要讲解的是 View 工作原理之 measure 过程,主要是以源码的形式进行分析,源码来源 API 26。

从 ViewRootImpl 的 #performTraversals 方法开始说起

performTraversals 方法是整个工作流程的核心,它里面分别取执行了 measure、layout 以及 draw 三个过程,看一下代码:

private void performTraversals() {
         ...
        if (!mStopped || mReportNextDraw) {
            boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
                        (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
            if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
                        || mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
                        updatedConfiguration) {
                int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
                int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

                // Ask host how big it wants to be
                performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                ...

        if (didLayout) {
            performLayout(lp, mWidth, mHeight);
            ...

        if (!cancelDraw && !newSurface) {
            if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
                for (int i = 0; i < mPendingTransitions.size(); ++i) {
                    mPendingTransitions.get(i).startChangingAnimations();
                }
                mPendingTransitions.clear();
            }

            performDraw();
            ...

performTraversals 这个方法的代码相当多,它里面分别去执行了 performMeasure、performLayout 以及 performDraw 三个方法,这三个方法内部分别调用了 View 的 measure、layout 以及 draw 对应的三个方法。

而接下来我们要说的 measure 过程是从 #performMeasure(childWidthMeasureSpec, childHeightMeasureSpec)方法开始的,performMeasure() 方法传递了两个参数到 View 的 measure 方法中,而 childWidthMeasureSpec 和 childHeightMeasureSpec 到底是什么呢? 还是从源码中找找吧:

int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

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;

其实就是调用 MeasureSpec.makeMeasureSpec 生成的一个测量规则,而 MeasureSpec 是干嘛用的,接下来介绍:

理解 MeasureSpec

MeasureSpec 是 View 的一个内部类,官方文档是这么介绍 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. 大致意思是说,该类封装了从父 View 传递到子 View 的尺寸规格,包括了父 View 的宽和高的信息。说白了就是只要有了当前 View 的 MeasureSpec,也就等于确定了 View 的大小。

MeasureSpec 的作用在于:在 measure 过程中,子 View 的 MeasureSpec 是由 父 View 的 MeasureSpec 以及 子 View 的 LayoutParams 共同决定的(后面会给出证实)

public static class MeasureSpec {
        private static final int MODE_SHIFT = 30;
        private static final int MODE_MASK  = 0x3 << MODE_SHIFT;
        /**
         * UNSPECIFIED 模式:
         * 父节点没有对子节点施加任何约束。它可以是任意大小
         */
        public static final int UNSPECIFIED = 0 << MODE_SHIFT;

        /**
         * EXACTLY 模式
         * 父 View 已经确定了子 View 的确切尺寸。也就是父 View 的剩余大小,该模式对应于 match_parent 和 具体数值这两种模式
         */
        public static final int EXACTLY     = 1 << MODE_SHIFT;

        /**
         * AT_MOST 模式:
         * 最大模式,子 View 的最终大小是父 View 的剩余大小,最大不能超过父 View 剩余空间大小,该模式对应于 wrap_content
         */
        public static final int AT_MOST     = 2 << MODE_SHIFT

        /**
         *讲 mode 和 size 打包成一个 32 位的 int 型数值,高 2 位 表示测量模式,低 30 位表示 specSize
         */
        public static int makeSafeMeasureSpec(int size, int mode) {
            if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) {
                return 0;
            }
            return makeMeasureSpec(size, mode);
        }

        /**
         *  将 32 位的 MeasureSpec 解包,返回当前 MeasureSpec 的测量模式
         */
        @MeasureSpecMode
        public static int getMode(int measureSpec) {
            //noinspection ResourceType
            return (measureSpec & MODE_MASK);
        }

        /**
         * 讲 32 位 MeasureSpec 解包,获取当前 MeasureSpec 的 测量大小
         */
        public static int getSize(int measureSpec) {
            return (measureSpec & ~MODE_MASK);
        }
    }

说完了 MeasureSpec,再回到 performTraversals 的 performMeasure 方法接收了 childWidthMeasureSpec 和 childHeightMeasureSpec 两个 MeasureSpec,而 这两个 MeasureSpec 其实就是根据屏幕的宽度以及高度生成的 MeasureSpec,既然都有了 MeasureSpec,那就开始测量呗!

performMeasure 方法

    private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        if (mView == null) {
            return;
        }
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
        try {
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

方法很简单,就是直接调用了 mView 的 measure(int widthMeasureSpec, int heightMeasureSpec) 方法, 而 mView 其实就是我们的顶级的 DecorView,也就是 ViewGroup,而 ViewGroup 又继承自 View,所以其实调用的还是 View 的 measure 方法,measure 方法内部:

View 的 measure 过程

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {

    if (cacheIndex < 0 || sIgnoreMeasureCache) {
        // measure ourselves, this should set the measured dimension flag back
        onMeasure(widthMeasureSpec, heightMeasureSpec);
        ...
     }
}

如此简单,我们直接看 onMeasure 方法就可以了

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.
大致意思是:该方法主要是用来确定 View 测量宽和测量高的,该方法应该被子类进行覆盖,以提供更加准确的测量值,并且当重写此方法时,必须调用 #setMeasuredDimension(int, int) 来存储该视图的测量宽度和高度,而且如果不这样做将会抛出 IllegalStateException 异常,建议我们调用父类的 #onMeasure(int, int) 方法

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

上面已经说过了 setMeasureDimension 方法是用来存储测量宽以及高的,来看一下吧:

protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
        mMeasuredWidth = measuredWidth;
        mMeasuredHeight = measuredHeight;
        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
    }

ViewGroup 的 measure 过程

由于 ViewGroup 是 View 的子类,所以当 performMeasure 方法调用 View 的 onMeasure 方法时,首先会先去执行 DecorView(其实也就是 ViewGroup)的 onMeasure 方法,但是 ViewGroup 本身并没有实现 onMeasure、onLayout、onDraw这三个方法,因为不同的 ViewGroup 有不同的实现,比如 LinearLayout 和 RelativeLayout 测量过程和布局过程肯定是不一样的,所以 ViewGroup 的 onMeasure 其实是在其子类中实现的

我们来看一下 LinearLayout 的 onMeasure 的方法

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mOrientation == VERTICAL) {
            measureVertical(widthMeasureSpec, heightMeasureSpec);
        } else {
            measureHorizontal(widthMeasureSpec, heightMeasureSpec);
        }
    }

这里我们就只看 measureVertical 方法了 measureHorizontal 是一样的思路,来看下 LinearLayout 的 onMeasure 方法

LinearLayout#onMeasure
该方法代码并不算很长,就300行左右,但是如果仔细看的话会发现逻辑其实是很清晰的,一步一步来分析吧

void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
        //用来记录已经使用的高度
        mTotalLength = 0;
        int maxWidth = 0;
        int childState = 0;
        int alternativeMaxWidth = 0;
        int weightedMaxWidth = 0;
        boolean allFillParent = true;
        //权重值得总和
        float totalWeight = 0;
        //获取子 View 的个数
        final int count = getVirtualChildCount();
        //LinearLayout 的 宽度模式以及高度模式
        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        boolean matchWidth = false;
        boolean skippedMeasure = false;

        final int baselineChildIndex = mBaselineAlignedChildIndex;
        final boolean useLargestChild = mUseLargestChild;

        int largestChildHeight = Integer.MIN_VALUE;
        int consumedExcessSpace = 0;

        int nonSkippedChildCount = 0;

        // See how tall everyone is. Also remember max width.
        for (int i = 0; i < count; ++i) {
            //取出子 View
            final View child = getVirtualChildAt(i);
            //如果子View为null,就跳过该View,测量下一个View
            if (child == null) {
                mTotalLength += measureNullChild(i);
                continue;
            }
            //如果view是隐藏的,就跳过该 View,测量下一个View
            if (child.getVisibility() == View.GONE) {
               i += getChildrenSkipCount(child, i);
               continue;
            }

            nonSkippedChildCount++;
            //计算每个 view 的分割线的高度
            if (hasDividerBeforeChildAt(i)) {
                mTotalLength += mDividerHeight;
            }
            //获取当前子 View 的layoutParams,通过 LayoutParams来获取view的宽度、高度、weight、margin
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            //记录总权重
            totalWeight += lp.weight;
            
            final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;
            //根据当前heightMode是EXACTLY,lp.height==0同时lp.weight>0在二次测量时会对符合条件的view进行二次测量
            if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {
                // Optimization: don't bother measuring children who are only
                // laid out using excess space. These views will get measured
                // later if we have space to distribute.
                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
                //这个标记在二次测量时会用到
                skippedMeasure = true;
            } else {
                //如果这个判断成立,那么就说明 LinearLayout 的 heightMode一定是 UNSPECIFIED 或者 MOST,然后给 view 的height重新赋值,这样在后面测量时就可以得到view的最佳高度
                if (useExcessSpace) {
                    // The heightMode is either UNSPECIFIED or AT_MOST, and
                    // this child is only laid out using excess space. Measure
                    // using WRAP_CONTENT so that we can find out the view's
                    // optimal height. We'll restore the original height of 0
                    // after measurement.
                    lp.height = LayoutParams.WRAP_CONTENT;
                }

                // Determine how big this child would like to be. If this or
                // previous children have given a weight, then we allow it to
                // use all available space (and we will shrink things later
                // if needed).
                //已使用的高度(当前view的toppadding+bottompadding+当前view的高度+分割线的高度 =LinearLayout的宽度
                final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
                //内部调用ViewGroup的measureChildWithMargins方法测量当前view的大小,后面会单独介绍
                measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
                        heightMeasureSpec, usedHeight);
                //获取当前view 的高度
                final int childHeight = child.getMeasuredHeight();
                if (useExcessSpace) {
                    // Restore the original height and record how much space
                    // we've allocated to excess-only children so that we can
                    // match the behavior of EXACTLY measurement.
                    lp.height = 0;
                    consumedExcessSpace += childHeight;
                }

                final int totalLength = mTotalLength;
                //累计已经测量过的view的高度,从而获取剩余高度
                mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
                       lp.bottomMargin + getNextLocationOffset(child));

                if (useLargestChild) {
                    largestChildHeight = Math.max(childHeight, largestChildHeight);
                }
            }

            /**
             * If applicable, compute the additional offset to the child's baseline
             * we'll need later when asked {@link #getBaseline}.
             */
            if ((baselineChildIndex >= 0) && (baselineChildIndex == i + 1)) {
               mBaselineChildTop = mTotalLength;
            }

            // if we are trying to use a child index for our baseline, the above
            // book keeping only works if there are no children above it with
            // weight.  fail fast to aid the developer.
            if (i < baselineChildIndex && lp.weight > 0) {
                throw new RuntimeException("A child of LinearLayout with index "
                        + "less than mBaselineAlignedChildIndex has weight > 0, which "
                        + "won't work.  Either remove the weight, or don't set "
                        + "mBaselineAlignedChildIndex.");
            }

            boolean matchWidthLocally = false;
            if (widthMode != MeasureSpec.EXACTLY && lp.width == LayoutParams.MATCH_PARENT) {
                // The width of the linear layout will scale, and at least one
                // child said it wanted to match our width. Set a flag
                // indicating that we need to remeasure at least that view when
                // we know our width.
                matchWidth = true;
                matchWidthLocally = true;
            }

            final int margin = lp.leftMargin + lp.rightMargin;
            final int measuredWidth = child.getMeasuredWidth() + margin;
            //根据子view的宽度计算当前view(LinearLayout的宽度)的宽度=子view的宽度+leftmargin+rightMargin,后面还会加上leftPadding以及rightPadding
            maxWidth = Math.max(maxWidth, measuredWidth);
            childState = combineMeasuredStates(childState, child.getMeasuredState());

            allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;
            if (lp.weight > 0) {
                /*
                 * Widths of weighted Views are bogus if we end up
                 * remeasuring, so keep them separate.
                 */
                weightedMaxWidth = Math.max(weightedMaxWidth,
                        matchWidthLocally ? margin : measuredWidth);
            } else {
                alternativeMaxWidth = Math.max(alternativeMaxWidth,
                        matchWidthLocally ? margin : measuredWidth);
            }

            i += getChildrenSkipCount(child, i);
        }

        if (nonSkippedChildCount > 0 && hasDividerBeforeChildAt(count)) {
            mTotalLength += mDividerHeight;
        }

        if (useLargestChild &&
                (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED)) {
            mTotalLength = 0;

            for (int i = 0; i < count; ++i) {
                final View child = getVirtualChildAt(i);
                if (child == null) {
                    mTotalLength += measureNullChild(i);
                    continue;
                }

                if (child.getVisibility() == GONE) {
                    i += getChildrenSkipCount(child, i);
                    continue;
                }

                final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
                        child.getLayoutParams();
                // Account for negative margins
                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + largestChildHeight +
                        lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
            }
        }

        // Add in our padding
        //当前View的最终高度
        mTotalLength += mPaddingTop + mPaddingBottom;

        int heightSize = mTotalLength;

        // Check against our minimum height
        //当前View的最终高度
        heightSize = Math.max(heightSize, getSuggestedMinimumHeight());

        // Reconcile our calculated size with the heightMeasureSpec
       //heightSizeAndState会被传入setMeasuredDimension方法中用于保存,也就是作为当前view(LinearLayout的测量高度)
        int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
        heightSize = heightSizeAndState & MEASURED_SIZE_MASK;
        // Either expand children with weight to take up available space or
        // shrink them if they extend beyond our current bounds. If we skipped
        // measurement on any children, we need to measure them now.
        //当前View的剩余高度
        int remainingExcess = heightSize - mTotalLength
                + (mAllowInconsistentMeasurement ? 0 : consumedExcessSpace);
        //如果有子view使用了weight属性,必进入该判断
        if (skippedMeasure || remainingExcess != 0 && totalWeight > 0.0f) {
            //weight总和
            float remainingWeightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;

            mTotalLength = 0;
            //遍历子View
            for (int i = 0; i < count; ++i) {
                final View child = getVirtualChildAt(i);
                //如果子view为null,继续循环
                if (child == null || child.getVisibility() == View.GONE) {
                    continue;
                }
                //获取子view的LayoutParams
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                //获取当前子view的weight值
                final float childWeight = lp.weight;
                //只测量子view的weight大于0的view
                if (childWeight > 0) {
                    //weight*剩余高度/总weightSum,得到当前子view应占剩余高度的比例值
                    final int share = (int) (childWeight * remainingExcess / remainingWeightSum);
                    //减去子view使用后的高度
                    remainingExcess -= share;
                    //减去子view使用的weight
                    remainingWeightSum -= childWeight;

                    final int childHeight;
                    if (mUseLargestChild && heightMode != MeasureSpec.EXACTLY) {
                        childHeight = largestChildHeight;
                    } else if (lp.height == 0 && (!mAllowInconsistentMeasurement
                            || heightMode == MeasureSpec.EXACTLY)) {
                        // This child needs to be laid out from scratch using
                        // only its share of excess space.
                        childHeight = share;
                    } else {
                        // This child had some intrinsic height to which we
                        // need to add its share of excess space.
                        childHeight = child.getMeasuredHeight() + share;
                    }

                    final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
                            Math.max(0, childHeight), MeasureSpec.EXACTLY);
                    final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                            mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin,
                            lp.width);
                    //这里测量的仅仅是指定了weight值的子view
                    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

                    // Child may now not fit in vertical dimension.
                    childState = combineMeasuredStates(childState, child.getMeasuredState()
                            & (MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT));
                }

                final int margin =  lp.leftMargin + lp.rightMargin;
                final int measuredWidth = child.getMeasuredWidth() + margin;
                maxWidth = Math.max(maxWidth, measuredWidth);

                boolean matchWidthLocally = widthMode != MeasureSpec.EXACTLY &&
                        lp.width == LayoutParams.MATCH_PARENT;

                alternativeMaxWidth = Math.max(alternativeMaxWidth,
                        matchWidthLocally ? margin : measuredWidth);

                allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;

                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredHeight() +
                        lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
            }

            // Add in our padding
            mTotalLength += mPaddingTop + mPaddingBottom;
            // TODO: Should we recompute the heightSpec based on the new total length?
        } else {
            alternativeMaxWidth = Math.max(alternativeMaxWidth,
                                           weightedMaxWidth);


            // We have no limit, so make all weighted views as tall as the largest child.
            // Children will have already been measured once.
            if (useLargestChild && heightMode != MeasureSpec.EXACTLY) {
                for (int i = 0; i < count; i++) {
                    final View child = getVirtualChildAt(i);
                    if (child == null || child.getVisibility() == View.GONE) {
                        continue;
                    }

                    final LinearLayout.LayoutParams lp =
                            (LinearLayout.LayoutParams) child.getLayoutParams();

                    float childExtra = lp.weight;
                    if (childExtra > 0) {
                        child.measure(
                                MeasureSpec.makeMeasureSpec(child.getMeasuredWidth(),
                                        MeasureSpec.EXACTLY),
                                MeasureSpec.makeMeasureSpec(largestChildHeight,
                                        MeasureSpec.EXACTLY));
                    }
                }
            }
        }

        if (!allFillParent && widthMode != MeasureSpec.EXACTLY) {
            maxWidth = alternativeMaxWidth;
        }

        maxWidth += mPaddingLeft + mPaddingRight;

        // Check against our minimum width
        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());

        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                heightSizeAndState);

        if (matchWidth) {
            forceUniformWidth(count, heightMeasureSpec);
        }
    }

代码中注释的已经很清楚了,接下来看看 measureChildBeforeLayout 方法

void measureChildBeforeLayout(View child, int childIndex,
            int widthMeasureSpec, int totalWidth, int heightMeasureSpec,
            int totalHeight) {
        measureChildWithMargins(child, widthMeasureSpec, totalWidth,
                heightMeasureSpec, totalHeight);
    }

这个方法就比较简单了,其实就是根据前面获取的宽的MeasureSpec和高的MeasureSpec以及已经使用的宽度和已经使用的高度去计算子view

ViewGroup#measureChildWithMargins

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

也很简单就是调用ViewGroup的getChildMeasureSpec方法

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);
        //剩余大小
        int size = Math.max(0, specSize - padding);

        int resultSize = 0;
        int resultMode = 0;

        switch (specMode) {
        // 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 时已经说过:

MeasureSpec 的作用在于:在 measure 过程中,子 View 的 MeasureSpec 是由 父 View 的 MeasureSpec 以及 子 View 的 LayoutParams 共同决定的

结论:

  • 当子 View 的 LayoutParams 为 LayoutParams.MATCH_PARENT 时,子 View 的 MeasureSpec 模式为 父 View 的 MeasureSpec 模式(父 View 为 EXACTLY,子 View 也为 EXACTLY,父 View 为 AT_MOST,子 View 也为 AT_MOST),而 子 View 的大小就是 父 View 剩余空间的大小
  • 当子 View 的 LayoutParams 为 LayoutParams.WRAP_CONTENT 时,不管父 View 的 MeasureSpec 模式是 EXACTLY(精准模式)还是 AT_MOST(最大模式),子 View 的 MeasureSpec 都为 AT_MOST(最大模式),子 View 的大小最大不得超过父 View 剩余空间的大小

到这里为止,也就非常清楚的知道 View 的 measure 过程到底是怎么执行的了

有几个小问题在这里记录一下

  • 自定义 View 继承 ViewGroup 时,需要手动处理自身的 Padding 以及子 View 的margin
    其实这个问题在上面已经解释的很清楚了,我们的 LinearLayout 就是继承自 ViewGroup,看源码时难道没有注意到其实已经对自身的 Padding 以及子 View 的 margin 做处理操作了么,不然为什么一直在就算剩余空间的大小呢? 嗯哼 ???
  • 自定义 View 继承 View 时,需要手动处理自身 Padding
    这个是必须手动处理的,因为父 View 仅仅处理了它自己的 padding 以及子 View 的 margin,并没有处理子 View 的 padding,如果继承 View 时没有对 padding 做处理操作,那么设置 padding 将没有任何效果,可以看 TextView 的 onMeasure 方法

这个是必须手动进行处理的,因为父 View 在调用 getChildMeasureSpec 方法时,当 view 的 LayoutParams 为 wrap_content 时,view 的测量大小最大不得超过剩余空间的大小,但是并没有给出具体的大小(只给了一个上限值),如果不对 View 的 wrap_content 做出来,默认是采用 match_parent 的,也就是会占满剩余空间,那么后续的 View 将不能正常显示,具体处理可以参照 TextView 的 onMeasure 方法

完事......

上一篇下一篇

猜你喜欢

热点阅读