Android自定义控件onMeasure、onLayout介绍
这两个方法的执行顺序是,先执行onMeasure计算大小,然后执行onLayout来布局。
## onMeasure
先说一下MeasureSpec.AT_MOST、MeasureSpec.EXACTLY、MeasureSpec.UNSPECIFIED;
MeasureSpec.makeMeasureSpec(size, mode)
MeasureSpec.EXACTLY:父视图希望子视图的大小应该是size中指定的。
MeasureSpec.AT_MOST:子视图的大小最多是size中指定的值,也就是说不建议子视图的大小超过size中给定的值。
MeasureSpec.UNSPECIFIED:我们可以随意指定视图的大小。
在onMeasure中计算子View的宽、高,
### 子View的宽、高为包裹内容
```java
for (int i = 1; i < getChildCount(); i++) {
View childView = getChildAt(i);
// 设定子view宽、高
childView.measure(
MeasureSpec.makeMeasureSpec(LayoutParams.WRAP_CONTENT, MeasureSpec.AT_MOST),
MeasureSpec.makeMeasureSpec(LayoutParams.WRAP_CONTENT, MeasureSpec.AT_MOST));
}
```
### 指定子View的宽、高
```java
for (int i = 1; i < getChildCount(); i++) {
View childView = getChildAt(i);
// 设定子view宽、高
childView.measure(
MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY));
}
```
其中100可以自己随意修改,单位是px(像素),如果想使用dp单位的话
```java
//转换dip为px
public static int convertDIP2PX(Context context, int dip) {
float scale = context.getResources().getDisplayMetrics().density;
return (int)(dipscale + 0.5f(dip>=0?1:-1));
}
```
### 不设定子View的宽、高
```java
for (int i = 1; i < getChildCount(); i++) {
View childView = getChildAt(i);
// 设定子view宽、高
childView.measure(
MeasureSpec.UNSPECIFIED,
MeasureSpec.UNSPECIFIED);
}
```
也可以这样写
```java
for (int i = 1; i < getChildCount(); i++) {
View childView = getChildAt(i);
// 设定子view宽、高
childView.measure(
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
}
```
当然,如果想要让这个自定义ViewGroup的宽高包裹内容的话,只需要获取子view的宽、高来计算,然后调用setMeasuredDimension(int measuredWidth, int measuredHeight)来设置一下就OK了。
```java
//我这里每行只放一个View,
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int parentWidthSize = 0;
int paretnHeightSize = 0;
for (int i = 1; i < getChildCount(); i++) {
View childView = getChildAt(i);
// 设定子view宽、高
childView.measure(
MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY));
parentWidthSize = childView.getMeasuredWidth();
paretnHeightSize += childView.getMeasuredHeight();
}
setMeasuredDimension(parentWidthSize, paretnHeightSize);
}
```
## onLayout
直接上代码
```java
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
for (int i = 1; i < getChildCount(); i++) {
View childView = getChildAt(i);
int childWidth = childView.getMeasuredWidth();
int childHeight = childView.getMeasuredHeight();
childView.layout(l, t, r, b);
}
}
```
一步一步看
```
childView.layout(l, t, r, b);
```
其中的
l是childView在这个自定义Viewgroup中距离左边的距离,
t是距离上边的距离,
r是自定义Viewgroup中多宽,
b是自定义Viewgroup中多高。
可以这样理解,**把这个子View看作一个矩形,那么就可以把l、t看作是矩形左上角的坐标,r、b看作是右下角的坐标,这样比较好理解**。
上面已经在onMeasure中计算过子View的宽高了,这里可以直接用,也就是这样写
```java
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
for (int i = 1; i < getChildCount(); i++) {
View childView = getChildAt(i);
//这里获取上面计算过的 子View的宽、高
int childWidth = childView.getMeasuredWidth();
int childHeight = childView.getMeasuredHeight();
childView.layout(0, 0, childWidth , childHeight );
}
}
```