前言
自定义View和自定义ViewGroup前,如果了解了需要重写方法的一些工作流程,会让自定义工作更加得心应手。
自定义的时候至少要干的事情
- 自定义View主要需要重写
onMeaure
方法和onDraw
方法,一个是确定View的大小,另一个是绘制View的内容 - 自定义ViewGroup主要需要重写
onMesure
方法和onLayout
方法,一个用于确定ViewGroup的大小,另一个确定子View的位置。onDraw
方法反而不是必须重写的,甚至不一定会被调用,只有当ViewGroup拥有background或调用setWillNotDraw(false)
后才会回调onDraw
方法。
onMeasure的默认实现
在View的源码中存在onMeasure
的默认实现,ViewGroup继承View并且没有重写该方法,使用的还是默认的实现。
//onMeasure的实现
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
关于参数MeasureSpec
详见 MeasureSpec到底是个什么东西
方法的实现具体涉及额外的四个方法:setMeasuredDimension
、getDefaultSize
、getSuggestedMinimumWidth
、getSuggestedMinimumHeight
,其中getSuggestedMinimumWidth
和getSuggestedMinimumHeight
差不多,返回了mBackground
的size
和miniSize
的其中较大的那一个,所以背景图片能够撑开一个View。
getDefaultSize
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
通过源码可以了解到这个方法是根据测量模式来返回指定值,当模式是UNSPECIFIED
的时候返回getSuggestedMinimumSize
方法传过来的值,当测量模式是AT_MOST
和EXACTLY
时返回由父View给定的适当尺寸。从这里可以看出在自定义View的时候如果不重写onMeasure
方法处理AT_MOST
测量模式的话,wrap_content
是不会生效的。
setMeasuredDimension
它是最后设置View宽高的方法。
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);
}
这里做了一个光学边界判断isLayoutModeOptical
。简单查了一下资料,据说是Android 5.0才加的,具体作用还未详细了解不过出现在这里并不影响后续阅读,不过这个点需要今后留意。之后就调用了setMeasuredDimensionRaw