绘制流程(二)

515 阅读2分钟

在上一篇绘制流程(一)ViewRootImplperformTraversals()方法会执行布局的测量、布局、绘制流程,接下来源码分析一下这些流程。

1.测量

ViewRootImplperformTraversals()调用performMeasure()执行测量流程

ViewRootImpl.java

final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams();

private void performTraversals(){
    ...
    WindowManager.LayoutParams lp = mWindowAttributes;
    ...
    //获取宽测量规格
    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
    //获取高测量规格
    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
    ...
     // Ask host how big it wants to be
    //开始测量
    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.
        //大小为windowSize,测量模式为EXACTLY
        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;
}

performMeasure方法接受两个参数:宽、高测量规格。这两个参数通过getRootMeasureSpec方法获取。在创建mWindowAtributes时,width和height被设置为MATCH_PARENT。

WindowManager.LayoutParams

public LayoutParams() {
    super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
    type = TYPE_APPLICATION;
    format = PixelFormat.OPAQUE;
}

因此childWidthMeasureSpec、childHeightMeasureSpec都为MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY)

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
    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);
    }
}

mView为DecorView它继承自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;
    //开始遍历子View测量他们的大小
    for (int i = 0; i < count; i++) {
        final View child = getChildAt(i);
        if (mMeasureAllChildren || child.getVisibility() != GONE) {
            //测量子View的大小
            measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            //获取子View中所需最大宽度值
            maxWidth = Math.max(maxWidth,
                    child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
            //获取子View中所需最大高度值
            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));

    ....
}

这里调用measureChildWithMargins方法用来测量子View的大小

protected void measureChildWithMargins(View child,
        int parentWidthMeasureSpec, int widthUsed,
        int parentHeightMeasureSpec, int heightUsed) {
    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
    //获取子View的测量规格
    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
            mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                    + widthUsed, lp.width);
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
            mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                    + heightUsed, lp.height);
    //开始测量子View
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

子View的测量规格由父容器测量规格以及自身的LayoutParams共同决定

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
    // 父容器要求子View自己决定其大小
    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);
}
parentSpecModeparentSpecSizechildDimensionchildSpecModechildSpecSize
EXACTLYsize> 0EXACTLYchildDimension
同上同上MATCH_PARENTEXACTLYsize
同上同上WRAP_CONTENTAT_MOSTsize
AT_MOST同上> 0EXACTLYchidlDimension
同上同上MATCH_PARENTAT_MOSTsize
同上同上WRAP_CONTENTAT_MOSTsize
UNSPECIFIED同上>0EXACTLYchildDimension
同上同上MATCH_PARENTUNSPECIFIEDsize
同上同上WRAP_CONTENTUNSPECIFIEDsize

ViewGroup的测量

ViewGroup需要测量所有子View,可调用measureChild、measureChildren、measureChildWithMargins对子View进行测量。然后根据自身业务需求获取自身大小,通过setMeasuredDimension()方法来保存自身测量结果。

2.布局

ViewRootIml.java

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
        int desiredWindowHeight) {
    ...
    final View host = mView;
    if (host == null) {
        return;
    }
    ...
    try {
        host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
    }
    ...
}

View.java

public void layout(int l, int t, int r, int b) {
    ...
    boolean changed = isLayoutModeOptical(mParent) ?
            setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
        onLayout(changed, l, t, r, b);
        ...
    }
    ...
}

在setFrame方法中会设置View的left,top,right,bottom属性。对于ViewGroup需要在onLayout方法中对子View进行摆放(调用子View的layout方法)