浅谈getWidth()和getMeasureWidth()区别

一个简单的例子:

重写自定义View的onDraw()代码:

        oval.left=getMeasuredWidth()/2-radius;                              //左边
        oval.top=getMeasuredHeight()/2 -radius;                                   //上边
        oval.right=getMeasuredWidth()/2 +radius;                             //右边
        oval.bottom=getMeasuredHeight()/2 +radius;
        canvas.drawArc(oval,0,360,true,mPaint);

得到效果图如下:


这么做肯定没问题。

有这么个疑问:

为什么我不用getwidth,getheight方法?

大多数情况下,getwidth和getMeasureWidth方法得到的结果都是一样的。

回到这个getWidth()方法和getMeasureWidth()的区别这个问题上。

网上说法很多,我决定自己一点点从源码里面扣。然后举例说明。

View#getMeasuredWidth():

public final int getMeasuredWidth() {
        return mMeasuredWidth & MEASURED_SIZE_MASK;
    }
得到的是最近一次调用measure()方法测量后得到的是View的宽度。

大概跟一下源码知道:

平时我们自定义View会重写onMeasure方法:(什么情况下写onMeasure?后续会有解答)

View#onMeasure源码如下:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }
View#onMeasure方法会调用View#setMeasuredDimension方法:

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#setMeasuredDimension方法会调用View#setMeasuredDimensionRaw方法:

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

        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
    }
看到这个方法代码第一行第二行有个赋值

mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
这就是我们的getMeasuredWidth()方法的值。

所以当我们重写onMeasure方法时,如果对setMeasuredDimension()这个方法参数直接自定义,如setMeasuredDimension(200,300),那么getMeasuredWidth()的值必然就是200,getMeasuredHeight()的值必然就是300。
当然,我们一般情况下,不会使用直接这种方式写死参数,一般还是对onMeasure(int widthMeasureSpec, int heightMeasureSpec)方法的参数进行处理,再传入setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);

什么情况下我们需要对onMeasure方法重写呢?

因为我们自定义view对于属性为wrap_content这种情况,如果不做处理其实是与match_parent是一样效果的。原因是什么呢?参考如下:任玉刚的书中表格:

当父容器的specmode为EXACTLY和AT_MOST时子view不管是wrap_content还是match_parent,它的默认大小都是父容器大小parentSize。

不信?举个栗子:
直接先上一个自定义view为match_parent时的效果图:

再上一个自定义view为wrap_content时的图:

好吧,没有比较就没有伤害。上一个自定义view为40dp的宽高的图。



这回效果很明显了吧。除非是精确值,否则大小都等于父布局的大小。

那么这我当然不能接受。我wrap_content需要有所变化,需要一个默认值大小200*200。

于是有了

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int speSize = MeasureSpec.getSize(heightMeasureSpec);
        int speMode = MeasureSpec.getMode(heightMeasureSpec);
        Log.d("MyView", "---speSize = " + speSize + "");
        Log.d("MyView", "---speMode = " + speMode + "");
        if(speMode == MeasureSpec.AT_MOST){
            Log.d("MyView", "---AT_MOST---");
        }
        if(speMode == MeasureSpec.EXACTLY){
            Log.d("MyView", "---EXACTLY---");
        }
        if(speMode == MeasureSpec.UNSPECIFIED){
            Log.d("MyView", "---UNSPECIFIED---");
        }
        if(speMode==MeasureSpec.AT_MOST){
            setMeasuredDimension(100, 100);
        }else{
        setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
        }
    }

再看效果图:

所以由此类推,我们之所以重写onMeasure也就是为了wrap_content时能自动按照需求改变。回到原本的话题的第二块:getWidth()方法:

View#getWidth()

源码如下

 @ViewDebug.ExportedProperty(category = "layout")
    public final int getWidth() {
        return mRight - mLeft;
    }

这里的mRight和mLeft到底是什么呢?其实它是layout过程传过来的四个参数中的两个:

public void layout(int l, int t, int r, int b) {
        if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
            onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        }

        int oldL = mLeft;
        int oldT = mTop;
        int oldB = mBottom;
        int oldR = mRight;

        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);
            mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;

            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnLayoutChangeListeners != null) {
                ArrayList listenersCopy =
                        (ArrayList)li.mOnLayoutChangeListeners.clone();
                int numListeners = listenersCopy.size();
                for (int i = 0; i < numListeners; ++i) {
                    listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
                }
            }
        }

        mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
        mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
    }
有两种测量setOpticalFrame、setFrame,最终都会在其中调用了setFrame方法,它的源码如下:

protected boolean setFrame(int left, int top, int right, int bottom) {  
        boolean changed = false;  
        if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {  
            changed = true;  
              
           //.... 
  
            mLeft = left;  
            mTop = top;  
            mRight = right;  
            mBottom = bottom;  
              
           //......
              
        }  
        return changed;  
    }  
这样就有了mLeft和mRight两个值了。

当然,光知道来处还的会用。我们平时自定义view继承自view时是不会对onlayout方法重写的。只有当重写布局viewGroup时才会对onlayout重写。

什么时候会遇到getWidth()和getMeasureWidth()不一致?

当继承布局viewGroup时,重写onlayout方法。对子view的 childView.layout(0,0,200,200);

我们平时重写onlayout()方法主要是为了对子布局自定义,比如瀑布流,比如放不下换行显示子view这种操作。

举个栗子:

当继承布局viewGroup时,重写onlayout方法,码如下:

 @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int mViewGroupWidth  = getMeasuredWidth();  //当前ViewGroup的总宽度
        int mPainterPosX = l;  //当前绘图光标横坐标位置
        int mPainterPosY = t;  //当前绘图光标纵坐标位置
        int childCount = getChildCount();
        for ( int i = 0; i < childCount; i++ ) {

            View childView = getChildAt(i);

            int width  = childView.getMeasuredWidth();
            int height = childView.getMeasuredHeight();

            //如果剩余的空间不够,则移到下一行开始位置
            if( mPainterPosX + width > mViewGroupWidth ) {
                mPainterPosX = l;
                mPainterPosY += height;
            }

            //执行ChildView的绘制
//            childView.layout(mPainterPosX,mPainterPosY,mPainterPosX+width, mPainterPosY+height);
            childView.layout(0,0,100, 200);

            //记录当前已经绘制到的横坐标位置
            mPainterPosX += width;
        }
    }
这里对子view布局使用固定值childView.layout(0,0,100, 200);

看下布局文件:


    
        
    
在子view种打个log:发现

现在子 view的getWidth和getMeasuredWidth不一样了。

什么时候用getWidth?什么时候用getMeasureWidth()?

由view绘制流程我们知道:顺序是:onMeasure()--》onLayout()--》onDraw();(见源码ViewRootImpl#performTraversals() 方法,下一篇打算讲这个内容)

所以再onMeasure之后可以getMeasuredWidth,在Onlayout()之后 可以用getWitdth().


你可能感兴趣的:(基础知识积累,android疑难)