浅谈getWidth()和getMeasureWidth()区别

来源:互联网 发布:ubuntu terminal替换 编辑:程序博客网 时间:2024/06/09 21:00

一个简单的例子:

重写自定义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<OnLayoutChangeListener> listenersCopy =                        (ArrayList<OnLayoutChangeListener>)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);

看下布局文件:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    >    <com.example.user.mytestview.MyViewGroup        android:layout_width="match_parent"        android:layout_height="match_parent">        <com.example.user.mytestview.MyView            android:layout_width="match_parent"            android:layout_height="match_parent" />    </com.example.user.mytestview.MyViewGroup></RelativeLayout>
在子view种打个log:发现

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

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

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

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


3 2
原创粉丝点击