ActivityThread中,首先建立Activity,而后经过attach方法初始化对应的mWindow,而后将顶级视图DecorView添加到Windows中,并建立ViewRootImpl对象,这个对象就是沟通WindowManager和DecorView之间的桥梁,也是View绘制的开始。android
View的绘制流程首先开始于ViewRootImpl的performTraversals()方法。依次通过三大过程,measure、layout、draw,performTraversals会依次调用performMeasure、performLayout、performDraw方法。其中measure用来对View进行测量,layout来肯定子元素在父元素中的位置即真实宽高以及四个顶点位置,draw负责将View绘制出来。canvas
说的接地气一点,measure就是给个建议值,View多大合适;layout就是去放View的外框,放在哪里,具体多大;draw就是去画View里面的内容。ide
measure过程获得的测量宽高能够经过getMeasureWidth和getMeasureHeight获得,其值不必定就是实际宽高,实际宽高是layout以后,能够经过getWidth和getHeight得到。函数
测量过程须要提到一个类,叫MeasureSpec,“测量规格”。MeasureSpec是View定义的一个内部类。MeasureSpec表明一个32位的int,高两位表明SpecMode,测量模式,第30位表明SpecSize,在某种测量模式下的规格大小。布局
MeasureSpec提供打包和解包的方法,能够将一组SpecMode和SpecSize经过makeMeasureSpec方法打包成MeasureSpec,也能够将一个MeasureSpec经过getMode和getSize进行解包得到对应的值。post
SpecMode测量模式包含三种,含义以下:spa
SpecMode值 | 含义 |
---|---|
UNSPECIFIED | 父容器没有对View有任何限制,要多大给多大 |
EXACTLY | 父容器能获得View的精确的值,这时候View的测量大小就是SpecSize的值,对应于View的LayoutParams中为match_parent或具体的值的状况。 |
AT_MOST | 父容器指定了一个可用大小SpecSize,View的大小不定,可是不能大于这个值,这个对应于View的LayoutParams的wrap_content。 |
系统须要经过MeasureSpec对View进行测量。View的MeasureSpec须要由父容器的MeasureSpec和View的View的layoutParams一块儿决定,而后根据View的MeasureSpec肯定View的宽和高。线程
因为DecorView是顶级视图,因此它的测量方法比较特殊,具体下面一一看下DecorView和普通View的MeasureSpec的计算。code
(1)DecorView的MeasureSpec值计算,DecorView是最早被测量的,能够从ViewRootImpl的performMeasure方法看出。orm
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) { Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure"); try { mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } }
这里的mView就是DecorView。往前搜传入的两个MeasureSpec的值。
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width); childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height); performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
能够看见调用了getRootMeasureSpec方法,传入了两个值,第一个值是屏幕尺寸,第二个值lp是LayoutParams 长宽的参数。因此,DecorView的MeasureSpec的值是由屏幕尺寸和它的LayoutParams 决定的。接下来看具体的关系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; }
能够看见若是是MATCH_PARENT的话,就是精确的模式,大小就是窗口的大小,若是是WRAP_CONTENT,就是AT_MOST模式,即大小不定,可是最大为窗口大小。
(2)普通View的MeasureSpec值计算。
前面说到DecorView首先被测量,调用了mView.measure(childWidthMeasureSpec, childHeightMeasureSpec),因为DecorView继承自FrameLayout继承自ViewGroup继承自View,能够看见View里面的measure方法是一个final方法,即不可被重写。
public final void measure(int widthMeasureSpec, int heightMeasureSpec)
这个方法实际上是去调用了onMeasure方法,这个方法是各个子类能够重写的。
跟踪这个方法,能够看见在View的measure是由ViewGroup传递过来的,具体是在ViewGroup里面的measureChildWithMargins方法。
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的LayoutParams,而后调用getChildMeasureSpec方法获取View的MeasureSpec,再调用View的measure进行测量,测量后面说,先说View的MeasureSpec的计算。看getChildMeasureSpec方法。
先看传入的三个参数:
看下具体的getChildMeasureSpec方法。
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); }
一句句看下来,首先,获取父容器的specMode和specSize,而后计算size = Math.max(0, specSize - padding),这里size就是说是父容器留给View的最大长度,若是specSize < padding,说明没有空间给View了,因此是0,不然能给View的剩余空间就是specSize - padding,这个比较好理解。
而后判断父容器specMode。根据父容器的specMode和View的LayoutParams值(match_parent、具体值、wrap_content)来决定。具体的逻辑很是简单,代码容易看懂。
总结成以下表:
View的LayoutParams\父容器的测量模式specMode | EXACTLY | AT_MOST | UNSPECIFIED |
---|---|---|---|
具体的值 | EXACTLY childsize | EXACTLY childsize | EXACTLY childsize |
match_parent | EXACTLY parentsize | AT_MOST parentsize | UNSPECIFIED 0 |
wrap_content | AT_MOST parentsize | AT_MOST parentsize | UNSPECIFIED 0 |
这里的parentsize就是父容器可用剩余空间。
首先,View的measure过程是由measure方法完成,看下View的measure方法。能够看见是个final方法,即不可被重写。
public final void measure(int widthMeasureSpec, int heightMeasureSpec)
measure方法会调用onMeasure方法,看onMeasure方法的实现。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }
setMeasuredDimension即设置View的测量宽高的值。计算方法是调用getDefaultSize,传入两个参数,先看这两个参数。
第二个参数是MeasureSpec,即前面说的,经过父MeasureSpec和View的LayoutParams以及父容器已经占用的空间进行计算获得。
看下第一个参数getSuggestedMinimumWidth()。
protected int getSuggestedMinimumWidth() { return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth()); }
思路比较简单,若是没有背景,则直接返回mMinWidth,mMinWidth即View的android:minWidth的设置值,默认为0;若是有背景,则返回max(mMinWidth, mBackground.getMinimumWidth(),mBackground.getMinimumWidth()为背景的尺寸,默认也是0。
如今看下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; }
思路也是比较简单的,若是测量模式是UNSPECIFIED,返回第一个参数,若是是AT_MOST和EXACTLY,直接返回View的MeasureSpec的规格大小。
从上面的分析能够发现,直接继承View的时候,须要重写onMeasure方法,并设置wrap_content时候的大小,不然会和match_parent的时候效果一直。好比当View设置为wrap_content的时候,此时View的MeasureSpec为AT_MOST,parentsize。当View设置为match_parent的时候,其测量值最后的结果也为parentsize。
ViewGroup除了完成本身的measure过程外,还会去遍历全部的子元素的measure方法,各个子元素再去递归这个过程。ViewGroup是个抽象类,其onMeasure方法是个抽象方法,须要子类去实现它,由于不一样的ViewGroup有不同的布局特性,因此致使他们的测量细节不同。
ViewGroup提供一个遍历的方法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); } } }
遍历全部的View,若是可见的话调用measureChild进行测量。getChildMeasureSpec的实现前面已经讲过了。获得以后调用子元素的measure方法。
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的测量和Activity的各个生命周期没有关系,因此在什么生命周期里面都有可能获得的measure测量值为0,即还没测量结束。从源码能够看见performTraversals是另开一个线程执行的。
final class TraversalRunnable implements Runnable { @Override public void run() { doTraversal(); } }
若是保证能获取到呢?有几个方法:
(1)为View添加onWindowFocusChanged监听,这个监听会在每次Activity的窗口得到和失去焦点的时候被调用,而View绘制成功的时候也是一次得到焦点的过程,因此也会调用。
public void onWindowFocusChanged(boolean hasWindowFocus) { super.onWindowFocusChanged(hasWindowFocus); if (hasWindowFocus) { int height = view.getMeasureHeight(); int width = view.getMeasureWidth(); } }
(2)view.post
经过post将一个runnable投递到消息队列尾部,而后调用的时候,说明View已经初始化好了。
view.post(new Runnable() { @Override public void run() { int height = view.getMeasureHeight(); int width = view.getMeasureWidth(); } });
(3)view.measure(int widthMeasureSpec, int heightMeasureSpec)
这个是最直接的方法,直接触发计算。这个通常是用在具体的值上,由于parentsize不知道。
好比宽高都是100px的。
int widthMeasureSpec = MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY); int heightMeasureSpec = MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY); view.measure(widthMeasureSpec, heightMeasureSpec);
layout的做用是ViewGroup用来肯定子元素的位置。首先调用setFrame初始化View的四个顶点,接着调用onLayout方法。
public void layout(int l, int t, int r, int b) { if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) { onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec); mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; } int oldL = mLeft; int oldT = mTop; int oldB = mBottom; int oldR = mRight; boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); ... }
onLayout因为不一样的子类实现细节都不同,因此onLayout在View和ViewGroup中都没有具体实现,都在子类中实现。以LinearLayout为例。
@Override protected void onLayout(boolean changed, int l, int t, int r, int b) { if (mOrientation == VERTICAL) { layoutVertical(l, t, r, b); } else { layoutHorizontal(l, t, r, b); } }
其中layoutVertical关键代码以下。
for (int i = 0; i < count; i++) { final View child = getVirtualChildAt(i); if (child == null) { childTop += measureNullChild(i); } else if (child.getVisibility() != GONE) { final int childWidth = child.getMeasuredWidth(); final int childHeight = child.getMeasuredHeight(); final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams(); int gravity = lp.gravity; if (gravity < 0) { gravity = minorGravity; } final int layoutDirection = getLayoutDirection(); final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection); switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { case Gravity.CENTER_HORIZONTAL: childLeft = paddingLeft + ((childSpace - childWidth) / 2) + lp.leftMargin - lp.rightMargin; break; case Gravity.RIGHT: childLeft = childRight - childWidth - lp.rightMargin; break; case Gravity.LEFT: default: childLeft = paddingLeft + lp.leftMargin; break; } if (hasDividerBeforeChildAt(i)) { childTop += mDividerHeight; } childTop += lp.topMargin; setChildFrame(child, childLeft, childTop + getLocationOffset(child), childWidth, childHeight); childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child); i += getChildrenSkipCount(child, i); } }
能够看见是一个逐渐往下的过程。在父容器完成定位后,调用setChildFrame调用子元素的layout。
private void setChildFrame(View child, int left, int top, int width, int height) { child.layout(left, top, left + width, top + height); }
这里的width和height就是测量宽高。
final int childWidth = child.getMeasuredWidth(); final int childHeight = child.getMeasuredHeight();
看下最后真正的宽高的计算方法以下:
public final int getWidth() { return mRight - mLeft; } public final int getHeight() { return mBottom - mTop; }
显然这个地方获得的值就是width和height。因此说在View的默认实现中,View的measure的结果测量宽高和layout的结果最终宽高是相等的。除非从新View,使得二者不一致。好比下面这样,可是这个没有什么意义。
public void layout(int l, int t, int r, int b) { super.layout(l, t, r + 100, b + 100); }
draw的过程就是将View绘制在屏幕上面。从Android源码的注释也能够看见绘制分为四个步骤,在View的draw方法中。
(1)绘制背景
drawBackground(canvas)
(2)绘制本身的内容
onDraw(canvas)
(3)绘制children
dispatchDraw(canvas);
(4)绘制装饰
onDrawForeground(canvas);
其中View的绘制经过dispatchDraw来遍历子元素并调用其draw方法,将draw事件一层层传递下去。