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事件一層層傳遞下去。