在我们自定义View的时候,在默认情况下,也就是继承View之后,什么都不干。我们设置这个自定义的View的宽高都为wrap_content。
public class CustomView extends View {
public CustomView(Context context) {
super(context);
}
public CustomView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public CustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".PopViewActivity">
<com.xzh.viewdemos.CustomView
android:background="#2979FF"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
</LinearLayout>
我们发现View填充了父View,也就是相对于match_parent的效果,这是为什么呢?
要想弄明白为什么。我们需要知道默认情况下,View是怎么设置这个大小的。
onMeasure的默认实现就是下面这几行代码。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
getDefaultSize是我们的关键代码,这个方法是一个工具方法,因为在自定义View的时候需要从measureSpec读取出size和mode,并且还要根据mode做判断,宽高都一样的流程,所以做了一个封装。
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;
}
这里有个关键点是MeasureSpec.AT_MOST是没有break语句的,也就是说AT_MOST和EXACTLY走的是同一段代码。AT_MOST对应wrap_content,EXACTLY对应match_parent和固定值。所以wrap_content和match_parent一个效果。这也就回答了为什么wrap_content和match_parent是一个效果。
这里就不得不引申出一个问题?为什么EXACTLY能够实现match_parent的效果?
从上面的代码可以看到,关键就在于specSize这个变量。
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;
}
//Undefine的代码
//...
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
这里最关键的是获取size这行代码,size指的是父View还有多少空间让子View填充。switch判断里面的每个case都要做三个判断,是固定值,LayoutParams.MATCH_PARENT还是LayoutParams.WRAP_CONTENT。因为用户在xml里面设置的方式就这三种,所以都要判断,不过在MeasureSpec.AT_MOST还是在MeasureSpec.EXACTLY里面,都是赋值为这个size,也就是填充父布局大小,实现的效果也就是所谓的match_parent了。
int size = Math.max(0, specSize - padding);
参考:https://cloud.tencent.com/developer/article/1394231