重写自定义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():viewGroup的遍历每个子view,子view的layout()方法测量的结果。测量方式:getwidth=子布局右侧-子布局左侧;
getMeasuredWidth():viewGroup的遍历每个子view,子view的最近一次调用measure()方法测量后得到的,就是View的宽度。
源码分析区别:
回到这个getWidth()方法和getMeasureWidth()的区别这个问题上。
网上说法很多,我决定自己一点点从源码里面扣。然后举例说明。
跟一下源码知道:
平时我们自定义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);
因为我们
自定义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()
@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重写。
当继承布局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不一样了。
由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(); } } ... }
