步骤:
代码:
? @Override
? protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
? ? ? //先执行原测量算法
? ? ? super.onMeasure(widthMeasureSpec, heightMeasureSpec);
? ? ? //获取原先的测量结果
? ? ? int measureWidth=getMeasuredWidth();
? ? ? int measureHeight=getMeasuredHeight();
? ? ? //利用原先的测量结果计算出新的尺寸
? ? ? if(measureWidth>measureHeight){
? ? ? ? ? measureWidth=measureHeight;
? ? ? }else{
? ? ? ? ? measureHeight=measureWidth;
? ? ? }
? ? ? //保存计算后的结果
? ? ? setMeasuredDimension(measureWidth,measureHeight);
? }
?
步骤:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
...
measuredWidth=...;
measuredHeight=...;
measuredWidth=resolveSize(measuredWidth,widthMeasureSpec);
measuredHeight=resolveSize(measuredHeight,heightMeasureSpec);
setMeasuredDimension(measuredWidth,measuredHeight);
}
? ? ? ?onMeasure()方法的两个参数 widthMeasureSpec和heightMeasureSpec是父View对子View的尺寸限制,子View在计算自己尺寸的时候,需要遵守这两个参数所包含的限制MeasureSpec。
理解MeasureSpec:
在 Android 中,View 的大小是由父容器和 View 自身的测量规格(MeasureSpec)共同决定的。
MeasureSpec 由大小和测量模式组成,测量模式有三种取值:
UNSPECIFIED(未指定):父容器对子 View 没有施加任何限制,子 View 可以任意大小。
EXACTLY(精确):父容器已经为子 View 精确指定了大小,子 View 应该匹配这个大小。
AT_MOST(至多):子 View 可以是任何大小,但不能超过父容器指定的大小。
MeasureSpec 是通过静态方法 MeasureSpec.makeMeasureSpec() 创建的,该方法接受两个参数:大小和测量模式。在自定义 View 或者自定义布局中,我们通常会使用 MeasureSpec 来测量子 View 的大小,并根据测量模式来决定子 View 的大小。
在自定义 View 中,我们通常会在 onMeasure() 方法中使用 MeasureSpec 来测量 View 的大小。在这个方法中,我们可以通过 MeasureSpec.getMode() 和 MeasureSpec.getSize() 方法来获取测量模式和大小,然后根据这些信息来确定 View 的最终大小。
解释resolveSize()这个方法:
//代码简化,不是源码
public static int resolveSize(int size, int measureSpec) {
? ? final int specMode = MeasureSpec.getMode(measureSpec);
? ? ? final int specSize = MeasureSpec.getSize(measureSpec);
? ? ? ? switch (specMode) {
? ? ? ? ? case MeasureSpec.AT_MOST:
? ? ? ? ? ? ? if (specSize < size) {
? ? ? ? ? ? ? ? ? result = specSize | MEASURED_STATE_TOO_SMALL;
? ? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? ? result = size;
? ? ? ? ? ? ? }
? ? ? ? ? ? ? break;
? ? ? ? ? case MeasureSpec.EXACTLY:
? ? ? ? ? ? ? result = specSize;
? ? ? ? ? ? ? break;
? ? ? ? ? case MeasureSpec.UNSPECIFIED:
? ? ? ? ? default:
? ? ? ? ? ? ? result = size;
? ? ? }
}
resolveSize()这个方法,父View传进来的尺寸限制measureSpec是由类型和尺寸值组成的,首先要调用MeasureSpec.getMode(measureSpec)方法和MeasureSpec.getSize(measureSpec)方法获取限制measureSpec的类型mode和size尺寸值。
限制的类型mode:
MeasureSpec.AT_MOST 限制上线
MeasureSpec.EXACTLY 限制固定尺寸
MeasureSpec.UNSPECIFIED 无限制
onMeasure()的重写,对于ViewGroup来说,包含三部分内容:
步骤:
理解LayoutParams
? ? ? ?在父View里调用子View的getLayoutParams()方法,可以获得一个LayoutParams对象,它包含了xml文件里的layout_打头的参数的对应值,其中它的width和height这两个属性就分别对应了layout_width和layout_height的值,并且是转换过了的值。
? @Override
? protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
?
? ? ? for(int i=0;i<getChildCount();i++){
? ? ? ? ? View childView=getChildAt(i);
? ? ? ? ? LayoutParams lp=childView.getLayoutParams();
? ? ? ? ? //lp.height ? lp.width
? ? ? }
? }
结合自己的可用空间来计算出对子View的宽度和高度的限制
可以根据layout_width和layout_height的值,分成三种情况:
第一种情况:固定值
不需要考虑可用空间的问题,直接用EXACTLY把子View尺寸限制为这个固定值就可以了。
第二种情况:match_parent
把子View的尺寸限制为固定值可用宽度或者高度
可用空间的判断方法:
根据自己的MeasureSpec中mode的不同:
1.EXACTLY/AT_MOST? ?可用空间:MeasureSpec中的size
2.UNSPECIFIED? ? ?可用空间:无限大
第三种情况:wrap_content
不能超过父View的边界的情况下,子View自己测量
public class SomeView extends ViewGroup {
...
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
...
for(int i=0;i<getChildCount();i++){
View childView=getChildAt(i);
LayoutParams lp=childView.getLayoutParams();
int selfwidthSpecMode=MeasureSpec.getMode(widthMeasureSpec);
int selfwidthSpecSize=MeasureSpec.getSize(widthMeasureSpec);
switch (lp.width){
case MATCH_PARENT:
if(selfwidthSpecMode==EXACTLY||selfwidthSpecMode==MeasureSpec.AT_MOST){
childWidthSpec=MeasureSpec.makeMeasureSpec(selfwidthSpecSize-usedWidth,EXACTLY);
}else{
childWidthSpec=MeasureSpec.makeMeasureSpec(0,MeasureSpec.UNSPECIFIED);
}
break;
case WRAP_CONTENT:
if(selfwidthSpecMode==EXACTLY||selfwidthSpecMode==MeasureSpec.AT_MOST){
childWidthSpec=MeasureSpec.makeMeasureSpec(selfwidthSpecSize-usedWidth,MeasureSpec.AT_MOST);
}else{
childWidthSpec=MeasureSpec.makeMeasureSpec(0,MeasureSpec.UNSPECIFIED);
}
break;
default:
childWidthSpec=MeasureSpec.makeMeasureSpec(lp.width, EXACTLY);
break;
}
}
}
}
关于保存子View位置的两点说明
1.不是所有的Layout都需要保存子View的位置(因为有的Layout可以在布局阶段实时推导出子View的位置,例如LinearLayout)
2.有时候对某些子View需要重复测量两次或多次才能得到正确的尺寸和位置
重写onLayout()来摆放子View
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
for(int i=0;i<getChildCount();i++){
View childView=getChildAt(i);
childView.layout(childLeft[i],childTop[i],childRight[i],childBottom[i]);
}
}
?