您的位置:首页 > 其它

View的工作原理之MeasureSpec测量规格

2016-05-16 07:30 337 查看

View的工作原理之MeasureSpec测量规格

1.ViewRoot和DecorView

ViewRoot对应于ViewRootImpl类  它是连接WindowManager和DecorView的桥梁
在Activity对象被创建的时候 会将DecorView添加到Window中  同时会创建ViewRootImpl  之后会将ViewRootImpl和DecorView相关联


root = new ViewRootImpl(view.getContext(),display);
root.setView(view,wparams,panelParebtView);


View的绘制的是从ViewRoot的performTraversals方法 开始的

Created with Raphaël 2.1.0performTraversals开始执行performMeasure --->measure --->onMeasureperformLayout --->layout--->onlayoutperformDraw --->draw--->ondraw完成

在measure方法完成以后  我们就可以获取  getMeasuredWidth和getMeasuredHeight  这是两个测量的值 并不是最终的值  但是一般来说  最终值都等于这两个值
在Layout方法之后  控件的四个顶点的位置就确定了,top left bottom right  并且可以通过getWidth 和getHeight 获取最终的高度和宽度了
最后draw方法把控件画出来即完成了View的绘制


2.MeasureSpec的理解

通过源码可以发现 MeasureSpec参与了View的measure过程 它决定了View的尺寸与规则

首先我们先看一下MeasureSpec的内部组成

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);
}


SpecMode有三种 :

UNSPECIFIED:
父容器不对View有任何限制 要多大有多大  这种通常用于系统内部,表示一种测量状态

EXACTLY:
父布局已经检测出了view的大小 并且View的大小就是 设置的View的大小  对应 Match_parent 和 规定大小的情况

AT_MOST:
父布局指定了一个可用大小 specSize  控件的最大大小不能超过这个  具体是什么未知,对应wrap_content这种情况


当前要测量的View的大小是由它的父布局和它自身的LayoutParam参数决定的 为什么这么说呢 你可能会认为View的大小当然是由它自身设置的大小或者自身设置的模式决定的怎么会和父布局相关呢 下面我来由源码分析一下:

首先我们要分两种情况,一种情况是顶级View的测量  另一种是非顶级View的测量 为什么要分这两种情况,因为顶级View的父布局就相当于Window 而非顶级View的父布局则不是


1.对于顶级View的测量 看下面的代码:

childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);


这是顶级View的父布局的代码 它会执行getRootMeasureSpec方法 :


private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {

case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// Window wants to be an exact size. Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}


而desiredWindowHeight和Width就是Window的高度和宽度
getRootMeasureSpec(int windowSize, int rootDimension)的两个参数的意思也很明确  第一个参数是window的大小  第二个参数 传入的是顶级View的lp中的大小 也就是设置的大小  这样就进入了switch的逻辑
如果设置的是Match_parent那么测量规格MeasureSpec就是窗口的大小
如果设置的是Wrap_content那么测量规格就是不确定但最大不超过窗口大小
如果设置的是个确定的值 那么就是按照这个确定的值设置
这样对于顶级View的测量就确定了


2.对于非顶级View的测量:

对于普通的View 测量是由父布局 也就是ViewGroup的measureChildWithMatgins方法传过来的 下面我们来看看这部分代码:


protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
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);

child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}


这部分代码先调用getChildMeasureSpec方法获取了测量规格 然后进行child的测量 而 getChildMeasureSpec方法就是关键了


public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
//注意这个函数的三个参数含义  spec是父布局的测量规格  padding是父布局的padding和margin  childDimension是子布局的设置的大小 由lp.height 和lp.width获得
//先获取父布局传过来的模式和父布局的大小
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
//size表示父布局可以填充的区域  由父布局的大小减去padding 如果 父布局设置的wrap_content 或者 match_parent 那么 就是 0   因为用了max函数
int size = Math.max(0, specSize - padding);

int resultSize = 0;  //child最终的测量大小
int resultMode = 0;  //child 最终的测量规格

switch (specMode) {  //下面是根据父布局的测量规格来获取child的MeasureSpec
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:  //如果父布局是 EXACTLY规则 代表父布局大小已经确定了  子布局也可以确定了
if (childDimension >= 0) {  //如果child设置了大小  那么最终的大小就是设置的大小  模式就是EXACTLY
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {//child设置了填充父布局  child最终的大小自然就是父布局的大小  模式是EXACTLY
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {//child设置乐适应内容  那么模式设置成AT_MOST 最终大小不能超过父布局size
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;

// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST: //如果父布局是AT_MOST模式代表父布局是适应内容的 大小还没有确定  因此即使child设置了
if (childDimension >= 0) {//如果设置了大小  那么就是设置的大小 模式为EXACTLY
// Child wants a specific size... so be it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {//如果设置了填充父布局 由于父布局是适应内容的  大小还不确定 所以child不能确定大小 只能是最大不超过父布局at_most模式的最大值
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {//如果child设置了适应内容 那么和上面那个一样  最大不超过父布局的大小
// Child wants to determine its own size. It can't be
// bigger than us.
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 = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
//最后由最终得到的大小和模式返回 由子布局测量
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}


protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {//测量孩子的源码
final LayoutParams lp = child.getLayoutParams();

final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);

child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}


经过对上面源码的分析 可以了解清楚 View的MeasureSpec的使用规则了

在这里再总结一下一个ViewGroup的测量过程:

首先它如果是一个顶级View  它会根据Window的大小先测量自身的大小 之后遍历孩子 调用measureChild类似的方法来测量孩子,得到孩子的测量规格后 调用child的measure方法  然后回调child的onMeasure方法  根据我们重写的内容进行设置测量的结果  setMeasuredDimension 这个方法是一个重要的方法  它设置的结构就是最后测量完成的结果  如果你在这随便设置一个值 那它测量的结果就是你设置的那个值  下面代码是View的onMeasure的实现


protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}


还有一个问题 我们发现在ViewGroup中 没发现 OnMeasure方法 这是因为这个方法 通常都是根据ViewGroup扩展的控件 重写这个方法的 所以没有

到这里对 MeasureSpec的讲解差不多结束了
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: