Android 自定义ViewGroup之测量onMeasure
自定义View一般都需要重写onMeasure()、onLayout()、onDraw()。但自定义ViewGroup更重要的是onMeasure()和onLayout(),因为作为容器,onDraw()方法显得没那么必要,即时你重写了此方法,它也不会调用,毕竟容器只需要关注自己内部view的摆放和约束其大小而已。一、onMeasure如何测量child?我们在重写完onMeasure(int w
自定义View一般都需要重写onMeasure()、onLayout()、onDraw()。但自定义ViewGroup更重要的是onMeasure()和onLayout(),因为作为容器,onDraw()方法显得没那么必要,即时你重写了此方法,它也不会调用,毕竟容器只需要关注自己内部view的摆放和约束其大小而已。
一、onMeasure如何测量child?
我们在重写完
onMeasure(int widthMeasureSpec, int heightMeasureSpec)
内部有widthMeasureSpec和heightMeasureSpec两个参数,通过它我们可以调用
int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
int modeHeight = MeasureSpec.getMode(heightMeasureSpec);
获取当前view的建议值和mode,view的实际大小由建议值和mode决定;而这个mode有以下三种:
MeasureSpec.EXACTLY //精确值,设置多少就是多少
MeasureSpec.AT_MOST //至多,实际大小不超过建议值
MeasureSpec.UNSPECIFIED //任意大小,无限大
那这个mode是由什么决定的呢?先从源码解析下,ViewGroup是如何测量child
ViewGroup中测量child调用measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) 即可测量到child
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
此方法中,我们主要分析getChildMeasureSpec如何拿到measureSpec,查看源码
/**
*spec:父View的spec
*padding:父View的内边距
*childDimension:子view设置的大小,例如:30dp、match_parent、wrap_content
**/
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) {
// 父布局模式是精确值
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
//设置的值是具体的,所以实际设置多大就是多大,模式就为EXACTLY
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// 填充父布局,但是父布局是具体值,所以实际等于父布局设置的大小,模式也为EXACTLY
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// 自适应布局,但不能超过父布局的具体值,所以建议值等于父布局设置的大小,模式为AT_MOST
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// 父布局模式是至多模式
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
//设置的值是具体的,所以实际设置多大就是多大,模式就为EXACTLY
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// 填充父布局,父布局为至多,child也为至多,故模式为AT_MOST
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
//自适应,但不会超过父布局的大小,故模式为AT_MOST
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// // 父布局模式是无限制
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
//设置的值是具体的,所以实际设置多大就是多大,模式就为EXACTLY
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// 和父布局同大,模式一样
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
//自适应下,父类模式无限制,故自己也不显示,模式为MeasureSpec.UNSPECIFIED
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
//返回确定好的child 确定好的spec
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
从源码可以回头看上面的问题mode是由什么决定的呢? 主要由父控件的模式和自身在xml中设置的大小决定。
通过measureChild() 测量child,而内部是调用getChildMeasureSpec()拿到了child的spec,里面包含child的大小和模式。
更多推荐
所有评论(0)