ViewRoot
在介紹View的繪制前,首先我們需要知道是誰負責執(zhí)行View繪制的整個流程。實際上,View的繪制是由ViewRoot來負責的。每個應(yīng)用程序窗口的decorView都有一個與之關(guān)聯(lián)的ViewRoot對象,這種關(guān)聯(lián)關(guān)系是由WindowManager來維護的。
在Activity啟動時,ActivityThread.handleResumeActivity()方法中建立了它們兩者的關(guān)聯(lián)關(guān)系。在ActivityThread中,當Activity對象被創(chuàng)建完畢后,會將DecorView添加到Window中,同時會創(chuàng)建ViewRootImpl對象,并將ViewRootImpl對象和DecorView建立關(guān)聯(lián),這個過程的源碼如下:
root = new ViewRootImpl(view.getContext(),display);
root.setView(view,wparams,panelParentView);
View繪制的起點
View繪制的起點是以ViewRootImpl的performTraversals()方法被調(diào)用開始的。下面,我們以performTraversals()為起點,來分析View的整個繪制流程。他經(jīng)過measure layout draw三個過程才能最終將一個View繪制出來。
其大致流程見下圖:

還有這開發(fā)藝術(shù)上的經(jīng)典:

MeasureSpec
在具體講解view繪制三大過程之前,我們先看看MeasureSpec這個概念。MeasureSpec代表一個32位的int值,高兩位代表SpecMode,低兩位代表SpecSize。SpecMode是指測量模式,SpecSize是指在某種測量模式下的規(guī)格大小。下面我們看看具體的代碼(節(jié)省篇幅,源碼中的注釋已刪除):
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
/** @hide */
@IntDef({UNSPECIFIED, EXACTLY, AT_MOST})
@Retention(RetentionPolicy.SOURCE)
public @interface MeasureSpecMode {}
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(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
@MeasureSpecMode int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
public static int makeSafeMeasureSpec(int size, int mode) {
if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) {
return 0;
}
return makeMeasureSpec(size, mode);
}
@MeasureSpecMode
public static int getMode(int measureSpec) {
//noinspection ResourceType
return (measureSpec & MODE_MASK);
}
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
這里MeasureSpec通過將SpecMode、SpecSiza打包成一個int值,并且提供了獲取SpecMode和SpecSize的方法,分別對應(yīng)的getMode和getSize方法??梢钥吹揭唤MSpecMode、SpecSize可以很容易打包成一個MeasureSpec,而一個MeasureSpec也可以很容易得到他的測量模式以及view的規(guī)格大小。
SpecMode
簡單介紹下SpecMode,有三種,如下:
1. UNSPECIFIED
父布局不對View有任何限制,要多大有多大,這種情況下一般只使用于系統(tǒng)內(nèi)部,表示一種測量狀態(tài)
2. EXACTLY
父容器已經(jīng)檢測出View所需要的精確大小,這個時候View的最終的大小就是這個SpecSize所指定的值。它對應(yīng)于LayoutParams中的match_parent和具體的數(shù)值這兩種模式
3. AT_MOST
父布局指定了一個可用大小即SpecSize,View的大小不能大于這個值,具體是什么值要看不同的View的具體實現(xiàn)。他對應(yīng)于LayoutParams中的wrap_content.
Measure過程
measure過程分為View和ViewGroup的measure過程。如果是一個原始的view,那么通過measure方法就可以完成其測量過程。而ViewGroup的測量除了要完成自己的測量值外,還要遍歷所有子View并調(diào)用他們的measure,各個元素再遞歸執(zhí)行這個流程。
1.ViewGroup的Measure流程
Android系統(tǒng)的視圖結(jié)構(gòu)的設(shè)計也采用了組合模式,即View作為所有圖形的基類,Viewgroup對View繼承擴展為視圖容器類,由此就得到了視圖部分的基本結(jié)構(gòu)--樹形結(jié)構(gòu)

ViewGroup是一個抽象類,他沒有重寫onMeasure方法,但提供了一個measureChildren的方法。源碼如下:
/**
* Ask all of the children of this view to measure themselves, taking into
* account both the MeasureSpec requirements for this view and its padding.
* We skip children that are in the GONE state The heavy lifting is done in
* getChildMeasureSpec.
*
* @param widthMeasureSpec The width requirements for this view
* @param heightMeasureSpec The height requirements for this view
*/
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
//對所有子View進行遍歷
for (int i = 0; i < size; ++i) {
final View child = children[i];
//當View不為GONE狀態(tài)時,進行測量
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
這個方法就實現(xiàn)了遍歷子View的大小,再看看上面代碼最后調(diào)用的measureChild方法:
/**
* Ask one of the children of this view to measure itself, taking into
* account both the MeasureSpec requirements for this view and its padding.
* The heavy lifting is done in getChildMeasureSpec.
*
* @param child The child to measure
* @param parentWidthMeasureSpec The width requirements for this view
* @param parentHeightMeasureSpec The height requirements for this view
*/
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
//可以看出子View的MeasureSpec由父View及其自己的MeasureSpec組成,而且還加入了padding值,這是因為
//要考慮到父View被占的大小,這樣最終的大小才組成了子View的MeasureSpec
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
//這里又調(diào)用了子View的measure方法,使子view繼續(xù)遍歷測量它的子view,這樣就實現(xiàn)了遍歷測量了
//整個ViewGroup里的所有View
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
這里有出現(xiàn)了兩個新方法,不急我們一個個的來看看,先看getChildrenMeasureSpec方法,剛剛我們知道了子View的MeasureSpec由父View及其自己的MeasureSpec組成,那到底是根據(jù)是很么樣的規(guī)則決定的呢。我們一起看看這個方法的源碼:
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
//這里表示父View可用大小為父View的尺寸減去padding值(父View被占用的大?。┑慕Y(jié)果
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
//這里的specMode是父View的,也就是說先根據(jù)父View的測量模式再對應(yīng)子View的測量模式?jīng)Q定
//子View的specMode和specSize。
switch (specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
//如果父View是EXACTLY模式,而子View的大小設(shè)置值不小于0,那么子View的specSize
//就為子View的大小設(shè)置值,specMode就為EXACTLY模式
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
//如果父View是EXACTLY模式,而子View設(shè)置的是MACTH_PARENT,那么子View的specSize就為父
//View的值,specMode設(shè)置為EXACTLY模式
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
//如果父View是EXACTLY模式,而子View設(shè)置的是WRAP_CONTENT,那么子View的specSize就為父
//View的值,specMode設(shè)置為AT_MOST模式
// 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
//最后將子view的measureSpec打包完成
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
看完這個方法,再結(jié)合前面所講,我們已經(jīng)有了個明確的意識,那就是ViewGroup遍歷測量子View時,子View的大小,也就是測量結(jié)果不僅只是子View自身決定,而是由父容器的MeasureSpec和子View的LayoutParams(當然還要考慮到View的margin和padding值相關(guān))共同決定子View的MeasureSpec。上面代碼給我們展示了是如何共同決定的,如果還不直觀,我們在看看這張圖:

對于DecorView來說,它的MeasureSpec與普通View不太相同。它由窗口的尺寸和其自身的LayoutParams共同決定。對于DecorView來說,在ViewRootImpl中的measureHierarchy方法中有如下代碼:
if (!goodMeasure) {
//這里展示了DecorView的MeasureSpec的創(chuàng)建過程
//desiredWindowWidth,desiredWindowHeight為屏幕的寬高
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
windowSizeMayChange = true;
}
}
再看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;
}
這里的規(guī)則比較簡單了:根據(jù)他的LayoutParams中的參數(shù)來劃分:
- LayoutParams.MATCH_PARENT:精確模式,大小就是窗口的尺寸
- LayoutParams.WRAP_CONTENT:最大模式,大小不定,都不能超過窗口的尺寸
- 固定大小:精確模式,大小就為指定大小
到這里我們就看完了ViewGroup的measureChildren方法,measureChild方法,以及getChildMeasureSpec方法,而這系列的方法簡單的來說就是實現(xiàn)了ViewGruop遍歷其下所有View并生成對應(yīng)View的MeasureSpec這個過程。那我們就想知道最后每一個View的測量過程如何實現(xiàn),那就看第二小點,View的Measure過程。
2、View的Measure過程
View的masure過程由ViewGroup傳遞,這個方法叫measureChildWithMargins方法,顧名思義,這個方法與margin值有關(guān)。我們還要看看這個方法:
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);
}
這里與measureChild方法不同的只是加入了margin值而已,其他的都差不多,就不多看了。這里調(diào)用了measure方法。View的measure方法不能重寫,并且View中會執(zhí)行View的onMeasure方法,所以在寫自定義view時一定要重寫onMeasure方法。我們直接來看onMeasure方法的源碼:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
這段代碼很短,但有好幾個方法,setMeasureDinmension方法,getDefaultSize方法以及getSuggestedMinimumWidth方法。我們用一張圖更直觀的看看他的原理:

首先來看setMeasureDinmension方法,其實就是設(shè)置View的長寬測量值,源碼如下:
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
boolean optical = isLayoutModeOptical(this);
//復(fù)雜的判斷
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int opticalWidth = insets.left + insets.right;
int opticalHeight = insets.top + insets.bottom;
measuredWidth += optical ? opticalWidth : -opticalWidth;
measuredHeight += optical ? opticalHeight : -opticalHeight;
}
//調(diào)用方法將測量的長寬設(shè)置為view的長寬
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
這里又調(diào)用了setMeasuredDimensionRaw方法:
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
顯然,setMeasureDinmension方法并不是重點,這個方法就是將測量的長寬設(shè)置為view的長寬,我們再看看getDefaultSize方法是什么作用:
/**
* Utility to return a default size. Uses the supplied size if the
* MeasureSpec imposed no constraints. Will get larger if allowed
* by the MeasureSpec.
* 實用程序返回默認大小。使用所提供的大小,如果MeasureSpec沒有任何限制。如果允許的話會變大
* 按比例計算。
*
* @param size Default size for this view
* @param measureSpec Constraints imposed by the parent
* @return The size this view should be.
*/
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;
}
//返回一個specSize值,這個值就是view的測量大小,他真正的大小需要在Layout階段確定,但一般這兩個值相等
return result;
}
這里在MATCH_PARENT和WRAP_CONTANT模式下,getDefaultSize返回的就是specSize的值,而在UNSPECIFIED模式下,寬高分別返回了getSuggestMinimumWidth和getSuggestMinimumHeight方法的返回值。我們看看這兩個方法的源碼:
protected int getSuggestedMinimumHeight() {
return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
以getSuggestedMinimumWidth方法為例,從代碼中可以看出,如果View沒有設(shè)置背景,那么View的寬度為mMinWidth,而這個mMinWidth值就是android:minwidth屬性所指的值。如果這個屬性沒有指定值,那默認值為0;如果View有背景,則View的寬度為個mMinWidth和mBackground.getMinimumWidth()的返回值的最大值。這里的mBackground.getMinimumWidth()是Drawable的getMinimumWidth方法里的,我們看看源碼:
public int getMinimumWidth() {
final int intrinsicWidth = getIntrinsicWidth();
return intrinsicWidth > 0 ? intrinsicWidth : 0;
}
這段代碼返回的就是Drawable的原始高度,如果有原始高度就返回值,否則返回0,比如BitmapDrawable就有原始高度,而ShapeDrawable就沒有。
最后總結(jié)一下getDefaultSize方法,View的高/寬由specSize決定,而直接繼承View的自定義空白控件需要重寫onMeasure方法并設(shè)置wrap_content時的自身大小,否則其wrap_content和match_parent是一樣的效果。至于原因,前面已經(jīng)講過了,就不在贅述了。我們這里提供了這個問題的解決辦法:
private int mMinWidth = 250; // 指定默認最小寬度
private int mMinHeight = 250; // 指定默認最小高度
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
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(mMinWidth, mMinHeight);
} else if (widthSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(mMinWidth, heightSpecSize);
} else if (heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(widthSpecSize, mMinHeight);
}
}
上面這段代碼中,我們指定了一個默認的寬高大?。簃MinWidth、 mMinHeight,并在wrap_content模式下時設(shè)置此默認大小值。而其他模式就跟原來一樣的。
到這里view的measure過程就簡單介紹完了,在measure完成之后,我們可以通過getMeasuredWidth/Heigth方法獲取到View的測量寬/高,但在某些情況下,獲得的值并不準確,所以建議在onLayout方法中去獲取View的最終寬/高。那么我們來看看Layout過程。
Layout過程
測量結(jié)束后,視圖的大小就已經(jīng)測量好了,接下來就是 Layout 布局的過程。上文說過 ViewRoot 的 performTraversals 方法會在 measure 結(jié)束后,執(zhí)行 performLayout 方法,performLayout 方法則會調(diào)用 layout 方法開始布局,代碼如下
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
mLayoutRequested = false;
mScrollMayChange = true;
mInLayout = true;
final View host = mView;
if (host == null) {
return;
}
if (DEBUG_ORIENTATION || DEBUG_LAYOUT) {
Log.v(mTag, "Laying out " + host + " to (" +
host.getMeasuredWidth() + ", " + host.getMeasuredHeight() + ")");
}
try {
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
//...省略代碼
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
mInLayout = false;
我們看看View 類中 layout 方法的源碼:
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;
}
//記錄下View原始位置
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
//通過setFrame方法設(shè)置子元素的四個頂點的位置
//返回布爾值判斷View布局是否改變
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
//如果View位置改變,調(diào)用onLayout方法
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
if (shouldDrawRoundScrollbar()) {
if(mRoundScrollbarRenderer == null) {
mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
}
} else {
mRoundScrollbarRenderer = null;
}
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLayoutChangeListeners != null) {
ArrayList<OnLayoutChangeListener> listenersCopy =
(ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
int numListeners = listenersCopy.size();
for (int i = 0; i < numListeners; ++i) {
listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
}
}
}
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0) {
mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
notifyEnterOrExitForAutoFillIfNeeded(true);
}
}
layout方法首先通過setFrame方法來設(shè)定View的四個頂點的位置,即初始化mLeft,mTop,mRight,mBottom這四個值,View的四個頂點一確定,那么它在父容器里的位置也就確定了,關(guān)鍵源碼如下:
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
接著又調(diào)用了onLayout方法,這個方法確定了子元素的位置,onLayout 源碼如下:
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
看到這,是不是覺得不對,為什么是個空方法,沒錯,就是一個空方法,因為 onLayout 過程是為了確定視圖在布局中所在的位置,而這個操作應(yīng)該是由布局來完成的,即父視圖決定子視圖的顯示位置,我們繼續(xù)看 ViewGroup 中的 onLayout 方法
@Override
protected abstract void onLayout(boolean changed, int l, int t, int r, int b);
,ViewGroup 中的 onLayout 方法竟然是一個抽象方法,這就意味著所有 ViewGroup 的子類都必須重寫這個方法。像 LinearLayout、RelativeLayout 等布局,都是重寫了這個方法,然后在內(nèi)部按照各自的規(guī)則對子視圖進行布局的。接下來我們看看LinearLayout的onLayout方法,源碼:
@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方法看看,主要源碼:
void layoutVertical(int left, int top, int right, int bottom) {
···
//childTop為View到Top的高度
//循環(huán)遍歷子View
for (int i = 0; i < count; i++) {
//獲取指定View
final View child = getVirtualChildAt(i);
if (child == null) {
childTop += measureNullChild(i);
} else if (child.getVisibility() != GONE) {
//如果View可見,獲取子元素的測量寬高
//在這里可以看出setChilFrame方法傳入的參數(shù)實際上就是子元素的測量寬高
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
//獲取子元素的LayoutParams參數(shù)
final LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) child.getLayoutParams();
···
if (hasDividerBeforeChildAt(i)) {
childTop += mDividerHeight;
}
childTop += lp.topMargin;
//設(shè)置子View位置
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
//重新計算View到top的位置
//下一個子View的top位置就會相應(yīng)的增加
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
i += getChildrenSkipCount(child, i);
}
}
}
上面代碼主要完成了遍歷所有子元素并調(diào)用了setChildFrame方法來為子元素指定對應(yīng)的位置,其中childTop會逐漸增大,這樣后面的元素就會放在更靠下的位置,這也剛好符合垂直方向線性布局的特點。再看setChildFrame方法,代碼如下:
private void setChildFrame(View child, int left, int top, int width, int height) {
child.layout(left, top, left + width, top + height);
}
可以看到這個方法就是調(diào)用了子元素的layout方法,這樣子元素在確定了自己的位置后,又會調(diào)用onLayout方法繼續(xù)往下確定子元素的位置。最后整個View樹的全部元素的位置就都確定了。
Draw過程
相比前面兩個過程,Draw過程已經(jīng)簡單了許多了,它主要有如下幾步:
- 繪制背景background。draw(canvas)
- 繪制自己(onDraw)
- 繪制children(dispatchDraw)
- 繪制裝飾(onDrawForeground)
前面說過Draw過程通過performDraw方法發(fā)現(xiàn)它調(diào)用了draw方法,所以我們看看到底draw方法是如何實現(xiàn)的:
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
// Step 1, draw the background, if needed繪制
int saveCount;
if (!dirtyOpaque) {
drawBackground(canvas);
}
// skip step 2 & 5 if possible (common case)
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
// Step 3, draw the content 繪制自己 調(diào)用onDraw方法
//onDraw是一個空方法,這是因為沒個視圖的內(nèi)容部分都不太相同
//自定義View就必須重寫這個方法來實現(xiàn)View的繪制
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children 分發(fā)繪制子元素
//ViewGroup的dispatchDraw方法有具體的繪制邏輯
dispatchDraw(canvas);
drawAutofilledHighlight(canvas);
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// Step 6, draw decorations (foreground, scrollbars)
//繪制裝飾
onDrawForeground(canvas);
// Step 7, draw the default focus highlight
drawDefaultFocusHighlight(canvas);
if (debugDraw()) {
debugDrawFocus(canvas);
}
// we're done...
return;
}
···
}
View的繪制過程的傳遞是在dispatchDraw方法中實現(xiàn)的,它會遍歷所有子元素,然后調(diào)用draw方法,這樣view的draw事件就一層一層的傳遞下去了??纯碫iewGroup中的dispatchDraw方法的代碼:
@Override
protected void dispatchDraw(Canvas canvas) {
boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);
final int childrenCount = mChildrenCount;
final View[] children = mChildren;
int flags = mGroupFlags;
···
// draw reordering internally
final ArrayList<View> preorderedList = usingRenderNodeProperties
? null : buildOrderedChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
//對子元素進行遍歷,同時調(diào)用了drawChild方法
for (int i = 0; i < childrenCount; i++) {
while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
final View transientChild = mTransientViews.get(transientIndex);
if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
transientChild.getAnimation() != null) {
more |= drawChild(canvas, transientChild, drawingTime);
}
transientIndex++;
if (transientIndex >= transientCount) {
transientIndex = -1;
}
}
···
}
在看看drawChild方法的源碼:
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
顯然,View的draw過程就要完成了,這里又調(diào)用了draw方法實現(xiàn)了每個子元素的繪制。
也就是說,到了dispatchDraw方法這里,Draw過程就完成了。View的繪制過程也就全部完成了。
站在巨人的肩膀上
本文參考了《Android開發(fā)藝術(shù)探索》