??經(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价脾,當然在解決那種迷之問題時,熟悉三大流程必將事倍功半笛匙。
??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的布局基本介紹一下肺素,具體的原理和底層的代碼我也不是很了解恨锚,所以也不好深入的分析這一塊匿情,況且本文并不是分析這一塊的知識蟆炊,所以,這里我就簡單的說明一下∶睿現(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);
??首先是判斷當前的寬高是否老的寬高相同携茂,如果相同,沒必要再次測量诅岩,同時如果當前View
的mode
是EXACTLY
和match_parent
都沒必要測量。為什么在EXACTLY
和match_parent
時带膜,不要調(diào)用onMeasure測量呢吩谦?
??首先當mode
為EXACTLY
時,表示當前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ī)則的同學對此不會陌生。我們的重點在第三步里面秧荆。由于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间影,當父View的寬高確定之后注竿,在進行第二次測量。
??2和3我們都不用看了魂贬,重點來看看measureChildWithMargins
方法巩割。還記得在很久很久以前,我就分析過這個方法付燥,有興趣的同學可以去看看: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
方法了勋拟,有興趣的同學可以看看。
??最后就是測量設(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
方法熊楼,告訴觀察者當前的布局已經(jīng)改變了。
??第二部分沒有分析的必要能犯,這個相信大多數(shù)的同學已經(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
方法纸兔,繪制背景惰瓜。- 保存當前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)。