当年,我初学android的时候,一直觉得能自定义各种View是一件很了不起的事情,现在过了1年,在看了各位大神的博客(感谢我的两个前辈GcsSloop,谷歌的小弟),自己总结出了一套,为了让自己印象更深刻,更是为了能让新人看得懂,也许其中也有一些问题,如果看到不妥,一定要马上指出,不过至少我按照现在这套方法,还没发现比较严重的bug,哈哈。大家做个参考
大概按照国际标准流程去写
onMeasure()
onLayout()
onDraw()
最后在一起写一个基本的自定义ViewGroup,来总结一下。
如果大家觉得写的东西能让你有所收获,就来个赞,如果不能的话,你来打我啊!
OK步入正题,写下面的流程之前,先认识几个知识点,我们一会需要用到的。
MeasureSpec
对 就是这个MeasureSpec,我刚才还忘了怎么拼,去ide复制的。这个东西,官网说他是一个16位,还是32位的一个值,具体多少位,我也记不清了,其实我也不需要记得清楚,我就记得,他的一些功能就可以了。至少我是这么做的。我们可以把他理解成一个东西。什么样的东西呢?就是可以把View的测量模式,大小集合起来,又可以拆解的这么一个东西。
MeasureSpec.makeMeasureSpec(int size,int mode)//将View大小和模式结合起来
MeasureSpec.getSize(int spec);//从东西中拆分出测量大小
MeasureSpec.getMode(int spec);//从东西中拆分出测量模式
View的大小:
宽高的实际测量值,地球人都知道
View的测量模式
说到测量模式,这可有的说了,大概是这三种测量模式。
MeasureSpec.AT_MOST;//这个模式,父容器没有检测到子View的大小,告诉子View你最大不能超过多少
MeasureSpec.EXACTLY;//父容器已经检测到子View的大小,给出确切的值。
MeasureSpec.UNSPECIFIED;//这个我不知道,没用过。有兴趣的自己去查一下吧。哇咔咔。
有没有同学像我当初一样有点懵逼,如果有,没关系,看下源码就不懵逼了。
我们就来最简单的ViewGroup 嵌套 View来说,直接看ViewGroup源码。建议大家打开ide,找到ViewGroup的源码,一起分析。
先从ViewGroup的measureChildWithMargins说起
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
先看一下各个参数:
child 顾名思义 就是子View
parentWidthMeasureSpec 宽度的测量规格
widthUsed 父View已经使用的宽度
parentHeightMeasureSpec 高度的测量规格
heightUsed 父View已经使用的高度
OK 继续深入里面的代码
//这一句,先拿到了待测量的子View的布局参数
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
然后下面有调用getChildMeasureSpec方法,并传进去了相关参数
先看一下传进去的3个参数
parentWidthMeasureSpec 父View的宽度测量规格
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin+widthUsed 这个参数是在水平方向上内外边距与已经使用的宽度之和,其实也就是 当前viewgroup已经占据的宽度。
lp.width 子View的宽度,这个是从Xml解析出来的宽度
OK 参数分析完了,下一步进去getChildMeasureSpec()这个方法里面看看他到底做了什么。代码不是很多,我们直接贴过来看
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent asked to see how big we want to be
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
有耐心的童鞋可以先简单的阅读一下代码,
前面两句
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
先从父View传来的测量规格里面,解析出测量模式,和在水平(垂直)方向上的大小(该大小是上级父View传递下来,给子View的总的可用大小,如果这里不理解可以看下面的分析)。
int size = Math.max(0, specSize - padding);
这句声明size,是上级View传递下来的可用总大小,减去父View在水平(垂直)方向已经占用的大小(就是这个padding,上面传过来的),结果就是 该View在水平(垂直)方向最大的可用大小,就是说子View的宽度(高度)最大,不可超过该大小。如果超过了,那宽度(高度)的大小就是0.
OK继续下面。
int resultSize = 0;
int resultMode = 0;
这句定义了View最终的测量模式,和测量实际大小。
继续下面代码
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
看第一个case,也就是测量模式MeasureSpec.EXACTLY,看注释就可以知道,这个case是当父View已经有了实际的测量值的情况,我们可以理解为,父View的layout_width 是实际的值,而不是wrap_content或者match_parent判断childDimension是否>= 0 ,还记得childDimension是什么吗?是子view LayoutParams.width,也就是通过Xml解析出来的layout_width的结果。
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
}
如果解析Xml解析出了layout_width的结果,那么最终大小(resultSize)就是解析出来的结果,而最终的测量模式resultMode 也就是精准测量模式MeasureSpec.EXACTLY,这里也就验证了我们上面提到的,为什么在子View的onMeasure里面可以得到MeasureSpec.EXACTLY.
OK继续下面
else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
}
这里说一下,为什么childDimension会有<0的情况。
由于解析Xml时候,如果解析到这些属性,LayoutParams就会按如下方式赋值
public static final int MATCH_PARENT = -1;
public static final int WRAP_CONTENT = -2;
所以就会出现小于0的情况。