measure(1)LinearLayout的measure流程

本文对LinearLayout的measure流程做一个基本分析,后边还有一篇文章做进一步分析并应用     measure(2)图片加载前预留位置

问题

1、如下布局,AImageView是ImageView的子类,请问AImageView最后的宽度是多少?
2、AImageView的onMeasure方法会调用几次?
3、我想在onCreate的时候知道AImageView的宽度,该怎么办?
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:id="@+id/aa"


    tools:context="test.onmeasure.finalss.TestMeasureActivity"
    android:orientation="vertical">


    <TextView
        android:id="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="你好你好"/>


    <iambaa.AImageView
        android:id="@+id/iamgeaaaaaa"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:background="#ff0000"/>
</LinearLayout>

public class AImageView extends ImageView {
    public AImageView(Context context) {
        super(context);
    }

    public AImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public AImageView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        LogUtil.d("call");
    }
}

初步分析

AImageView是fill_parent的,那么他的宽度应该跟parent一致,parent又是wrap_content的,有点搞不灵清了。。

measure代码分析

先来看第三个问题好了,在主activity中加入些代码来获取AImageView的measuredWidth,可以得到是168
public class TestMeasureActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test_measure);

        View vv = findViewById(R.id.aa);
        int width = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
        int height = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);

        TextView tw = (TextView) findViewById(R.id.text);
        AImageView aa = (AImageView) findViewById(R.id.iamgeaaaaaa);
        vv.measure(width, height);
        LogUtil.d(aa.getMeasuredWidth());

    }
}
那么这个168又是如何来的呢?我们来看看measure的代码,

主动measure代码分析

vv.measure(width,height);这里开始,传入0,0。vv这个LinearLayout下面有2个子view,TextView和AImageView。

measure内部主要就是调用onMeasure(widthMeasureSpec, heightMeasureSpec);

onMeasure是override的,会进入LinearLayout的onMeasure。

由于此处是VERTICAL,那就进入measureVertical. 
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
        mTotalLength = 0;
        int maxWidth = 0;
        int childState = 0;
        int alternativeMaxWidth = 0;
        int weightedMaxWidth = 0;
        boolean allFillParent = true;
        float totalWeight = 0;


        final int count = getVirtualChildCount();
        
        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);


        boolean matchWidth = false;
        boolean skippedMeasure = false;


        final int baselineChildIndex = mBaselineAlignedChildIndex;        
        final boolean useLargestChild = mUseLargestChild;


        int largestChildHeight = Integer.MIN_VALUE;


        // See how tall everyone is. Also remember max width.
        for (int i = 0; i < count; ++i) {
            final View child = getVirtualChildAt(i);


            if (child == null) {
                mTotalLength += measureNullChild(i);
                continue;
            }


            if (child.getVisibility() == View.GONE) {
               i += getChildrenSkipCount(child, i);
               continue;
            }


            if (hasDividerBeforeChildAt(i)) {
                mTotalLength += mDividerHeight;
            }
            //1.1
            //2.1
            LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();


            totalWeight += lp.weight;
            
            if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {
                // Optimization: don't bother measuring children who are going to use
                // leftover space. These views will get measured again down below if
                // there is any leftover space.
                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
                skippedMeasure = true;
            } else {
                //1.2
                //2.2
                //a.1.0
                //a.2.0
                int oldHeight = Integer.MIN_VALUE;


                if (lp.height == 0 && lp.weight > 0) {
                    // heightMode is either UNSPECIFIED or AT_MOST, and this
                    // child wanted to stretch to fill available space.
                    // Translate that to WRAP_CONTENT so that it does not end up
                    // with a height of 0
                    oldHeight = 0;
                    lp.height = LayoutParams.WRAP_CONTENT;
                }


                // 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).
                measureChildBeforeLayout(
                       child, i, widthMeasureSpec, 0, heightMeasureSpec,
                       totalWeight == 0 ? mTotalLength : 0);


                if (oldHeight != Integer.MIN_VALUE) {
                   lp.height = oldHeight;
                }


                final int childHeight = child.getMeasuredHeight();
                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
                       lp.bottomMargin + getNextLocationOffset(child));


                if (useLargestChild) {
                    largestChildHeight = Math.max(childHeight, largestChildHeight);
                }
            }


            /**
             * If applicable, compute the additional offset to the child's baseline
             * we'll need later when asked {@link #getBaseline}.
             */
            if ((baselineChildIndex >= 0) && (baselineChildIndex == i + 1)) {
               mBaselineChildTop = mTotalLength;
            }


            // if we are trying to use a child index for our baseline, the above
            // book keeping only works if there are no children above it with
            // weight.  fail fast to aid the developer.
            if (i < baselineChildIndex && lp.weight > 0) {
                throw new RuntimeException("A child of LinearLayout with index "
                        + "less than mBaselineAlignedChildIndex has weight > 0, which "
                        + "won't work.  Either remove the weight, or don't set "
                        + "mBaselineAlignedChildIndex.");
            }


            boolean matchWidthLocally = false;
            if (widthMode != MeasureSpec.EXACTLY && lp.width == LayoutParams.MATCH_PARENT) {
                // The width of the linear layout will scale, and at least one
                // child said it wanted to match our width. Set a flag
                // indicating that we need to remeasure at least that view when
                // we know our width.
                //2.3
                matchWidth = true;
                matchWidthLocally = true;
            }


            final int margin = lp.leftMargin + lp.rightMargin;
            //1.3,child.getMeasuredWidth()为168,margin0,所以measuredWidth168
            //2.4 measuredWidth 0
            final int measuredWidth = child.getMeasuredWidth() + margin;
            //2.5 maxWidth 168
            maxWidth = Math.max(maxWidth, measuredWidth);
            childState = combineMeasuredStates(childState, child.getMeasuredState());


            allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;
            if (lp.weight > 0) {
                /*
                 * Widths of weighted Views are bogus if we end up
                 * remeasuring, so keep them separate.
                 */
                weightedMaxWidth = Math.max(weightedMaxWidth,
                        matchWidthLocally ? margin : measuredWidth);
            } else {
                //1.4 alternativeMaxWidth 168
                //2.6 alternativeMaxWidth 168
                //a.1.1
                //a.2.1
                alternativeMaxWidth = Math.max(alternativeMaxWidth,
                        matchWidthLocally ? margin : measuredWidth);
            }


            i += getChildrenSkipCount(child, i);
        }


        if (mTotalLength > 0 && hasDividerBeforeChildAt(count)) {
            mTotalLength += mDividerHeight;
        }


        if (useLargestChild &&
                (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED)) {
            mTotalLength = 0;


            for (int i = 0; i < count; ++i) {
                final View child = getVirtualChildAt(i);


                if (child == null) {
                    mTotalLength += measureNullChild(i);
                    continue;
                }


                if (child.getVisibility() == GONE) {
                    i += getChildrenSkipCount(child, i);
                    continue;
                }


                final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
                        child.getLayoutParams();
                // Account for negative margins
                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + largestChildHeight +
                        lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
            }
        }


        // 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;
        
        // Either expand children with weight to take up available space or
        // shrink them if they extend beyond our current bounds. If we skipped
        // measurement on any children, we need to measure them now.
        int delta = heightSize - mTotalLength;
        if (skippedMeasure || delta != 0 && totalWeight > 0.0f) {
            float weightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;


            mTotalLength = 0;


            for (int i = 0; i < count; ++i) {
                final View child = getVirtualChildAt(i);
                
                if (child.getVisibility() == View.GONE) {
                    continue;
                }
                
                LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
                
                float childExtra = lp.weight;
                if (childExtra > 0) {
                    // Child said it could absorb extra space -- give him his share
                    int share = (int) (childExtra * delta / weightSum);
                    weightSum -= childExtra;
                    delta -= share;


                    final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                            mPaddingLeft + mPaddingRight +
                                    lp.leftMargin + lp.rightMargin, lp.width);


                    // TODO: Use a field like lp.isMeasured to figure out if this
                    // child has been previously measured
                    if ((lp.height != 0) || (heightMode != MeasureSpec.EXACTLY)) {
                        // child was measured once already above...
                        // base new measurement on stored values
                        int childHeight = child.getMeasuredHeight() + share;
                        if (childHeight < 0) {
                            childHeight = 0;
                        }
                        
                        child.measure(childWidthMeasureSpec,
                                MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));
                    } else {
                        // child was skipped in the loop above.
                        // Measure for this first time here      
                        child.measure(childWidthMeasureSpec,
                                MeasureSpec.makeMeasureSpec(share > 0 ? share : 0,
                                        MeasureSpec.EXACTLY));
                    }


                    // Child may now not fit in vertical dimension.
                    childState = combineMeasuredStates(childState, child.getMeasuredState()
                            & (MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT));
                }


                final int margin =  lp.leftMargin + lp.rightMargin;
                final int measuredWidth = child.getMeasuredWidth() + margin;
                maxWidth = Math.max(maxWidth, measuredWidth);


                boolean matchWidthLocally = widthMode != MeasureSpec.EXACTLY &&
                        lp.width == LayoutParams.MATCH_PARENT;


                alternativeMaxWidth = Math.max(alternativeMaxWidth,
                        matchWidthLocally ? margin : measuredWidth);


                allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;


                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredHeight() +
                        lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
            }


            // Add in our padding
            mTotalLength += mPaddingTop + mPaddingBottom;
            // TODO: Should we recompute the heightSpec based on the new total length?
        } else {
            //0.1 alternativeMaxWidth=168
            //a.0.0
            alternativeMaxWidth = Math.max(alternativeMaxWidth,
                                           weightedMaxWidth);




            // We have no limit, so make all weighted views as tall as the largest child.
            // Children will have already been measured once.
            if (useLargestChild && heightMode != MeasureSpec.EXACTLY) {
                for (int i = 0; i < count; i++) {
                    final View child = getVirtualChildAt(i);


                    if (child == null || child.getVisibility() == View.GONE) {
                        continue;
                    }


                    final LinearLayout.LayoutParams lp =
                            (LinearLayout.LayoutParams) child.getLayoutParams();


                    float childExtra = lp.weight;
                    if (childExtra > 0) {
                        child.measure(
                                MeasureSpec.makeMeasureSpec(child.getMeasuredWidth(),
                                        MeasureSpec.EXACTLY),
                                MeasureSpec.makeMeasureSpec(largestChildHeight,
                                        MeasureSpec.EXACTLY));
                    }
                }
            }
        }


        if (!allFillParent && widthMode != MeasureSpec.EXACTLY) {
            //0.2
            maxWidth = alternativeMaxWidth;
        }
        //0.3
        maxWidth += mPaddingLeft + mPaddingRight;
        
        // Check against our minimum width
        //0.4
        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
        //0.5
        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                heightSizeAndState);


        if (matchWidth) {
            //0.6
            forceUniformWidth(count, heightMeasureSpec);
        }
    }

主要内容在L29的for循环,流程为1.1 1.2 1.3 1.4 2.1 2.2 2.3 2.4 2.5 2.6 0.1 0.2 0.30.4 0.5 0.6

1.开头的是和第一个childview相关的,2.开头的是和第二个childview相关的。

好,我们来分析下measureVertical内的执行流程,

首先走到L12,此处的widthMode和heightMode都是UNSPECIFIED。

代码会走到1.2,走到L84,这行measureChildBeforeLayout很关键,测量子view想要多大,由于我们设置了textview是("你好你好");而且TextView的   android:layout_width="wrap_content",所以TextView的宽度由它的内容决定,所以TextView大小可以测量出来,child.getMeasuredWidth()值是168。

继续走到1.3,measuredWidth和maxWidth都会变为168。注意此处的measuredWidth是,child的getMeasuredWidth加上margin值。

走到1.4,alternativeMaxWidth变为168,for循环的第一次结束。

马上要开始第二次,关于AImageView的。会走到2.3,这里记

下matchWidth = true; matchWidthLocally =true;

           if (widthMode != MeasureSpec.EXACTLY && lp.width == LayoutParams.MATCH_PARENT) {
                // The width of the linear layout will scale, and at least one
                // child said it wanted to match our width. Set a flag
                // indicating that we need to remeasure at least that view when
                // we know our width.
                //2.3
                matchWidth = true;
                matchWidthLocally = true;
            }


这里的注释的意思是如果此时LinearLayout的大小还没确定,而这个child的width又是match_parent,那么等到LinearLayout的宽度确定之后必须重新measure一遍这个child以确定其measuredWidth


再走到2.4,此时AImageView的宽是fill_parent,但是parent此时还是0,所以measuredWidth是0.

走到2.5,maxWidth依然还是168。

走到2.6,此处matchWidthLocally为true了,就会选择margin而不是measuredWidth,选择好了再与alternativeMaxWidth比较。此处的意思就是,如果matchWidthLocally为true,那么我们的宽度就只要管margin就好了,而不管margin+child.getMeasuredWidth(),也就是说此时child.getMeasuredWidth()不再有意义。alternativeMaxWidth就是目前我们选择出来的最大宽度值,意义是指我们根据measureVertical传进来的2个参数以及子view的情况,得出结论我们希望这个LinearLayout的measureWidth是alternativeMaxWidth,这里是168.当然这里还没有把LinearLayout的padding算进去。

接着,走到0.1,此处我们没用到weight,alternativeMaxWidth维持168。

再走到0.3,加上padding值

走到0.5,首先调用resolveSizeAndState确定width的值,resolveSizeAndState后面会分析。然后调setMeasuredDimension

走到0.6,forceUniformWidth。这里我们回头看一下,LinearLayout有measuredWidth了,TextView有measuredWidth了,但是我们的AImageView还没有measuredWidth,为什么啊?刚才测量的时候,LinearLayout的measuredWidth为0,而AImageView是match_parent的,所以AImageView的measuredWidth也是0。那么现在已经有LinearLayout了,所以要重新管一管它的小弟。看forceUniformWidth代码,首先用当前的getMeasuredWidth和EXACTLY拼出一个uniformMeasureSpec,然后measureChildWithMargins(child, uniformMeasureSpec, 0,heightMeasureSpec, 0);

这样这个child的measuredWidth也出来了。

    private void forceUniformWidth(int count, int heightMeasureSpec) {
        // Pretend that the linear layout has an exact size.
        int uniformMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(),
                MeasureSpec.EXACTLY);
        for (int i = 0; i< count; ++i) {
           final View child = getVirtualChildAt(i);
           if (child.getVisibility() != GONE) { 
               LinearLayout.LayoutParams lp = ((LinearLayout.LayoutParams)child.getLayoutParams());
               
               if (lp.width == LayoutParams.MATCH_PARENT) {
                   // Temporarily force children to reuse their old measured height
                   // FIXME: this may not be right for something like wrapping text?
                   int oldHeight = lp.height;
                   lp.height = child.getMeasuredHeight();
                   
                   // Remeasue with new dimensions
                   measureChildWithMargins(child, uniformMeasureSpec, 0, heightMeasureSpec, 0);
                   lp.height = oldHeight;
               }
           }
        }
    }


    public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
        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:
            if (specSize < size) {
                result = specSize | MEASURED_STATE_TOO_SMALL;
            } else {
                result = size;
            }
            break;
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result | (childMeasuredState&MEASURED_STATE_MASK);
    }

 

resolveSizeAndState是view的一个static方法,这段代码其实很简单,根据measureSpec要求以及一个参考值size来确定最后的resultSize。如果measureSpec是UNSPECIFIED的,那么随便多少都行,就设置为参考值好了。如果measureSpec是AT_MOST,就说明resultSize不能超过measureSpec规定的值。如果measureSpec是EXACTLY,那就按measureSpec的值来,你自己别折腾了。


好了,再回头看看vv.measure(width, height);的流程。

1、 根据widthMeasureSpec和heightMeasureSpec来测量下它的所有子view的宽度,会得到一个最大值

2、把最大值丢到resolveSizeAndState内部,计算之后确定measuredWidth

3、更新那些宽fill_parent的子view的measuredSize。

结合本文的例子,vv.measure(width,height);过程,第一步会调用TextView和AImageView的onMeasure,然后第3步会调用AImageView的onMeasure。

getChildMeasureSpec与resolveSizeAndState

还有个疑问,measureChildBeforeLayout调用measureChildWithMargins,measureChildWithMargins调用getChildMeasureSpec,我们来看一看

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

是不是和resolveSizeAndState看起来很像,实际上完全不一样的用处,首先resolveSizeAndState是view的函数,getChildMeasureSpec是viewGroup的函数。getChildMeasureSpec是父parent要measure child了,该传入什么参数呢,通过getChildMeasureSpec来确定参数。resolveSizeAndState是在我们已经得到了一个宽度或者高度的期望值,现在在对比一下本次measure的2个spec来确定最终值。getChildMeasureSpec是获取子view 的spec,而resolveSizeAndState是获取一个最终的measuredSize值,用来setMeasuredDimension
getChildMeasureSpec这里有3个参数,spec,padding,和childdimention
spec是父窗口对子窗口的一个准备值,我们这里直接就是父窗口measure时候的参数
padding是总的padding值,包括父view的padding,子view的margin以及已经使用的width,可以看下面的代码
childdimention是子窗口的大小,这个值就是LayoutParams,一般就是从xml里读取的值
    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);
    }


getChildMeasureSpec可以看到如果当前view指定了正数的宽度,那么measure的时候就直接用 此宽度+EXACTLY,而根本不会考虑父view的准备spec。
那么如果父view只有300px宽,子view有400px宽,measure之后子view依然是400px宽吗?
答案是对的,这个时候子view的width和measuredWidth都是400,而父view是300. 居然有如此神奇的事情,子view会比父view还要大,我已经试验过,确实如此。
这个子view的宽度虽然是400,但是他的父亲只有300,所以在onDraw的时候,只绘制300,这个子view实际上没有画完被截断了。
所以要写固定的宽高,一定要考虑好,保证父view够大。否则子view可能被截断


到现在为止,我在onCreate的时候获取到了AImageView的measuredWidth,而且知道了measure的流程,但是这里是我们主动去measure的,如果去掉这些代码,它本身又是如何去measure的呢?

原有measure代码分析

修改代码为
public class TestMeasureActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test_measure);

//        View vv = findViewById(R.id.aa);
//        int width = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
//        int height = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
//
//        TextView tw = (TextView) findViewById(R.id.text);
//        AImageView aa = (AImageView) findViewById(R.id.iamgeaaaaaa);
//        vv.measure(width, height);
//        LogUtil.d(aa.getMeasuredWidth());

    }
}
在AImageView的onMeasure方法中打断点,可以看到LinearLayout的measure方法同样会被调用,看看调用层次图。
doTraversal():1054, ViewRootImpl {android.view}
performTraversals():1372, ViewRootImpl {android.view}
measureHierarchy():1166, ViewRootImpl {android.view}
performMeasure():2001, ViewRootImpl {android.view}
measure():17430, View {android.view}
onMeasure():2560, PhoneWindow$DecorView {com.android.internal.policy.impl}
onMeasure():430, FrameLayout {android.widget}
measureChildWithMargins():5463, ViewGroup {android.view}
measure():17430, View {android.view}
onMeasure():613, LinearLayout {android.widget}
measureVertical():722, LinearLayout {android.widget}
measureChildBeforeLayout():1436, LinearLayout {android.widget}
measureChildWithMargins():5463, ViewGroup {android.view}
measure():17430, View {android.view}
onMeasure():430, FrameLayout {android.widget}
measureChildWithMargins():5463, ViewGroup {android.view}
measure():17430, View {android.view}
onMeasure():613, LinearLayout {android.widget}
measureVertical():722, LinearLayout {android.widget}
measureChildBeforeLayout():1436, LinearLayout {android.widget}
measureChildWithMargins():5463, ViewGroup {android.view}
measure():17430, View {android.view}
onMeasure():27, AImageView {iambaa}

首先我们要了解view的结构图最顶部PhoneWindow的DecorView,这是FrameLayout的一个子类,他内部有一个子view,LinearLayout(A),A内部有2个子view,一个viewstub(gone的),一个FrameLayout(B),B内部有个LinearLayout(C)就是我们这里id为aa的LinearLayout。

然后void measure(int widthMeasureSpec, int heightMeasureSpec),我们要measure一个view,需要传入2个参数,一个宽度限制值,一个高度限制值

然后我们看看,LinearLayout的measure方法同样被调用,传入的参数却有不同,此时传入宽度限制值是AT_MOST加屏幕宽度组成的MeasureSpec,而我们刚才主动调用的时候传入的是UNSPECIFIED加0,就这点不一样(我们这里只管宽度)。那么这个AT_MOST加屏幕宽度组成的MeasureSpec,这个初始值最先是在哪里设置的呢?

ViewRootImpl的measureHierarchy内有这么段代码

if (!goodMeasure) {
            childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
            childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
            performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
            if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
                windowSizeMayChange = true;
            }
        }

childWidthMeasureSpec =getRootMeasureSpec(desiredWindowWidth, lp.width);这句话就定了measure DecorView的宽度限制值。

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

这里desiredWindowWidth是屏幕宽1080,那lp又是从哪里来的呢?lp就是ViewRootImpl内的mWindowAttributes,看定义处

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

lp的宽高,在构造函数中被初始化为-1即MATCH_PARENT。那么宽高在getRootMeasureSpec内走的都是

measureSpec = MeasureSpec.makeMeasureSpec(windowSize,MeasureSpec.EXACTLY);

也就是说performMeasure(childWidthMeasureSpec,childHeightMeasureSpec);这里childWidthMeasureSpec和childHeightMeasureSpec都是EXACTLY的

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

performMeasure会调到ViewGroup(DecorView)的measureChildWithMargins,DecorView的child是LinearLayout A,而A的宽高也是-1,所以经过getChildMeasureSpec计算之后的childWidthMeasureSpec和childHeightMeasureSpec依然是EXACTLY的。

然后调child.measure(childWidthMeasureSpec, childHeightMeasureSpec);,这个child是LinearLayout A,会掉measureVertical,measureChildBeforeLayout,measureChildWithMargins(纳尼,又到ViewGroup的这个函数了),只不过此时ViewGroup是LinearLayout A,child是FrameLayou B,B的宽高也是-1即MATCH_PARENT,那childWidthMeasureSpec和childHeightMeasureSpec还是EXACTLY。

然后调child.measure(childWidthMeasureSpec,childHeightMeasureSpec);,这个child是FrameLayou B,会调measure,onMeasure(FrameLayout), measureChildWithMargins(ViewGroup)。调measureChildWithMargins时,ViewGroup是FrameLayou B,child是LinearLayout C,它的宽高是-2,-1,也就是说是WRAP_CONTENT和MATCH_PARENT,所以经过getChildMeasureSpec 运算之后childWidthMeasureSpec会变为AT_MOST,而高依然是EXACTLY。到了这里就和前面对应起来了,measure LinearLayout C的宽度限制值是AT_MOST,+屏幕宽度,高度限制值是EXACTLY+屏幕高度。


此处可以多想一点,LinearLayout C是我们的content的跟布局(setContentView设置的),而measureChildWithMargins,content的跟布局作为childmeasure的时候,父View的期待的宽高限定值是(EXACTLY+屏幕宽,EXACTLY+activity内容高度)。我们以后分析measure过程的时候就不用从DecorView开始分析了,可以直接从这个点开始分析。

activity内容高度一般是指屏幕高度减去navigatorbar的高度再减去statusbar的高度


还有一点,我们一般写xml的时候,跟布局宽高一般都是match_parent,这样有什么特点呢?如果跟布局为RelativeLayout,LinearLayout,FrameLayout,content跟布局宽高为match_parent。那么onMeasure()的时候传进来的建议的高度和最后measure好了之后得到的measuredHeight一致。用代码来表示就是下边,h1和h2一直保持一致。宽度同理。这有什么意义呢?在自定义控件的时候可以在super.onMeasure(widthMeasureSpec, heightMeasureSpec);做一些处理。非常有意义!!!

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int h1=MeasureSpec.getSize(heightMeasureSpec);
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int h2=getMeasuredHeight();
        boolean ff=(h1==h2);
        LogUtil.fish(""+ff);
    }


问题答案

1、如下布局,AImageView是ImageView的子类,请问AImageView最后的宽度是多少?
LinearLayout在测量完所有子view的宽之后,决定使用TextView的宽作为它的宽。然后它看了一眼AImageView的宽是match_parent的,就更新它的measuredWidth值,AImageView的宽度和TextView宽度一致,由TextView的内容“你好你好”决定。你把TextView的内容改为“你好”,那AImageView宽也会变。
2、AImageView的onMeasure方法会调用几次?
2次,第一次是测子view想要的宽高,第二次是更新子view的measuredWidth。
3、我想再onCreate的时候知道AImageView的宽度,该怎么办?
上面已经有方法了,不说了


主动measure的宽高限制值

把aa的layout_width改为fill_parent之后,主activity改为
public class TestMeasureActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test_measure);

        View vv = findViewById(R.id.aa);
        int width = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
        int height = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);

        TextView tw = (TextView) findViewById(R.id.text);
        AImageView aa = (AImageView) findViewById(R.id.iamgeaaaaaa);
        vv.measure(width, height);
        int xx=aa.getMeasuredWidth();
        LogUtil.d(aa.getMeasuredWidth());

    }
}
我想在onCreate时知道AImageView的measuredWidth,用了上述代码,发现得到的值不合理,只有168,而按理说aa宽是fill_parent,AImageView的宽是fill_parent,那xx怎么只有168 呢?
实际上问题就在于宽度限制值设置错误,代码改为int width = View.MeasureSpec.makeMeasureSpec(screenWidth, View.MeasureSpec.EXACTLY);之后,就得到了预期的结果1080.

总结

1、measure会调onMeasure,而onMeasure内会 setMeasuredDimension。
2、view的width包括padding,但是不包括margin
3、主动测量的时候注意设置合理的宽度限定值和高度限定值
4、我们要measure一个view需要传入2个值,widthSpec和heightSpec。spec(即上文说的限定值)由什么决定呢?measure 当前view的parent的双spec参数以及当前view的宽高所决定。代码参考getChildMeasureSpec
5、一个viewgroup的measuredSize由measure传进来的2个限制值和子view的大小决定。
6、我们分析一个布局的measure过程的时候,content的跟布局作为child measure的时候,父View的期待的宽高限定值是(EXACTLY+屏幕宽,EXACTLY+activity内容高度),不用从DecorView开始分析。activity内容高度一般是指屏幕高度减去navigatorbar的高度再减去statusbar的高度
7、如果跟布局为RelativeLayout,LinearLayout,FrameLayout,content跟布局宽高为match_parent。那么onMeasure()的时候传进来的建议的高度和最后measure好了之后得到的measuredHeight一致。

猜你喜欢

转载自blog.csdn.net/litefish/article/details/46623909