重写自定义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()的区别这个问题上。
网上说法很多,我决定自己一点点从源码里面扣。然后举例说明。
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);
因为我们自定义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()方法:
源码如下
@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重写。
当继承布局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不一样了。
由view绘制流程我们知道:顺序是:onMeasure()--》onLayout()--》onDraw();(见源码ViewRootImpl#performTraversals() 方法,下一篇打算讲这个内容)
所以再onMeasure之后可以getMeasuredWidth,在Onlayout()之后 可以用getWitdth().