[例证]浅谈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()和getMeasureWidth()区别_第1张图片

这么做肯定没问题。

有这么个疑问:

二、getwidth,getheight和getMeasuredWidth和getMeasuredHeight的分别是什么?区别是什么?

定义总结:

getwidth():viewGroup的遍历每个子view,子view的layout()方法测量的结果。测量方式:getwidth=子布局右侧-子布局左侧;

getMeasuredWidth():viewGroup的遍历每个子view,子view的最近一次调用measure()方法测量后得到的,就是View的宽度。

源码分析区别:

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

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

三、View#getMeasuredWidth():

public final int getMeasuredWidth() {
        return mMeasuredWidth & MEASURED_SIZE_MASK;
    }

得到的是最近一次调用measure()方法测量后得到的是View的宽度。

跟一下源码知道:

平时我们自定义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()与getMeasuredHeight()方法的值。

例如:

所以当我们重写onMeasure方法时,如果对setMeasuredDimension()这个方法参数直接自定义,如setMeasuredDimension(200,300),那么getMeasuredWidth()的值必然就是200,getMeasuredHeight()的值必然就是300。
使用场景:

当然,我们一般情况下,不会使用直接这种方式写死参数,一般还是对onMeasure(int widthMeasureSpec, int heightMeasureSpec)方法的参数进行处理,再传入setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);

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

总结
子View的MeasureSpec由父容器的MeasureSpec和自身的LayoutParams共同决定”这个结论
为什么使用?

因为我们

自定义view对于属性为wrap_content这种情况,如果不做处理其实是与match_parent是一样效果的

原因是什么呢?

参考如下:任玉刚的书中表格:(源码也可分析,暂不做此处发散)

[例证]浅谈getWidth()和getMeasureWidth()区别_第2张图片

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

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

[例证]浅谈getWidth()和getMeasureWidth()区别_第3张图片

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

[例证]浅谈getWidth()和getMeasureWidth()区别_第4张图片

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


[例证]浅谈getWidth()和getMeasureWidth()区别_第5张图片
这回效果很明显了吧。除非是精确值,否则大小都等于父布局的大小。

那么这我当然不能接受。我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);
        }
    }

 
  

再看效果图:

[例证]浅谈getWidth()和getMeasureWidth()区别_第6张图片

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

五、View#getWidth()

源码如下

View#getWidth()

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

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

View#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方法,它的源码如下:

View#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方法,

重写后的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().

此处简单附上精简源码:

ViewRootImpl#performTraversals() 源码如下:

private void performTraversals() {
            ...

        if (!mStopped) {
            int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);  // 1
            int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
            performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);       
            }
        } 

        if (didLayout) {
            performLayout(lp, desiredWindowWidth, desiredWindowHeight);
            ...
        }


        if (!cancelDraw && !newSurface) {
            if (!skipDraw || mReportNextDraw) {
                if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
                    for (int i = 0; i < mPendingTransitions.size(); ++i) {
                        mPendingTransitions.get(i).startChangingAnimations();
                    }
                    mPendingTransitions.clear();
                }

                performDraw();
            }
        } 
        ...
}





你可能感兴趣的:(android,源码,getWidth)