您的位置:首页 > 移动开发 > Android开发

Android中View的MeasureSpec以及Measure的过程

2017-08-13 15:29 387 查看

一、了解View的MeasureSpec

MeasureSpec从字面上的意思来是测量说明书,实际上载View的Measure过程中,它的确起到了非常重要的作用。

MeasureSpec是一个32位的Integer值,它由两个部分组成分别是SpecMode和SpecSize,即测量模式和该模式下的尺寸。

通过makeMeasureSpec(SpecSize, SpecMode)可以得到MeasureSpec

通过getMode(MeasureSpec)和 getSize(MeasureSpec)解包可以获得该MeasureSpec对应的mode和size。

SpecMode有三个具体的模式:

1.UNSPECIFIED(对应获取不到View的LayoutParams的情况)

2.EXACTLY(对应View的LayoutParams为match_parent和设置了具体数值的情况)

3.AT_MOST(对应View的LayoutParams为wrap_content的情况)

二、DecorView的MeasureSpec

一般情况下来说,View的MeasureSpec是由父容器的约束和自身的LayoutParams共同决定的,但是DecorView是Android中最顶层的View它是没有父容器的,那么约束它的工作自然而然就交给了Window,它的MeasureSpec是通过getRootMeasureSpec()方法获取到的,源码如下:

/**
* @param windowSize: 即Window的大小
* @param rootDimension:即DecorView的LayoutParams
*/
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch(rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTILY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTILY);
break;
}
}


获取到DecorView的MeasureSpec后会执行performMeasure(childWidthMeasureSpec, childHeightMeasureSpce)该方法发起DecorView的measure过程

三、View的MeasureSpec

除了DecorView比较特殊之外,其他View的MeasureSpec都是通过其父容器构建好,传递到其onMeasure()方法中的。接下来就来看看View的MeasureSpec是如何在其父容器中构建的。

protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams)child.getLayoutParams();
//getChildMe
4000
asureSpec()传入三个参数
//第一个是该ViewGroup的MeasureSpec
//第二个是该ViewGroup的childView不可用的空间
//第三个为childView的LayoutParams
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin + widthUsed, lp.width);
final int childHeighteasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin + widthUsed, lp.width);
child.measure(childWidthMeasureSpec, childHeighteasureSpec);
}


由上面的代码可知,View的MeasureSpec是在它的父容器中measureChildWithMargins()方法通过getChildMeasureSpec()获取到的

getChildMeasureSpec()该方法会根据当前ViewGroup的MeasureSpec的SpecMode与 View的LayoutParams共同决定子View的SpecMode和SpecSize,最终通过makeMeasureSpec()将其打包成子View的MeasureSpec。

View的MeasureSpec生成好之后会调用View的measure方法发起子View的Measure过程

四、View的Measure过程

View的measure方法会去调用其自身的onMeasure方法,下面看看原生View的onMeasure源码:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//设置View测量的尺寸
//getDefaultSize()为获取系统默认的尺寸,其两个参数分别为Size,和MeasureSpec
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}


getSuggestedMinimumXXX()类型的方法是判断当前View是否设置了背景图,若设置了背景图则返回设置的尺寸和背景图中的最大值

接下来分析getDefaultSize方法,源码如下:

public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);

switch(specMode) {
//如果测量模式为不确定时,result即为getSuggestedMinimumXXX()
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
//如果测量模式为精确值时,result即为measureSpec重的specSize
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}


由上述源码可知我们原生View对于AT_MOST类型的Mode是没有做任何处理的,也没有break,所以会继续往下执行也就是说wrap_content的效果与match_parent是一致的,所以我们想要完成wrap_cotent的效果,需要重写View的onMeasure方法自己去实现(TextView、ImageView均做了相应的处理)。

五、ViewGroup的Measure过程

ViewGroup是一个抽象类继承自View,它没有重写View的onMeasure方法,将Measure的过程交给具体的实现者,但是它提供了measureChildXXX的方法,该方法会循环遍历ViewGroup中的childView,获取其MeasureSpec后并调用childView的measure方法, 当所有的childView测量结束后, 再具体的执行其Measure自身的操作
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: