视图绘制三部曲之onMeasure()源码最简解析 带你轻松领略源代码之美

performTraversals():

(怎么找到这个类:使用as的全局搜索功能搜索ViewRootImpl

(太长了:由于这个方法800多行,所以着重挑选出与视图绘制有关的内容。)

(为什么要看这个类?View的绘制从这个方法开始。)


取得父视图的大小

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

getRootMeasureSpec

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;
}
就是用这个方法取得根布局的大小的。

1.match_parent 

对应MeasureSpec.EXACTLY,该是多少就是多少

2.wrap_content

对应 MeasureSpec.AT_MOST,你这个资源本身是多大就是多大,大到不能再大为止

3.自己定义了多少多少dp 

对应MeasureSpec.EXACTLY,该是多少就是多少


performMeasure()

// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
到了这里,performTraversals()就可以不看了,因为已经进入我们的onMeasure流程了(后面还会有performlayout,performDraw)

进入performMeasure以后,又会在带着参数,调用View的measure方法,measure中又会调用onmeasure方法(方法之间的跳来跳去有点多)


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;
}
这个方法代表着AT_MOST,EXACTLY(即wrap_content,Match_parent,多少多少dp)这几种正常情况,都是直接把specSize作为结果。UNSPECIFIED一般只会在自定义控件的时候出现这种操作。(你直接覆盖父类的onMeasure()为指定数值时)


setMeasuredDimension():设置我们通过getDefaultSize()获取的视图大小

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);
}
这里很容易理解。每个view都有类型模式。这里的if语句指代的是,如果你不是父模式,那你必须在父模式下对你实际测量出来的大小再进行修改,你得按照你的父亲的大小来。

如果你就是根布局,那你直接就可以setMeasuredDimensionRaw(measuredWidth, measuredHeight);


setMeasuredDimensionRaw()

private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
    mMeasuredWidth = measuredWidth;
    mMeasuredHeight = measuredHeight;

    mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}


那么子视图的测量又在哪里呢?

这里仿佛就戛然而止了,但是根布局一定是容器控件,那一定是继承viewGroup的,所以对子view进行measure是不需要外部调用的,是每个继承自viewgroup的自己的任务。


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);
        }
    }
}
viewGroup内部维护一个子view的数组mChildren,以及子view的数目mChildrenCount。

会通过简单的for循环(其实这里用增强for循环更好)通过measureChild方法来测绘每个子的大小


measureChild

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);
}
好了,又回到measure了(这里再次强调:只有容器控件才能存放子view,所以必须得继承自viewgroup,所以会自己去测绘子view的大小,不需要外力)


最后梳理下流程

performTraversals()开始我们的视图绘制


getRootMeasureSpec()测出根布局的大小,把测出来的大小传给performMeasure()


performMeasure()调用view的measure方法


measure()调用measure方法


onMeasure()调用getDefaultSize方法获取大小,并把获取的大小通过调用setMeasuredDimension方法进行设置


setMeasuredDimension()对宽高进行最后一步处理,把最终的宽高通过调用setMeasuredDimensionRaw方法进行设置


setMeasuredDimensionRaw()把得到的真实宽高赋给全局变量


一次测绘结束


如果当前的是viewgroup且里面还有子布局,遍历测绘


全部测绘结束



贴上所有源码

performTraversals():太长了,所以贴上有关测量大小的代码

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

if (DEBUG_LAYOUT) Log.v(mTag, "Ooops, something changed!  mWidth="
        + mWidth + " measuredWidth=" + host.getMeasuredWidth()
        + " mHeight=" + mHeight
        + " measuredHeight=" + host.getMeasuredHeight()
        + " coveredInsetsChanged=" + contentInsetsChanged);

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

getRootMeasureSpec()

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

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

measure()

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    boolean optical = isLayoutModeOptical(this);
    if (optical != isLayoutModeOptical(mParent)) {
        Insets insets = getOpticalInsets();
        int oWidth  = insets.left + insets.right;
        int oHeight = insets.top  + insets.bottom;
        widthMeasureSpec  = MeasureSpec.adjust(widthMeasureSpec,  optical ? -oWidth  : oWidth);
        heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
    }

    // Suppress sign extension for the low bytes
    long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
    if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);

    final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;

    // Optimize layout by avoiding an extra EXACTLY pass when the view is
    // already measured as the correct size. In API 23 and below, this
    // extra pass is required to make LinearLayout re-distribute weight.
    final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
            || heightMeasureSpec != mOldHeightMeasureSpec;
    final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
            && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
    final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
            && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
    final boolean needsLayout = specChanged
            && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);

    if (forceLayout || needsLayout) {
        // first clears the measured dimension flag
        mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;

        resolveRtlPropertiesIfNeeded();

        int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
        if (cacheIndex < 0 || sIgnoreMeasureCache) {
            // measure ourselves, this should set the measured dimension flag back
            onMeasure(widthMeasureSpec, heightMeasureSpec);
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        } else {
            long value = mMeasureCache.valueAt(cacheIndex);
            // Casting a long to int drops the high 32 bits, no mask needed
            setMeasuredDimensionRaw((int) (value >> 32), (int) value);
            mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        }

        // flag not set, setMeasuredDimension() was not invoked, we raise
        // an exception to warn the developer
        if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
            throw new IllegalStateException("View with id " + getId() + ": "
                    + getClass().getName() + "#onMeasure() did not set the"
                    + " measured dimension by calling"
                    + " setMeasuredDimension()");
        }

        mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
    }

    mOldWidthMeasureSpec = widthMeasureSpec;
    mOldHeightMeasureSpec = heightMeasureSpec;

    mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
            (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
}

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

setMeasuredDimensionRaw()

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()

private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
    mMeasuredWidth = measuredWidth;
    mMeasuredHeight = measuredHeight;

    mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}

有耐心看到这里的人我会告诉他更多的东西(其实就是我一开始忘了现在补上去)


1. performMeasure其实是告诉host(你当前的宿主view,简单来说就是你要返回的view)你想要怎么样的宽高

// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
注释就是这个意思。

int width = host.getMeasuredWidth();
int height = host.getMeasuredHeight();
boolean measureAgain = false;
所以就这样取出来用了。


2.重测机制:当你拥有weight属性的时候,你觉得你设置的宽高还是你设置的宽高吗?

(比如说LinearLayout的weight,你设为1,方向为横向,那这个时候你设置他的宽度还有什么意义吗?)

(但是我不是很搞得懂为什么一开始不对这个weight的情况进行判断,这样不是更好吗,重测两次性能是不是有点浪费。)

if (lp.horizontalWeight > 0.0f) {
    width += (int) ((mWidth - width) * lp.horizontalWeight);
    childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
            MeasureSpec.EXACTLY);
    measureAgain = true;
}
if (lp.verticalWeight > 0.0f) {
    height += (int) ((mHeight - height) * lp.verticalWeight);
    childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
            MeasureSpec.EXACTLY);
    measureAgain = true;
}

if (measureAgain) {
    if (DEBUG_LAYOUT) Log.v(mTag,
            "And hey let's measure once more: width=" + width
            + " height=" + height);
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
一目了然,水平weight的时候修改宽度怎样怎样,竖直的时候怎样怎样。


3.layoutRequested设为true,开启onLayout()

layoutRequested = true;
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
boolean triggerGlobalLayoutListener = didLayout
        || mAttachInfo.mRecomputeGlobalAttributes;
if (didLayout) {
    performLayout(lp, mWidth, mHeight);

写的真漂亮!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值