我们看图说话,图片原创,盗取注明,view的测量从Viewgroup开始,自上而下,层级测量,一共大约十个方法会用到,虽然知道了流程,但是未必会自定义view,因为没有demo的讲解都是耍流氓。下面慢慢讲:
public class CustomViewOne extends View {
public CustomViewOne(Context context) {
this(context,null);
}
public CustomViewOne(Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);
}
public CustomViewOne(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}
}
首先我们自定义了一个view,我们将它放在布局文件中:
<?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">
<com.example.suzhudan.datastruct.CustomViewOne
android:layout_width="match_parent"
android:layout_height="300dp"
android:background="@color/colorAccent" />
</LinearLayout>
我们将自定义view放入布局中,设置高度为300,背景色为colorAccent,运行结果如下:
现在你会发现,你做的自定义view什么都没做,就展示出来了,并且大小也和你设置的一样,但是你确实没再onmeasure方法中做什么,默认它调用了super.onMeasure(widthMeasureSpec, heightMeasureSpec),那么你没做,那肯定是它做了。我们就看看父类的onmeasure默认做了什么能让你不费吹灰之力就完成了view的测量工作:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
里面只是调用了一个方法,setMeasuredDimension
,这个方法是保存view的测量宽高的,每个view必须调用这个方法,不然报异常。这个方法也没干什么,最终调用了setMeasuredDimensionRaw
保存了宽高。
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
宽高的获取这里是在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;
}
这里有一个知识点得讲讲,不然没法往下看了,就是MeasureSpec这个类,onmeasure方法中的两个参数widthMeasureSpec,widthMeasureSpec,其实不是一个普通的int值,从流程图中我们可以看出来是父类传递过来的,这两个值都是通过MeasureSpec生成的,是一个32位的二进制数值,高两位代表测量模式,低30位代表测量值。然后它提供了生成和分解这个值的具体方法,所以知道就可以了,没点啥。现在关键是onmeasure这两个参数的值是怎么生成的,是在流程图中的measureChild()
方法中生成的:
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);
}
这个方法是Viewgroup中的方法,也就是这个View的父类的方法,它会获取子view的LayoutParams,这个东西就是我们在布局中定义的和layout开头有关的参数的对象,我们定义的高度300就是在这个里面,然后通过getChildMeasureSpec生成measure中两个参数:
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);
}