最近看开源项目时,往往对一些以前熟悉(现在模糊)的知识,导致无法继续思考.
LayoutParams是什么?
LayoutParams可以理解为是子控制在父容器中的布局信息对象 ,它封装了子控件摆放的位置、高、宽等信息。如:屏幕一块区域被子控件使用,将一个子控件添加到一个父容器中,我们需要告诉父容器布局的摆放位置,那么子控件可以通过LayoutParams封装信息通知父布局 。
如:上图launcher界面 ,每个App图标都占据一个位置,也就是App图标都有一个位置的信息,这些位置信息及图标大小就是LayoutParams。
- LayoutParams只是封装了宽高,宽和高可以设置三种值:
- 固定的值;
- MATCH_PARENT,填满(和父容器一样大小);
- WRAP_CONTENT,能裹住组件就好。
可以简单理解:LayoutParams就是子布局在父布局的一个信息位置对象。
俩个布局演示:
relativeLayout = (RelativeLayout) findViewById(R.id.main);
Button button = new Button(this);
button.setText("button");
button.setTextColor(Color.WHITE);
button.setBackgroundColor(Color.BLACK);
FrameLayout.LayoutParams lytp = new FrameLayout.LayoutParams(200, 200);
lytp.setMargins(200,200,0,0);
button.setLayoutParams(lytp);
relativeLayout.addView(button);
relativeLayout = (RelativeLayout) findViewById(R.id.main);
RelativeLayout relative = new RelativeLayout(this);
relative.setBackgroundColor(Color.WHITE);
RelativeLayout.LayoutParams lp=new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CO NTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
lp.addRule(RelativeLayout.CENTER_IN_PARENT);
relative.setLayoutParams(lp);
relativeLayout.addView(relative);
这个ViewGroup怎么没有,自己想想怎么回事?
View的MeasureSpec测量过程
在自定义View时,我们需要根据需求来控制View的尺寸。这时候我们需要重写onMeasure进行定制。而如何定制与MeasureSpec有很大关系,我们接下来进行分析。
我们利用debug来查看onMeasure的调用过程:
我们主要查看measureChildWithMargins方法
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
//在测量中会根据View的Layoutparams与父容器所施加的规
//则转化成对应子布局的MeasureSpec。
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);
//然后根据获取的子布局的 WidthMeasureSpec 和 HeightMeasureSpec
//对子 view 进行测量,这里会最终调用view的onMeasure进行测量
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
上面可以看出WuXiaoView的LayoutParams 与父布局有紧密的关系。
通过getChildMeasureSpec()源码查看他们的关系:
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) {
// 父容器精确模式.
case MeasureSpec.EXACTLY:
//子布局参数是固定值,比如"layout_width" = "25px"
//那么子布局测量模式肯定是EXACTLY,测量的宽就是25px,
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
//子控件的布局参数是"match_parent",也就是想占满父容器
//而此时父容器是精确模式,也就是能确定自己的尺寸了,那子控件也能确定自己大小了
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
//子控件的布局参数是"wrap_content",也可以重写根据自己的逻辑决定自己大小(大小肯定不能大于父容器的大小)
//测量模式就是AT_MOST,测量大小就是父容器的size
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// 父控件最大模式(父控件还不知道自己的尺寸)
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
//子控件能确定自己大小,尽管父容器自己还不知道自己大小
//所以优先设置子布局
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
//子控件想要和父容器一样大,但父容器也不明确自己大小
//那么子控件也是AT_MOST,并且最大值不会超过父容器大小
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
//子控件根据自己逻辑决定大小(默认最大值不会超过父容器大小)
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 = 0;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
里面很多地方用到了MeasureSpec,接下来看看它的真面目。
MeasureSpec是什么?
sdk解释:MeasureSpc类封装了父View传递给子View的布局(layout)要求。每个MeasureSpc实例代表宽度或者高度。
MeasureSpc的源码很简洁:
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
public static final int EXACTLY = 1 << MODE_SHIFT;
public static final int AT_MOST = 2 << MODE_SHIFT;
public static int makeMeasureSpec(int size, int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
........
}
包含了View的三种测量模式:
测量模式为int类型(32bit),其中高2位用来封装MeasureMode。
- UNSPECIFIED - 00000000 00000000 00000000 00000000 父容器不对子布局有任何限制,要多大给多大(如: scrollview)
- EXACTLY - 01000000 00000000 00000000 00000000 父容器已经测量出子布局大小。
- AT_MOST - 10000000 00000000 00000000 00000000 父窗口限定了一个最大值给子子布局。
低30位用来封装size.
应用:
当父容器为wrap_content,子布局也是wrap_content的情况默认宽高为600x600。这时候需要重写MeasureSpec进行判断了,从上面我们分析,我们知道如果不自己设置子布局为剩余父容器大小
这不是我想要的。
应用布局
WuXiaoView 布局部分代码
public class WuXiaoView extends View {
...
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthspecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthspecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightspecMode = MeasureSpec.getMode(heightMeasureSpec);
int heightspecSize = MeasureSpec.getSize(heightMeasureSpec);
switch (widthspecMode) {
case MeasureSpec.EXACTLY:
width =widthspecSize;
break;
case MeasureSpec.AT_MOST:
width =600;
break;
}
switch (heightspecMode) {
case MeasureSpec.EXACTLY:
height =heightspecSize;
break;
case MeasureSpec.AT_MOST:
height =600;
break;
}
setMeasuredDimension(width,height);
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawText("wuxiao",width/2,height/2,paint);
}
}