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

来源:互联网 发布:淘宝买家账号钻石级别 编辑:程序博客网 时间:2024/06/09 23:10

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 beperformMeasure(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 beperformMeasure(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 beperformMeasure(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
原创粉丝点击