昨天爬了一天的山~,我的脚已经崩溃了,嘻嘻不过还是挺开心的,今天我们要总结下View的measure(测量)ViewGroup的measure
我们平常继承View 和ViewGroup 这2个方法呢,都是会有一个测量的步骤的,如果只是一个View那么measure完了就完了,如果是ViewGroup不仅自己要测量下,自己的子View也要测量下。
View里面的measure
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
boolean optical = isLayoutModeOptical(this);
.....省略
if (forceLayout || needsLayout) {
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
resolveRtlPropertiesIfNeeded();
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
//我们直接看这里,因为这里是设置布局大小的地方
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
long value = mMeasureCache.valueAt(cacheIndex);
// Casting a long to int drops the high 32 bits, no mask needed
setMeasuredDimensionRaw((int) (value >> 32), (int) value);
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
.....省略
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
//setMeasuredDimension 方法直接设置宽高了
}
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;
//不管你是wrap还是match最后的结果由specSize决定
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
UNSPECIFIED测量模式我们先不理它,其它2种的测量模式最后的结果都是specSize,就是View测量后的大小
我们这里面还有一个getSuggestedMinimumWidth()和getSuggestedMinimumHeight()2个方法,我们看其中一个
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
//如果backgroud(背景)==null,我们就取mMinWidth,否则我们就去后面2个比较的那个最大值
//这里的mMinWidth==android:minWidth,当然如果我们没有去指定的话,mMinWidth=0了
我们点击mBackground.getMinimumWidth()这个方法看
public int getMinimumWidth() {
final int intrinsicWidth = getIntrinsicWidth();
return intrinsicWidth > 0 ? intrinsicWidth : 0;
}
现在我们知道了一个View不管你设置wrap还是match,最后的结果都是由
specSize 它决定,现在有一个问题来了,当我们自定义View的时候,我们平常写宽度/高度为wrap,我们会在脑海中想象他是个包裹的状态,但是当我们真正去实验的时候,发现他却是全屏的状态(match),这个就让我们很诧异,很疑惑?。
现在我们目前只知道2点
1、我们不管是wrap还是match最后的结果都是由specSize决定你的大小(代码可以看出来)
2、Linlayout在绘制布局的时候会调用measureChildWithMargins方法,在这个方法里面会调用child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
当然第二点我们后面再讲到
如果你看过我对MeasureSpece的总结的话你应该就知道了
我们可以明白这一点,Linlayout(父布局)决定了子布局的大小,本文中specSize的大小由父容器来给你决定,他决定你你有多大的使用空间(父容器剩余的空间),我们可以通过上面的图片知道,如果我们的 childLayoutParams的参数是wrap_content那么我们不管父容器是wrap还是match,子容器的测量模式都是AT_MOST,但是它的宽高大小都是parentSize的大小,也就是父容器的大小(你要明白这一点,你需要看我前面的学习总结)
下面这个例子很好的展示了当子容器是wrap的时候,沾满全屏的情况
现在大家看到的这个效果是一个Linlayout,嵌套一个自定义的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">
<scanning.mobile.com.viewstudy.viewStu.st
android:background="@color/colorAccent"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
但是我嵌套里面的宽/高都是wrap_content,但是我们却看到的是沾满全屏了
这个是为啥?我们可以采用刚刚我们学到的知识来解释,
我们拿着我们刚刚参考那张表格来看
首先父容器是match_parent,那么对应的测量模式是:EXACTLY(精确模式),子容器是wrap_content,那么它对应的模式就是AT_MOST
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);//父容器的大小
int size = Math.max(0, specSize - padding);//获取剩下的空间
else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
我们知道我们最后的size就是Math.max(0, specSize - padding);//获取剩下的空间,
所以我们就是父容器的大小了,那么问题了来了,我们要如何来解决这样的问题?
public class st extends View{
private static final String TAG = st.class.getSimpleName();
public st(Context context) {
super(context);
}
public st(Context context, AttributeSet attrs) {
super(context, attrs);
}
public st(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
int height=200;
int width = 200;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthSpecMode==MeasureSpec.AT_MOST&&heightSpecMode==MeasureSpec.AT_MOST){//他会进入这里,所以我们在这里直接来解决
setMeasuredDimension(width,height);
}else if (widthSpecMode==MeasureSpec.AT_MOST){
setMeasuredDimension(width,heightSpecSize);
}else if (heightSpecMode == MeasureSpec.AT_MOST){
setMeasuredDimension(height,widthSpecSize);
}
}
}
获取它的测量模式,根据测量模式来修改
学习资源书籍:《Android 开发艺术探索》