??經(jīng)過兩個多月的框架源碼轟炸,感覺自己的腦子變得有點懵逼了。在這兩個多月里面,先后看了RxJava、OkHttp和Retrofit的源碼,并且將自己的理解寫成了博客,作為記錄;后續(xù)又看了EventBus和ButterKnife的源碼,本來都想寫成博客的,但是覺得這兩個框架比較簡單,因此就沒有寫(純粹個人想法,大家有意見的話,盡管噴);最后,就是簡單的看了一下Glide的源碼,太特么的難了,看不懂看不懂,看到一半就放棄了,應(yīng)該是自己的功力不夠,自己再沉淀沉淀,之后再去試試吧。
??今天,我將帶來一篇比較輕松的文章--View的mesure、layout、draw三大流程。本文將詳細講解View的三大流程,閱讀本文最好有牢固的Android基礎(chǔ),并且對Android View的基本結(jié)構(gòu)有所了解。
??說到寫本文的經(jīng)歷還有點曲折,本來一開始打算好好的寫這篇文章,但是寫著寫著感覺沒什么寫的,然后自己轉(zhuǎn)而去看RecyclerView
的源碼,將RecyclerView
的三大流程簡單的梳理完畢之后,發(fā)現(xiàn)RecyclerView
的三大流程跟普通的View
有很大的不同,所以決定重新來寫這篇文章。說到底,本文就是為了后面的RecyclerView
源碼打基礎(chǔ)??。
??好了,廢話少說,進入正文。本文參考資料:
??1. Android View源碼解讀:淺談DecorView與ViewRootImpl
??2. Android View 測量流程(Measure)完全解析
??3. 從requestLayout()初探View的繪制原理
??4.Android View 繪制流程(Draw) 完全解析
??5. 任玉剛大神的《Android開發(fā)藝術(shù)探索》
??注意:本文所有源碼都基于 API 27。
1. 概述
??View的三大流程非常的重要,重要到那種程度呢?幾乎達到了面試必問的程度,同時,在實際的開發(fā)中,如果熟悉三大流程的話,自定義View可以寫的非常6,當(dāng)然在解決那種迷之問題時,熟悉三大流程必將事倍功半。
??View的三大流程,分別是measure、layout、draw三個過程。我想,不用解釋這三大流程分別是干嘛的吧?咱們從它的英文意思上就可以知道。
??在正式分析源碼之前,我們先來通過一張圖片對三大流程有一個整體的了解。
??上面的流程圖從大概上解釋了三大流程的過程,但是很多的細節(jié)都沒有解釋到,這就需要我們從源碼的程度來分析了。接下來,我們正式進入View三大流程的源碼分析。
2. ViewRootImpl
??View的三大流程從ViewRootImpl
的performTraversals
方法開始的,具體是怎么調(diào)用的這個方法來的,這里就不詳細的解釋了,因為這里面涉及到Activity
的創(chuàng)建、setContentView
、PhoneWindow
等等。這里我們只需要知道,performTraversals
方法就是三大流程的開始。但是整個過程是怎么傳遞下去的呢?這個我們必須得對整個Activity
的布局結(jié)構(gòu)有一個整體的認識,我們來看看。
??由于這部分的知識不是本文的核心內(nèi)容,所以這里就不貼出源碼來展示了。我就簡單的解釋一下。
??每一個Activity
都一個Window
對象的,Activity
所有的View
操作都托管給這個Window
,我們可以把這個Window
對象看成Activity
的代理對象,包括Activity
的setContentView
和findViewById
方法都是由Window
接管的。所以,我們看到Activity
的布局,實際上是Window
的布局。
??同時,我們還知道,Android中的View
成樹形結(jié)構(gòu),樹必須就得有一個根,那么在Window
中,這個View
樹的根是什么呢?沒錯,就是我們DectorView
。而DectorView
本身是一個FrameLayout
,并沒有什么優(yōu)勢?所以通常在DectorView
里面還會有一個類似于LinearLayout
,這個LinearLayout
裝著兩部分的布局,一部分是ActionBar
,另一部分是contentView
,也就是我們通過setContentView
方法設(shè)置的布局那部分,contentView
的id固定是android.R.id.content
,這個在開發(fā)中有一定的幫助。
??而ViewRootImpl
的performTraversals
方法就是DecorView
的三大流程,然后借助DecorView
將這三個流程傳遞下去,就像是事件分發(fā)機制一樣,一層一層傳遞下去。然后DecorView
雖然是一個ViewGroup
,但是它的三大流程跟普通的ViewGroup
相比,有一定的差別。
??這里,我只是對Activity的布局基本介紹一下,具體的原理和底層的代碼我也不是很了解,所以也不好深入的分析這一塊,況且本文并不是分析這一塊的知識,所以,這里我就簡單的說明一下?,F(xiàn)在我們來開始對源碼進行分析,先來看看performTraversals
方法相關(guān)代碼:
if (!mStopped || mReportNextDraw) {
boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
(relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
updatedConfiguration) {
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
int width = host.getMeasuredWidth();
int height = host.getMeasuredHeight();
boolean measureAgain = false;
if (lp.horizontalWeight > 0.0f) {
width += (int) ((mWidth - width) * lp.horizontalWeight);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
MeasureSpec.EXACTLY);
measureAgain = true;
}
if (lp.verticalWeight > 0.0f) {
height += (int) ((mHeight - height) * lp.verticalWeight);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
MeasureSpec.EXACTLY);
measureAgain = true;
}
if (measureAgain) {
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
layoutRequested = true;
}
}
}
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
boolean triggerGlobalLayoutListener = didLayout
|| mAttachInfo.mRecomputeGlobalAttributes;
if (didLayout) {
performLayout(lp, mWidth, mHeight);
}
if (!cancelDraw && !newSurface) {
if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).startChangingAnimations();
}
mPendingTransitions.clear();
}
performDraw();
}
}
??performTraversals
方法比較長,這里我只是將關(guān)鍵性代碼展示出來,在這里我們將知道三大流程的調(diào)用順序,最先是measure
過程,通過performMeasure
方法開始的;其次,layout
過程通過performLayout
方法開始;最后,draw
過程通過performDraw
方法開始的。接下來,我們簡單的看一下這三個方法。為什么簡單看一下呢?因為這三個方法就是操作分發(fā)到DecorView
,過程是非常的簡單。
(1). performMeasure
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
??performMeasure
方法里面幾乎沒做什么,就是把Measure
操作傳遞到DecorView
里面,而這列mView
就是DecorView
對象。
(2). performLayout
??performLayout
方法比較長,這里就不詳細的分析整個過程,但是最終的結(jié)果就是調(diào)用了DecorView
的layout
方法。待會我們在分析DecorView
時,將會詳細的分析。
(3). performDraw
??performDraw
方法跟performLayout
方法一樣,最后調(diào)用DecorView
的draw
方法,來繪制View。具體的細節(jié),之后我們會詳細的分析。這里我們先有一個概念就行。
3. meaure
??三大流程相互獨立,如果合在一起分析難免會繞圈子,所以打算一一的來分析,將每個流程單獨的打通。首先我們來看看measure
流程。
(1).measure方法
??measure
流程從ViewRootImpl
的performMeasure
方法開始,調(diào)用了mView
是什么呢?沒錯,就是DecorView
。DecorView
的measure
方法時從View
那里繼承過來的。同時,不僅僅是DecorView
,所以控件的measure
方法都是View
那里繼承過來的,因為measure
是一個final
方法,不能重寫。接下來,我們來看看View
的measure
方法:
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
// Optimize layout by avoiding an extra EXACTLY pass when the view is
// already measured as the correct size. In API 23 and below, this
// extra pass is required to make LinearLayout re-distribute weight.
final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
|| heightMeasureSpec != mOldHeightMeasureSpec;
final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
&& MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
&& getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
final boolean needsLayout = specChanged
&& (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);
if (forceLayout || needsLayout) {
// first clears the measured dimension flag
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
resolveRtlPropertiesIfNeeded();
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
long value = mMeasureCache.valueAt(cacheIndex);
// Casting a long to int drops the high 32 bits, no mask needed
setMeasuredDimensionRaw((int) (value >> 32), (int) value);
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
}
}
??View
的measure
方法比較簡單,為了代碼簡潔,我省略了很多沒必要的代碼,我們只來看看核心代碼。整個measure方法流程,我們只需要記住一點,就是判斷調(diào)用onMeasure
方法,其他的代碼都是來幫助達到這個目的的。
??我們來看看,什么時候需要調(diào)用onMeasure
方法,什么時候又不需要調(diào)用onMeasure
方法,而這種時候為什么不要調(diào)用onMeasure
方法。這三個問題,是我們重點關(guān)心的。
??從代碼中看來,我們知道forceLayout
為true或者needsLayout
為true時,有可能會調(diào)用onMeasure
方法。而這兩個方法有表示什么意思呢?
??forceLayout
變量,我們從名字就知道是什么意思,判斷時候強制布局,這個非常理解?那這個變量在什么時候為true呢?從View
的Api方法中,我們找到了一個方法--forceLayout
方法。
public void forceLayout() {
if (mMeasureCache != null) mMeasureCache.clear();
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED;
}
??在forceLayout
方法里面,這里mPrivateFlags
變量跟 PFLAG_FORCE_LAYOUT
做了一個或的位運算,所以在measure方法里面,forceLayout
才會為true:
final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
??不過這里需要注意的是,如果View
第一次調(diào)用measure
方法,forceLayout
是肯定為true的。具體是為什么,我也不太清楚,但是我們可以通過下面的代碼來驗證一下:
public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
try {
Field flags = this.getClass().getField("mPrivateFlags");
flags.setAccessible(true);
int anInt = flags.getInt(this);
Log.i("pby123", " " + ((anInt & (0x00001000)) == (0x00001000)));
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
??下面就是log日志:
??所以我們可以得出一個結(jié)論,一個
View
的onMeasure
方法至少會被執(zhí)行一次。??其實,我們可以這樣來想,如果在某些情況下
onMeasure
方法不會被執(zhí)行,那么我們在外部調(diào)用View
的getMeasureWidth
方法始終得到的是0,這是不可能的。同時,如果getMeasureWidth
方法返回值為0的話,那么在layout階段,我們根本不知道怎么進行布局,這也是不可能的。這樣,我們就從側(cè)面可以得出,onMeasure
方法至少會被執(zhí)行一次。??那為什么需要判斷是否執(zhí)行
onMeasure
方法呢?這是為了避免多次執(zhí)行的onMeasure
方法。??另一個變量就是
needsLayout
,這個變量我們從名字上就可以判斷出來,表示是否需要布局,這個變量在什么時候為true呢?
final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
|| heightMeasureSpec != mOldHeightMeasureSpec;
final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
&& MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
&& getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
final boolean needsLayout = specChanged
&& (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);
??首先是判斷當(dāng)前的寬高是否老的寬高相同,如果相同,沒必要再次測量,同時如果當(dāng)前View
的mode
是EXACTLY
和match_parent
都沒必要測量。為什么在EXACTLY
和match_parent
時,不要調(diào)用onMeasure測量呢?
??首先當(dāng)mode
為EXACTLY
時,表示當(dāng)前View的寬高在第一次調(diào)用onMeasure
方法已經(jīng)定死了,沒必要調(diào)用onMeasure
方法進行測量。
??其次就是match_parent
,跟EXACTLY
一樣,在父View分發(fā)measure事件下來時,也是經(jīng)過第一次measure方法之后,寬高已經(jīng)定死了,后續(xù)就沒必要再次測量。
??將measure
方法簡單的分析一下之后,我們來看看DecorView
的onMeasure
方法,看看怎么測量自己和測量子View的。
(2).onMeasure方法
??DecorView
的onMeasure
方法比較長,我先簡單將這個方法分為過程,然后一一來分析。
1.根據(jù)mode,來計算
widthMeasureSpec
和heightMeasureSpec
2.如果存在outset,并且mode不為UNSPECIFIED
,那么就會考慮到outset
,重新計算widthMeasureSpec
和heightMeasureSpec
3.調(diào)用super.onMeasure
方法,進行真正的測量。
??前兩步都沒有什么可以分析,都是基本的操作,相信熟悉Android測量規(guī)則的同學(xué)對此不會陌生。我們的重點在第三步里面。由于DecorView
繼承于FrameLayout
,所以,我們來看看FrameLayout
的onMeasure
方法。
??FrameLayout
的onMeasure
方法也比較長,這里先分為幾個過程。
- 調(diào)用每個child的measure方法,測量每個child的寬高;并且記錄設(shè)置了
match_parent
屬性的child- 調(diào)用
setMeasuredDimension
方法,對自身寬高進行設(shè)置。- 對設(shè)置了
match_parent
屬性的child進行測量。
??整個過程還是比較清晰,我們一個一個來分析。首先來看看第一個過程:
final boolean measureMatchParentChildren =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
mMatchParentChildren.clear();
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
maxWidth = Math.max(maxWidth,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
maxHeight = Math.max(maxHeight,
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
childState = combineMeasuredStates(childState, child.getMeasuredState());
if (measureMatchParentChildren) {
if (lp.width == LayoutParams.MATCH_PARENT ||
lp.height == LayoutParams.MATCH_PARENT) {
mMatchParentChildren.add(child);
}
}
}
}
??這個過程,我們可以將它分成3個部分來看:
1 . 調(diào)用
measureChildWithMargins
方法對子View的進行測量。
- 不斷更新
maxHeight
和maxWidth
的值,主要是用于父View的測量,如果父View本身為wrap_content,這兩個值就非常的重要。- 記錄下設(shè)置
match_parent
屬性的child,當(dāng)父View的寬高確定之后,在進行第二次測量。
??2和3我們都不用看了,重點來看看measureChildWithMargins
方法。還記得在很久很久以前,我就分析過這個方法,有興趣的同學(xué)可以去看看:Android 踩坑系列-ViewGroup的子View真正實現(xiàn)Margin屬性。好了,我們來看看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);
}
??measureChildWithMargins
方法比較簡單,就是通過調(diào)用getChildMeasureSpec
方法來獲取child的MeasureSpec
,然后將計算完畢的MeasureSpec
傳遞到child
的measure
進行真正測量。這里的重點在getChildMeasureSpec
方法,也是整個Android系統(tǒng)中的View
測量核心之一,從這個方法里面,我們可以獲得很多的測量規(guī)則。我們重點分析分析:
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);
}
??在分析這個方法之前,我們先對每個變量有一個認識。
變量名 | 類型 | 含義 |
---|---|---|
spec | int | 父View 的MeasureSpec ,在getChildMeasureSpec 方法里面,主要是通過這個變量來獲得父View的測量mode。因為子View 的MeasureSpec 是由父View 的MeasureSpec 和子View 的MeasureSpec 共同決定的 |
padding | int | 主要是記錄父View 的padding 和子View 的margin
|
childDimension | int | 子View 的MeasureSpec ,與spec 共同決定子View 的MeasureSpec
|
??整個getChildMeasureSpec
方法比較簡單,分為三種大情況,每種大情況又分為三種小情況,所以一共9種情況。現(xiàn)在我們通過一張表來分析。
??上面表中就詳細的分析了每種情況下規(guī)則,這里我就不多說了。
??通過
getChildMeasureSpec
方法,我們可以獲得child
的MeasureSpec
,然后調(diào)用child
的measure
方法進行測量,這就將measure
事件分發(fā)下去了??對第一個過程分析完畢之后,我們來看第二個過程:調(diào)用
setMeasuredDimension
方法,對自身寬高進行設(shè)置。
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
??這一步比較簡單,通過resolveSizeAndState
方法來獲得父View
的MeasureSpec
。這里主要是考慮到父View
可能是warp_content
,所以有maxHeight
和maxWidth
參與,這里就不分析resolveSizeAndState
方法了,有興趣的同學(xué)可以看看。
??最后就是測量設(shè)置了match_parent
的child
,這個過程跟第一個過程比較像,這里就在就不分析了。
??整個measure過程,我們算是分析完畢了。這里我做一個簡單的總結(jié)。
- measure過程從
DecorView
的measure
方法開始,而measure
本身不會進行測量,而是分發(fā)到了onMeasure
方法。由于DecorView
繼承于
FrameLayout
,所以調(diào)用的是FrameLayout
的onMeasure
方法。FrameLayout
的onMeasure
方法會測量自身,同時同時會將測量事件分發(fā)到每個View手里,從而完成了整個View樹的測量。
??分析完畢measure過程,現(xiàn)在我們來看看layout過程。
4. layout
??前面已經(jīng)說了,ViewRootImpl
會通過performLayout
方法來分發(fā),而performLayout
方法最終會調(diào)用DecorView
的layout
方法進行布局。
??我們先來看看performLayout
方法:
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
mLayoutRequested = false;
final View host = mView;
if (host == null) {
return;
}
try {
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
int numViewsRequestingLayout = mLayoutRequesters.size();
if (numViewsRequestingLayout > 0) {
// requestLayout() was called during layout.
// If no layout-request flags are set on the requesting views, there is no problem.
// If some requests are still pending, then we need to clear those flags and do
// a full request/measure/layout pass to handle this situation.
ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,
false);
if (validLayoutRequesters != null) {
// Set this flag to indicate that any further requests are happening during
// the second pass, which may result in posting those requests to the next
// frame instead
mHandlingLayoutInLayoutRequest = true;
// Process fresh layout requests, then measure and layout
int numValidRequests = validLayoutRequesters.size();
for (int i = 0; i < numValidRequests; ++i) {
final View view = validLayoutRequesters.get(i);
Log.w("View", "requestLayout() improperly called by " + view +
" during layout: running second layout pass");
view.requestLayout();
}
measureHierarchy(host, lp, mView.getContext().getResources(),
desiredWindowWidth, desiredWindowHeight);
mInLayout = true;
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
mHandlingLayoutInLayoutRequest = false;
// Check the valid requests again, this time without checking/clearing the
// layout flags, since requests happening during the second pass get noop'd
validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, true);
if (validLayoutRequesters != null) {
final ArrayList<View> finalRequesters = validLayoutRequesters;
// Post second-pass requests to the next frame
getRunQueue().post(new Runnable() {
@Override
public void run() {
int numValidRequests = finalRequesters.size();
for (int i = 0; i < numValidRequests; ++i) {
final View view = finalRequesters.get(i);
Log.w("View", "requestLayout() improperly called by " + view +
" during second layout pass: posting in next frame");
view.requestLayout();
}
}
});
}
}
}
}
}
??整個performLayout
方法比較長,我將它分為兩個部分。
- 如果
host
不為null,也就是DecorView
不為null,調(diào)用DecorView
的layout
方法,將布局操作分發(fā)下去。- 如果
mLayoutRequesters
不為空的話,進行第二次布局。至于mLayoutRequesters
什么不為空,這就涉及到requestLayout
方法了,后續(xù)我會單獨寫一篇文章來分析這個方法,本文不做過多的講解。
??這里,我們重點的看第一個部分。第一個部分調(diào)用了DecorView
的layout
方法。而DecorView
的layout
方法最終會調(diào)用到View
的layout
方法,我們直接來看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;
}
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);
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);
}
}
??View
的layout
方法也比較簡單,我將它分為兩個部分:
- 調(diào)用onLayout方法,進行真正的布局操作。
- 回調(diào)
OnLayoutChangeListener
的onLayoutChange
方法,告訴觀察者當(dāng)前的布局已經(jīng)改變了。
??第二部分沒有分析的必要,這個相信大多數(shù)的同學(xué)已經(jīng)司空見慣了。我們重點來看看onLayout
方法,而View
的onLayout
方法本身是一個空方法。從這個空方法,我們可以得出兩點結(jié)論:
- 普通的View調(diào)用layout方法進行布局,其實就是簡單將left、top、right、bottom4個變量記錄下來,并沒有做其他的操作布局。
ViewGroup
必須實現(xiàn)onLayout
方法,制定子View的布局規(guī)則。這就是ViewGroup
有一個抽象方法的原因。
??既然View
的layout
調(diào)用了onLayout
方法,接下來我們來看看DecorView
的onLayout
方法
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
getOutsets(mOutsets);
if (mOutsets.left > 0) {
offsetLeftAndRight(-mOutsets.left);
}
if (mOutsets.top > 0) {
offsetTopAndBottom(-mOutsets.top);
}
if (mApplyFloatingVerticalInsets) {
offsetTopAndBottom(mFloatingInsets.top);
}
if (mApplyFloatingHorizontalInsets) {
offsetLeftAndRight(mFloatingInsets.left);
}
// If the application changed its SystemUI metrics, we might also have to adapt
// our shadow elevation.
updateElevation();
mAllowUpdateElevation = true;
if (changed && mResizeMode == RESIZE_MODE_DOCKED_DIVIDER) {
getViewRootImpl().requestInvalidateRootRenderNode();
}
}
??DecorView
的onLayout
方法,我也簡單將它分為兩步:
- 調(diào)用
super.onLayout
方法,也就是FrameLayout
的onLayout
方法來進行布局。- 根據(jù)
mOutsets
來調(diào)整位置。至于mOutsets
是什么,抱歉,我也不知道??。
??看來我們看看FrameLayout
的onLayout
方法。
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
??好嘛,又調(diào)用layoutChildren
方法。在layoutChildren
方法里面才是真正對child進行布局的操作。
??這里就不對layoutChildren
方法進行展開了,因為比較簡單。就是根據(jù)每種ViewGroup不同的布局特性,進行計算每個view
的left、top、right和bottom,然后調(diào)用child
的layout
方法。
??如果child
是一個普通的View
的話,那么調(diào)用layout
方法就是記錄下4個值,等待draw流程的到來;如果child
是一個ViewGroup
的話,就會像FrameLayout
一樣,將layout
事件分發(fā)下去。
??如上,就是整個View
的layout流程,這里我做一個簡單的總結(jié)。
- layout過程從
DecorView
的layout
方法(也是View
的layout
方法)開始。在View
的layout
方法里面,會記錄下自身的left、top、right、bottom4個屬性,等待繪制,同時會調(diào)用onLayout
方法將layout
事件分發(fā)下去。- 如果是普通的
View
,在layout
方法里面調(diào)用onLayout
方法是沒有用的,因為在View
里面,onLayout
方法是一個空方法;如果是一個ViewGroup
,在onLayout
里面,會調(diào)用每個child
的layout
方法。這樣整個layout流程就走通了。
??分析完layout
流程之后,我們再來看看三大流程的最后一個流程--draw
。
5. draw
??前面已經(jīng)說了,View
樹的draw操作是從ViewRootImpl
的performDraw
方法開始的。現(xiàn)在我們來看看performDraw
方法。
private void performDraw() {
// ······
try {
draw(fullRedrawNeeded);
} finally {
}
// ······
}
??performDraw
方法比較長,這里我將代碼簡化了一下。說到底,performDraw
方法就是調(diào)用draw
方法。
??我們來看一下draw
方法,整個draw
方法比較長,我簡單的將它分為幾個部分:
- 根據(jù)
fullRedrawNeeded
變量,來計算dirty
。dirty
是一個矩陣,表示這次繪制的范圍。- 調(diào)用
drawSoftware
方法進行繪制。
??整個draw
方法比較復(fù)雜,因為這里面涉及到動畫之類的。如果此時在動畫,表示本次繪制并不是最終的繪制,所以需要調(diào)用scheduleTraversals
方法往主線程post一個Message用來下次繪制。
??其次,dirty
的計算也是比較復(fù)雜的,我們這里也不去分析,因為這些都是計算,如果深入分析的話,容易將我們聰明的大腦搞暈??。
??我們還是直接來看drawSoftware
方法吧。
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty) {
final Canvas canvas;
try {
canvas = mSurface.lockCanvas(dirty);
// TODO: Do this in native
canvas.setDensity(mDensity);
} catch (Surface.OutOfResourcesException e) {
handleOutOfResourcesException(e);
return false;
} catch (IllegalArgumentException e) {
return false;
}
try {
if (!canvas.isOpaque() || yoff != 0 || xoff != 0) {
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
}
dirty.setEmpty();
mIsAnimating = false;
mView.mPrivateFlags |= View.PFLAG_DRAWN;
try {
canvas.translate(-xoff, -yoff);
mView.draw(canvas);
} finally {
}
} finally {
try {
surface.unlockCanvasAndPost(canvas);
} catch (IllegalArgumentException e) {
return false;
}
}
return true;
}
??整個drawSoftware
方法比較長,我簡化了一下代碼。這里,我先將整個方法分為3個部分:
- 根據(jù)
dirty
矩陣獲得繪制的Canvas
對象- 調(diào)用
DecorView
的draw
方法,繪制整個View
樹- 釋放
Canvas
??我們一一的分析,首先來看看第一步。
canvas = mSurface.lockCanvas(dirty);
// TODO: Do this in native
canvas.setDensity(mDensity);
??這里通過mSurface
來鎖定一塊畫布,從而保證后續(xù)的繪制操作是線程安全的。
??與之對應(yīng)的是,最后是釋放了這塊區(qū)域。
??我們重點的是是如下的代碼:
mView.draw(canvas);
??上面的代碼最終是調(diào)用View
的draw
方法。我們來看看View
的draw
方法:
public void draw(Canvas canvas) {
/*
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
*
* 1. Draw the background
* 2. If necessary, save the canvas' layers to prepare for fading
* 3. Draw view's content
* 4. Draw children
* 5. If necessary, draw the fading edges and restore layers
* 6. Draw decorations (scrollbars for instance)
*/
// 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
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
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);
return;
}
//······
}
??整個draw方法的流程非常的清晰,一個分為7步:
- 調(diào)用
drawBackground
方法,繪制背景。- 保存當(dāng)前View的畫布層次,這一步只在繪制fading edge才會執(zhí)行。
- 調(diào)用
onDraw
方法,繪制View
自身。- 調(diào)用
dispatchDraw
方法,繪制children
- 繪制fading edge,這個只在View本身需要繪制ading edge才會執(zhí)行。
- 調(diào)用
onDrawForeground
方法,繪制View
的前景。- 調(diào)用
drawDefaultFocusHighlight
方法,繪制高亮部分。
??View
通過這7步就將整個View
樹繪制完畢。這里,我們就不對每個過程做詳細的分析,因為每個過程都可以寫的非常多,況且,我也不知道??。
??說到draw流程,就會想到invalidate
和postInvalidate
這兩個吊的一逼的方法,后續(xù)我會專門寫文章來分析這兩個方法,這里就不糾結(jié)了。
??draw流程算是分析完畢了,這里我對整個draw做一個小小的總結(jié)。
- draw流程是從
ViewRootImpl
的performDraw
方法開始,在這個方法主要是調(diào)用draw方法來進行操作。ViewRootImpl
的draw
方法主要是做了兩步,一是計算畫布區(qū)域,用于后面獲取畫布對象;二是調(diào)用drawSoftware
方法來進行操作。drawSoftware
方法主要做了3步,一是獲得鎖定一個畫布對象;二是調(diào)用View
的draw
啟動整個draw流程的執(zhí)行;三是釋放畫布對象。View
的draw
方法一共分為7步。每步做了可以參考上面的說明,這里就不重復(fù)的介紹了。對于View
的draw
方法,我們沒必要去沒比較去糾結(jié)每步是怎么做的,因為這樣容易導(dǎo)致深入源碼,不可自拔。
6. 總結(jié)
??View三大流程的流程到這里算是已經(jīng)結(jié)束,總的來說,介紹比較粗糙。但是我們分析源碼,沒必要去糾結(jié)每一行代碼,搞懂整個流程就OK,因為整個Android framework架構(gòu)是非常的復(fù)雜。
??這里我對三大流程做一個簡單的總結(jié)。
- 三大流程從View都是從
ViewRootImpl
的performTraversals
方法,分別調(diào)用performMeasure
、performLayout
和performDraw
方法進行三大流程的分發(fā)。- 三大流程的執(zhí)行流程非常的相似,都是一種View樹的遞歸遍歷思想。
??三大流程的源碼分析到此就結(jié)束了,接下來我會趁熱打鐵,進一步的分析requestLayout
、invalidate
和postInvalidate
這三個方法。因為這三個方法跟layout和draw兩個流程有關(guān)。