在ViewGroup的getChildMeasureSpec方法中上回说到这里,也就是说,父亲View已经有了实际的值(父ViewGroup在Xml中的宽(高)定义都为实际的值,而不是wrap 或者 match)
// 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;
上回分析了childDimension(子View的宽高,Xml解析出来的结果),当childDimension <= 0的时候,说明解析到的是MatchParent 和 WrapContent,
那么我们下面继续看
else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
}
当childDimension解析为MatchParent的时候,最终的大小,就等于size,还记着size是什么吗?也就是我们上次提到的int size = Math.max(0, specSize - padding); 也就是父容器在宽度(高度)上目前可用的大小,然后把resultMode = MeasureSpec.EXACTLY;OK 继续看
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;
}
当解析的结果为Wrap_Content 的时候,宽度(高度)的大小竟然也赋值了最大可用大小,但是测量模式却是 MeasureSpec.AT_MOST。我们继续看下面的代码
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;
这次,父ViewGroup在Xml中没有被解析出实际的值(这里我们先这么理解),
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;
}
最终MeasureSpec.makeMeasureSpec(resultSize, resultMode);将最后的结果拼成一个MeasureSpec。
再看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.measure(childWidthMeasureSpec, childHeightMeasureSpec);
会直接调用子View的onMeasure方法,并把测量规格这个东西,传给子View.这样就差不多跑通了一部分了,不是那么一头雾水了。
这里提一个主意的地方,大家有没有过自定义简单View,发现,虽然在Xml中将View的大小定义成为了wrap_content,但是View还是填充了全部父View,而,像官方的一些控件,却不一样?
大家可以亲自体验一下这个效果,我最开始也是非常好奇,直到我看了源码,终于醒悟了(其实答案就在源码中)。那肯定有人会问了,为什么TextView,Button这些系统控件使用wrapcontent,为什么就没有填充呢?先告诉大家答案,因为那些系统控件,已经在自己的onMeasure方法里面,对父亲传过来的测量模式,进行分析,并有处理。OK,这也可以说明一个问题,View本身的大小,不完全有自己的父View决定,是由View本身和父View共同决定的。
光看文字大家觉得没什么意思,那么现在我们直接来写一个简单的demo测试一下。
我们可以直接定义一个子View就可以,父View我们直接用LinearLayout,因为还没说到onLayout 方法,OK 下面开始我们的代码
public class MyView extends View {
Paint mPaint;
public MyView(Context context) {
super(context);
initView();
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint);
}
private void initView() {
mPaint = new Paint();
mPaint.setColor(Color.BLUE);
}
}
Layout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<picture.yisi.com.viewconfigrationtest.MyView
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
Ok,就是这样,很easy,不跟你多BB,直接一个简单的自定义View,直接以View的宽高作为矩形的宽高。onDraw后面会写,大家先简单看一下结果哈,验证一样onMeasure
这个图好像有点大,截的不太好,大家见谅。果然,虽然,在上面我们View已经用了wrap_content ,但是这个图片还是填充了全部的屏幕。我们可以按照上面的源码分析一下结果,我们这里面父View LinearLayout的解析结果都是mathch_parent 所以对应着这种情况,也就是看源码
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;
第三个else if,结果就是View的最终大小,就是size(父View可用的最大大小)也就是match_parent 喽。
大家一定有疑问了,这样不是,子View的大小完全由父View决定吗?OK,下面我们试着对MyView 的onMeasure 方法进行改造,我们先只针对wrap_content 进行处理哈
改造后的MyView,多了onMeasure方法
public class MyView extends View {
Paint mPaint;
public MyView(Context context) {
super(context);
initView();
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint);
}
private void initView() {
mPaint = new Paint();
mPaint.setColor(Color.BLUE);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
//只测试一下宽度就好
switch (widthMode) {
//从ViewGroup的源码中可以看出,当为wrap_content 的时候
case MeasureSpec.AT_MOST:
//我们这里写死了200
widthSize = 200;
break;
case MeasureSpec.EXACTLY:
break;
case MeasureSpec.UNSPECIFIED:
break;
}
//千万别忘了告诉父View自己最终的大小调用这个方法。不调会崩溃哦
setMeasuredDimension(widthSize, heightSize);
}
}
看看这次的结果
呀哈,变成200了,哈哈所以这就可以说明,确实是有父View和子View共同决定的。然后我在给大家一张表,从我的老师谷歌的小弟那偷过来的,嘿嘿。
OK,onMeasure篇可能就写到这里,如果还有别的我会补充,如果没什么主意的,可能就要直接onLayout篇了。,谢谢大家的支持