浅析View的Measure过程

一,疑问?

   相信很多人有过这样的经历,直接在(setContentView)之后就直接使用view.getMeasuredHeight/Width,
很尴尬, 结果是0。 那么大家有没有想想为什么是0?

  前面我们提到view测绘流程实际源头是activity.onResume回调之前创建的ViewRootImpl处理。而setContentView也仅仅只是构建视图层级结构而已,还远远未到需要测绘的地步。

二,measure准备

   1,MeasureSpec:  这个是测量主要的参数,共32位, 由两部分组成 Mode(前两位),  Size(后三十位) 。
       Mode: 
           EXACTLY  (精确) 字面意义很清晰,大小固定。  
          AT_MOST:  (最多) ,这个“最多”有点,大家想想wrap_content ,子视图要测量自己实际大小,方可告知父视图。 
           UNSPECIFIED:  (不明确), 就是根本不明确任何大小

        Size:  大小

  2, Mode 如何明确
         EXACTLY
         (1) 如果父视图明确大小, 如果子视图Match_Parent ,那么子视图也是EXACTLY
         (2) 不管父视图是否明确,如果子视图固定大小,那么子视图也是EXACTLY
  
          AT_MOST:
         (1) 如果父视图大小明确, 如果子视图wrap_content, 那么子视图也是AT_MOST
         (2) 如果父视图大小不明确,子视图wrap_content,或者match_parent,  子视图也是AT_MOST

        UNSPECIFIED:
         (1) 父视图 UNSPECIFIED, 子视图未明确大小,那么子视图就UNSPECIFIED
         (2) 子视图本身UNSPECIFIED


3, measure思路
    view树在遍历过程, 根部视图是知道自身大小的(如屏幕大小),在遍历过程,子视图如何确定自己范围呢?必须依赖父视图给子视图,因此,父视图会将建议子视图大小传过去,依次类推。
    如何界定子视图需要大小? 父视图本身的padding,以及子视图的margin属性其实都是不属于子视图区域的,因此得剔除。

三,measure流程

1, ViewRootImpl.performMeasure
    private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
        try {
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

2, DecorView.OnMeasure(实际FrameLayout)

 @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++) {
            final View child = getChildAt(i);
            if (mMeasureAllChildren || child.getVisibility() != GONE) {
                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                maxWidth = Math.max(maxWidth,
                        child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
                maxHeight = Math.max(maxHeight,
                        child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
                childState = combineMeasuredStates(childState, child.getMeasuredState());
                if (measureMatchParentChildren) {
                    if (lp.width == LayoutParams.MATCH_PARENT ||
                            lp.height == LayoutParams.MATCH_PARENT) {
                        mMatchParentChildren.add(child);
                    }
                }
            }
        }

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

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

        // Check against our foreground's minimum height and width
        final Drawable drawable = getForeground();
        if (drawable != null) {
            maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
            maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
        }

        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                resolveSizeAndState(maxHeight, heightMeasureSpec,
                        childState << MEASURED_HEIGHT_STATE_SHIFT));measureMatchParentChildren =
                MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
                MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
        mMatchParentChildren.clear();

主要处理了:让子视图去measure本身(当然传给子视图建议大小measurespec)
                    setMeasuredDimension设置此次measure后当前视图的measure大小。

附:我们前面说到getMeasuredHeight获取结果为0,实际就是通过setMeasuredDimension设置measure后的结果。这样我们才能获取正确数据,因此必须要先measure。
注:我们在自定义view的onMeasure后要么super.onMeasure,要么自己重写后调用setMeasuredDimension。务必调用setMeasuredDimension,该方法调用反映测量过程完结。

3,measureChildWithMargin
    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);
    }
 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 = 0;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size.... find out how
                // big it should be
                resultSize = 0;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

计算传递给子视图的建议大小,排除父视图本身padding, 子视图margin。以及确定子视图的mode。子视图进一步measure本身。


四:小结

(1)我们务必在measure之后方能拿到getMeasuredHeight/Width。
(2)务必在onMeasure中调用setMeasuredDimension返回measure结果。
(3)measure由根视图开始分发,(根视图是包含固定大小(由window或屏幕决定)),根视图在会让子视图measure,传递给子视图建议空间(当然去除父视图padding,和子视图margin后大小)



 
 
 
 

猜你喜欢

转载自blog.csdn.net/u011098381/article/details/79707209