View的工作原理(一)——从ViewRoot和DecorView说起
前言
本文参考《Android开发艺术与探索》第四章内容及网上几篇博客,里面融入笔者的个人理解。希望能对大家理解View有所帮助。
基本概念介绍
介绍View的工作原理之前我们首先要理解DecorView和ViewRoot两个概念:
1、DecorView
DecorView是Windows中的View的最顶层View。我们可以根据下面一副图来认识它:
由这幅图我们可以看到 ,其实DecorView是一个FrameLayout,里面是一个垂直的线性布局,在线性布局中分上下两部分FrameLayout,上面一部分是TitleBar,下面是android.R.id.content,我们平常的setContentView就是将布局加载在android.R.id.content中。
2、ViewRoot
ViewRoot是连接WindowsManager和DecorView的桥梁对应于ViewRootImpl。
View的绘制流程就是从ViewRootImpl的performTraversala()方法开始的,包含三大流程:
1、Measure():[ 测量流程]
2、Layout():[布局流程]
3、Draw():[绘制流程]
这三大流程也就是View绘制的三大流程。我们可以通过下面两幅图来理解performTrarsala()方法;
什么是MeasureSpec
MeasureSpec 从名字上来看看起来是“测量规格“或是”测量说明书“。大致意思就是决定View的Measure过程。我们可以这样来理解:”MeasureSpec在很大程度上决定了一个View的尺寸规格,之所以这样说是很大程度上是因为这个过程还受父容器的影响,因为父容器影响View的MeasureSpec的创建过程。“
1、MeasureSpec
MeasureSpec代表一个32位int值,高2位代表SPecMode(测量模式),低30位代表SpecSize(在某种测量模式下的规格大小)。
SpecMode有三类如下所示:
UNSPECIFIED(未指定模式)
父容器不对View有任何限制,要多大给多大。
EXACTLY(确定模式)
父容器已检测出View所需要的精确大小,这时候View的最终大小就是SpecSize指定的值,它对应于LayoutParams中的match_parent和具体指定的数值这两种模式。
AT_MOST(最多模式)
父容器指定一个可用大小即SpecSize,View的代销不能大于这个值。对应于wrap_content。
2、MeasureSPec 和LayoutParams的对应关系
为什么要说MeasureSpec和LayoutParams对应昵?那是因为View在测量的时候会将我们设置的LayoutParams 在父容器的约束条件下 转换成MeasureSpec,然后再根据MeasureSpec来确定View测量后的宽和高。
有些读者会问:那么顶级View(DecorView)怎么转换昵?
对于顶级View:MeasureSpec有窗口尺寸和自身的LayoutParams来共同决定。
对于普通View:由父容器的MeasureSpec和自身的LayoutParams共同来决定。
通过下面一段代码我们来理解DecorView的创建MeasureSpec过程,desiredWindowWidth和desiredWindowHeight是屏幕尺寸:
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
host.measure(childWidthMeasureSpec, childHeightMeasureSpec);
我们来再看看getRootMeasureSpec方法:
private 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;
}
在android.view.ViewRootImpl 中可以看到其对应关系LayoutParams 中这三个值在内部有个对应关系,那就是
LayoutParams.MATCH_PARENT 对应 MeasureSpec.EXACTLY
.LayoutParams.WRAP_CONTENT对应 MeasureSpec.AT_MOST
默认值(也就是具体值) 对应 MeasureSpec.EXACTLY
也就是内部只有两种模式 EXACTLY 精确模式 和 AT_MOST 最大模式!
对于普通View它的measure是由ViewGroup传递而来,我们看一下ViewGroup的measureChildWidthMargins方法:
/**
* Ask one of the children of this view to measure itself, taking into
* account both the MeasureSpec requirements for this view and its padding
* and margins. The child must have MarginLayoutParams The heavy lifting is
* done in getChildMeasureSpec.
*
* @param child The child to measure
* @param parentWidthMeasureSpec The width requirements for this view
* @param widthUsed Extra space that has been used up by the parent
* horizontally (possibly by other children of the parent)
* @param parentHeightMeasureSpec The height requirements for this view
* @param heightUsed Extra space that has been used up by the parent
* vertically (possibly by other children of the parent)
*/
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 // 考虑上parent的padding和child的margin
+ widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
// 这里如果child是个ViewGroup类型,则实际会递归下去。。。
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
我们可以清楚的看到子View的MeasureSpec创建与父容器的MeasureSpec和子元素本身的LayoutParams有关,此外还和VIew的margin 和 padding 有关,我们来看下ViewGroup的getChildMeasureSpec:
/**
* Does the hard part of measureChildren: figuring out the MeasureSpec to
* pass to a particular child. This method figures out the right MeasureSpec
* for one dimension (height or width) of one child view.
*
* The goal is to combine information from our MeasureSpec with the
* LayoutParams of the child to get the best possible results. For example,
* if the this view knows its size (because its MeasureSpec has a mode of
* EXACTLY), and the child has indicated in its LayoutParams that it wants
* to be the same size as the parent, the parent should ask the child to
* layout given an exact size.
*
* @param spec The requirements for this view
* @param padding The padding of this view for the current dimension and
* margins, if applicable
* @param childDimension How big the child wants to be in the current
* dimension
* @return a MeasureSpec integer for the child
*/
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
// 这个方法是协商型的,最终结果既可能直接由spec(parent提供的),也可能由childDimension决定
// 所以我们知道了,一个View的大小不是简单的单方面决定的,而是通过一系列条件协商的结果,
// 有时会尊重parent的spec,有时会坚持自己的dimension要求
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) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY: // parent说child你应该是个确定的大小
if (childDimension >= 0) { // child正好设置了确定的大小
resultSize = childDimension; // 让child是那个确定的大小
resultMode = MeasureSpec.EXACTLY; // 设置mode为EXACTLY
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size; // 其他情况下都是parent spec中的大小,只是mode不同
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size; // 不能超过size
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST: // parent说child你应该最大是某个值。。。
if (childDimension >= 0) { // child指定确定值了,则听child的
// Child wants a specific size... so be it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// 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 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: // parent没对child的大小有啥要求
if (childDimension >= 0) { // child指定了确定的值,听child的
// 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);
}
从上述方法中我们不难理解, 子View的MeasureSpec创建与父容器的MeasureSpec和子元素本身的LayoutParams有关。
下面一张表格对MeasureSpec和LayoutParams对应做个总结:
普通View的MeasureSpec创建规则
作者:紫雾凌寒
来源:CSDN