欢迎访问移动开发之家(rcyd.net),关注移动开发教程。移动开发之家  移动开发问答|  每日更新
页面位置 : > > 内容正文

Android视图绘制流程之measure,android视图measure

来源: 开发者 投稿于  被查看 35649 次 评论:66

Android视图绘制流程之measure,android视图measure


借鉴自开发艺术


view的measure


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);
    }
}
performMeasure是入口


直接看measure的onMeasure

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

getDefaultSize

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:
        result = size;
        break;
    case MeasureSpec.AT_MOST:
    case MeasureSpec.EXACTLY:
        result = specSize;
        break;
    }
    return result;
}
UNSPECIFIED可以不看。从MeasureSpec中解包出SpecMode、SpecSize。这里的SpecSize就是View测量后的大小。(而View最终的大小是在layout阶段确定的)从代码逻辑中可以看出来,AT_MOST和EXACTLY是一种操作,所以getDefaultSize方法返回的就是SpecSize。


UNSPECIFIED情况一般用于系统内部的测量,所以getSuggestedMinimumHeight()方法获取的是UNSPECIFIED情况下的高,宽亦然。暂时不必深究。


从getDefaultSize方法的实现来看,View的宽高由SpecSize决定,所以可以得出结论:直接继承View的自定义控件需要重写onMeasure方法并设置wrap_content时的自身大小。否则在布局中使用wrap_content就相当于使用match_parent。如果View在布局中是wrap_content,那么他的SpecMode是AT_MOST,在这种模式下,他的宽高等于SpecSize。这种情况下,SpecSize是parentSize(父容器中当前可以使用的大小),这种效果和在布局中使用match_parent一致。

如何解决这个问题?

View view = new View(this){
    private int mWidth = 20;
    private int mHeight = 20;
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
        if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(mWidth, mHeight);
        } else if (widthSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(mWidth, heightSpecSize);
        } else if (heightSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(widthSpecSize, mHeight);
        }
    }
};
其实这个很容易理解。比如TextView,你如果在xml中(即lp中)设置了wrap_content,一般性都是包住文本即可,但是我们不说,谁能知道?所以这个必须得我们去设置。mWidth,mHeight就是我们设置的默认值,在TextView中要进行对字体大小的计算就可以得到大小。如果没有这个设置的话,就相当于match_parent了。


view group的measure


view group是一个抽象类,没有重写view的onMeasure方法,但是提供了一个measureChildren方法。

protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
    final int size = mChildrenCount;
    final View[] children = mChildren;
    for (int i = 0; i < size; ++i) {
        final View child = children[i];
        if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
        }
    }
}

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);
}
measureChild的过程就是去除子元素的lp,再通过getChildMeasureSpec来创建子元素的MeasureSpec,之后把子元素的MeasureSpec传给child进行测量。


view group没有重写onMeasure,因为onMeasure在不同的布局中测量的细节都不一样。


LinearLayout的onMeasure方法

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    if (mOrientation == VERTICAL) {
        measureVertical(widthMeasureSpec, heightMeasureSpec);
    } else {
        measureHorizontal(widthMeasureSpec, heightMeasureSpec);
    }
}
看看measureVertical的关键代码

// See how tall everyone is. Also remember max width.
for (int i = 0; i < count; ++i) {
    final View child = getVirtualChildAt(i);
    //...
    // 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).
    final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
    measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
            heightMeasureSpec, usedHeight);

    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;
    mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
            lp.bottomMargin + getNextLocationOffset(child));

    if (useLargestChild) {
        largestChildHeight = Math.max(childHeight, largestChildHeight);
    }
}
会对每个孩子都执行measureChildBeforeLayout方法。这个方法内部会调用子元素的measure方法,这样各个子元素就依次进入measure过程,并且系统会通过mTotalLength这个变量来存储LinearLayout在竖直方向的初步高度。每测量一个子元素,mTotalLength就会增加。增加的部分主要包括了子元素的高度以及子元素在竖直方向上的margin等。当子元素测量完毕后,LinearLayout会测量自己的大小。

// Add in our padding
mTotalLength += mPaddingTop + mPaddingBottom;

int heightSize = mTotalLength;

// Check against our minimum height
heightSize = Math.max(heightSize, getSuggestedMinimumHeight());

// Reconcile our calculated size with the heightMeasureSpec
int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
heightSize = heightSizeAndState & MEASURED_SIZE_MASK;
...
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
heightSizeAndState);
当子元素测量完后,LinearLayout会根据子元素的情况来测量自己的大小。对于竖直的LinearLayout来说,他的水平方向的测量过程遵循View的测量过程,竖直方向上的测量过程就和View的不一样了。如果它的布局中高度采用的是match_parent或者具体数值,那么它的测量过程和View一致,即高度为SpecSize。如果它的布局高度采用的是wrap_content,那么它的高度是所有子元素所占用的高度总和,但是仍然不能超过它的父容器的剩余空间。它的最终高度还需要考虑其在竖直方向的padding。这个过程源码如下

public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
    final int specMode = MeasureSpec.getMode(measureSpec);
    final int specSize = MeasureSpec.getSize(measureSpec);
    final int result;
    switch (specMode) {
        case MeasureSpec.AT_MOST:
            if (specSize < size) {
                result = specSize | MEASURED_STATE_TOO_SMALL;
            } else {
                result = size;
            }
            break;
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        case MeasureSpec.UNSPECIFIED:
        default:
            result = size;
    }
    return result | (childMeasuredState & MEASURED_STATE_MASK);
}

查看评论
相关频道:

用户评论