[例证]浅谈getWidth()和getMeasureWidth()区别

    xiaoxiao2026-08-17  0

    一、一个简单的例子:

    重写自定义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和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是一样效果的

    原因是什么呢?

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

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

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

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

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

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

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

    于是有了

    <pre name="code" class="java">@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()

    源码如下

    View#getWidth()

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

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

    View#layout()

    <pre name="code" class="java">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方法,它的源码如下:

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

    看下布局文件:

    <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().

    此处简单附上精简源码:

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

    转载请注明原文地址: https://ju.6miu.com/read-1311265.html
    最新回复(0)