众所周知,android为咱们提供大量的基础控件,这些控件完成基本功能是没有问题的,也比较全面,可是对于一些比较精致的产品,不只仅是基础功能实现就OK,它们每每要很炫的效果,这就须要自定义view了,好了很少说了,直接开始主题,View的绘制分为measure、layout、draw,其中测量是最复杂的,咱们单独来说,布局和绘制将在下一篇文章去讲解。android
在正式讲解View的工做原理以前,咱们先了解一下ViewRoot,ViewRoot的实现类是ViewRootImpl,它是链接WindowManager和DecorView的纽带,View的三大流程都是经过ViewRoot来完成的,它是在ActivityThread中被初始化的。View的绘制流程是从ViewRoot的performTraversals开始的,经历三个步骤后最终呈如今界面的view,大体以下: 面试
下面来简单讲一下DecorView,以下图:bash
能够说,这个概念是贯穿了整个View绘制的全部流程,是的,表面上看它就是一个尺寸规格,也就是决定View的大小,它绝大部分均可以决定View的大小,固然也不是它一我的说了算,毕竟有些ViewGroup的LayoutParams也对子view的大小有影响。ide
上面提到过了,View的大小是由MeasureSpec来决定的,咱们通常会给view设置LayoutParams参数,这个params参数会在父容器的MeasureSpec约束的状况下转换为对应的MeasureSpec,这个Spec会最终肯定view的测量大小,也就是说view的大小是由父容器的MeasureSpec和view的LayoutParams共同决定的,MeasureSpec一旦肯定后,onMeasure就能够肯定view的测量宽高了。布局
View的measure过程是由ViewGroup的measure传递来的,这里看一下ViewGroup的measureChildWithMargins方法,post
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);
}
复制代码
能够看到,在测量子view的时候会去获取子view的MeasureSpec,这里详细看一下getChildMeasureSpec方法ui
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) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
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; resultMode = MeasureSpec.AT_MOST; } break; // Parent has imposed a maximum size on us case MeasureSpec.AT_MOST: if (childDimension >= 0) { // 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:
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;
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
复制代码
这个方法有点长,可是很简单,它就是根据父容器的MeasureSpec和子view的LayoutParams来肯定子view的MeasureSpec。咱们把上述规则总结到一张表里面,方便记忆: spa
view的三大过程当中,measure是最复杂的,由于每每要肯定一个view的大小,要经历好屡次测量才能ok。measure过程要分状况来看,View和ViewGroup,由于ViewGroup不只仅要测量本身还要测量子元素,一层一层传递下去。code
View里面的measure是final方法,这就意味着该类不容许被继承,measure里面调用了onMeasure方法,也就是说measure的工做就是在onMeasure里面完成的,看看o n M e asure方法:orm
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;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
复制代码
getDefaultsize就是返回测量后的大小,这里注意是测量后的大小,由于view的大小最终肯定是在layout后,有时候layout也会对view的大小形成影响,不过绝大部分getDefaultsize就是最终view的大小。
注意的点: 从getDefaultsize方法能够看到,view的大小由specSize来决定,因此,直接继承View的自定义控件须要重写onMeasure方法而且设置wrap_content时的自身大小,不然在布局中的wrap_content和match_parent就没有什么区别了,从上面的表格也能够清晰的看到,这种状况是咱们不但愿见解的,怎么解决呢?很简单,咱们设置一个默认值就能够了
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//若是view在布局中使用wrap_content ,这时候就是AT_MOST,咱们须要在onmeasure里面作特殊处理,不然和match_parent就没有区别了
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(500, 300);
}else if (heightSpecMode == MeasureSpec.AT_MOST){
setMeasuredDimension(widthSpecSize, 300);
}else if (widthSpecMode == MeasureSpec.AT_MOST){
setMeasuredDimension(500, heightSpecSize);
}
}
复制代码
具体的默认值是多少,咱们根据本身的状况来定。
对于ViewGroup的measure过程,它会更加复杂一点,由于它不只要measure本身,还要measure子view,ViewGroup没有重写onMeasure方法,它提供了另外一种方法measureChildren
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < size; ++i) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
复制代码
上面的方法很明了,就是对每个子元素进行measure,咱们看看measureChild:
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);
}
复制代码
这也太简单了,就是获取子元素的measure spec值,而后调用view的measure操做,这个和单独的view就没啥区别了,就这样一直迭代下去,直到单个的view测量结束。这是测量子元素的过程,那么ViewGroup怎么测量本身的呢。
其实ViewGroup并无定义其测量的具体过程,由于它是一个抽象类,其测量过程onMeasure交给了其子类去实现了,好比LinearLayout类就有本身专门的onMeasure方法,这也是符合逻辑的,由于没个Layout都有本身的特性,咱们不可能在ViewGroup统一去处理。 咱们以LinearLayout为例,看以下代码:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
复制代码
看垂直方向的,水平方向的相似:
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
mTotalLength = 0;
...
final int count = getVirtualChildCount();
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
boolean matchWidth = false;
boolean skippedMeasure = false;
final int baselineChildIndex = mBaselineAlignedChildIndex;
final boolean useLargestChild = mUseLargestChild;
int largestChildHeight = Integer.MIN_VALUE;
int consumedExcessSpace = 0;
int nonSkippedChildCount = 0;
// See how tall everyone is. Also remember max width.
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
if (child == null) {
mTotalLength += measureNullChild(i);
continue;
}
if (child.getVisibility() == View.GONE) {
i += getChildrenSkipCount(child, i);
continue;
}
nonSkippedChildCount++;
if (hasDividerBeforeChildAt(i)) {
mTotalLength += mDividerHeight;
}
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
totalWeight += lp.weight;
final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;
if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {
// Optimization: don't bother measuring children who are only // laid out using excess space. These views will get measured // later if we have space to distribute. final int totalLength = mTotalLength; mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin); skippedMeasure = true; } else { if (useExcessSpace) { final int usedHeight = totalWeight == 0 ? mTotalLength : 0; measureChildBeforeLayout(child, i, widthMeasureSpec, 0, heightMeasureSpec, usedHeight); final int childHeight = child.getMeasuredHeight(); if (useExcessSpace) { lp.height = 0; consumedExcessSpace += childHeight; } final int totalLength = mTotalLength; mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin + lp.bottomMargin + getNextLocationOffset(child)); if (useLargestChild) { largestChildHeight = Math.max(childHeight, largestChildHeight); } } final int margin = lp.leftMargin + lp.rightMargin; final int measuredWidth = child.getMeasuredWidth() + margin; maxWidth = Math.max(maxWidth, measuredWidth); childState = combineMeasuredStates(childState, child.getMeasuredState()); allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT; if (lp.weight > 0) { /* * Widths of weighted Views are bogus if we end up * remeasuring, so keep them separate. */ weightedMaxWidth = Math.max(weightedMaxWidth, matchWidthLocally ? margin : measuredWidth); } else { alternativeMaxWidth = Math.max(alternativeMaxWidth, matchWidthLocally ? margin : measuredWidth); } i += getChildrenSkipCount(child, i); } if (nonSkippedChildCount > 0 && hasDividerBeforeChildAt(count)) { mTotalLength += mDividerHeight; } if (!allFillParent && widthMode != MeasureSpec.EXACTLY) { maxWidth = alternativeMaxWidth; } ... maxWidth += mPaddingLeft + mPaddingRight; // Check against our minimum width maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth()); setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), heightSizeAndState); if (matchWidth) { forceUniformWidth(count, heightMeasureSpec); } } 复制代码
这个方法很是长,系统会遍历每一个子元素,而且调用子元素的measureChildBeforeLayout方法:
void measureChildBeforeLayout(View child, int childIndex,
int widthMeasureSpec, int totalWidth, int heightMeasureSpec,
int totalHeight) {
measureChildWithMargins(child, widthMeasureSpec, totalWidth,
heightMeasureSpec, totalHeight);
}
复制代码
这个方法内部又在执行measure子元素的操做,当子元素所有测量完毕后,Linearlayout才会去测量本身的大小。
注意的点 : 测量的过程是从父类开始分发,递归的测量子元素,最后再测量父类。layout的过程是刚好相反的,咱们后面再讲。
View的measure通常是很复杂的,某些状况下得屡次测量,因此为了保险起见,咱们应该在layout结束后再去获取View的宽和高。在实际需求中,好比,在activity中,你怎么获取某个view的width和height呢?有的人确定会说,很简答啊,直接在oncreat中去调用getWidth和getHeight,这确定是不行的,大家能够去试试,这里获取到的极有多是空值,这是由于View的绘制和Activity生命周期不存在同步的关系,没法保证在哪个周期View的测量工做已经完成了,因此不靠谱。这里简单提一下几种常见的解决方案,可是不展开讲解了:
1.对于measure过程,咱们只要了解view和ViewGropup的大体流程就能够了,尤为注意 在自定义view 的时候要重写onMeasure方法,而且给wrap_content布局赋默认值。
2.获取某个view宽和高的时机,这个很重要,在面试中常常被考察到。