在上一篇文章中,我們介紹了DecorView
與MeasureSpec
, 下面的文章就開始討論View的三大流程。
View的三大流程都是通過ViewRoot
來完成的。ViewRoot
對應于ViewRootImpl
類,它是連接WindowManager
與DecorView
的紐帶。在ActivityThread中,當Activity
對象被創(chuàng)建完畢之后,會將DecorView
添加到Window
中,同時創(chuàng)建ViewRootImpl
對象,并將ViewRootImpl
對象和DecorView
建立關聯(lián)。
View的繪制流程是從ViewRootImpl#performTraversals()
開始的,它經(jīng)過measure(測量),layout(布局),draw(繪制)三個過程才能最終將一個view顯示出來。
measure過程測量View的寬高尺寸。Measure完成以后,就可以通過
getMeasuredWidth()
和getMeasuredHeight()
來獲取View的寬高了。layout過程確定View在父容器中的擺放位置。layout()完成之后,就可以通過
getTop()
,getLeft()
,getRight()
,getBottom()
來拿到View的左上角,右下角的坐標數(shù)據(jù)了。draw過程負責將View繪制在屏幕上。draw()方法完成之后,View才能顯示在屏幕上。
ViewRootImpl#performTraversals()
依次調用performMeasure
, performLayout
,performDraw
三個方法。這三個方法依次完成View的 measure,layout,draw過程。
借用網(wǎng)上的一張圖,可以清晰的表達這個過程。
performMeasure
調用View#measure
方法,而View#measure
則又調用onMeasure
方法,而對于View中的onMeasure
方法,直接保存了測量得到的尺寸,而類似FrameLayout
,RelativeLayout
等各種容器ViewGroup,則在自己覆蓋重寫的的onMeasure
方法中,對所有的子元素進行measure過程,此時measure流程就從父容器傳遞到子View中了,這就完成了一次measure過程,最終這個遞歸的過程完成了整個View樹的遍歷。performLayout
,performDraw
的傳遞流程也是類似的。唯一不同的是performDraw
的傳遞過程是通過draw
方法中通過dispatchDraw
來實現(xiàn)的,但是道理都是相同的。traversals的意思就是遍歷。
其實到了這個時候,我們大概的流程就已經(jīng)知道了,那么剩下的具體就看看View和ViewGroup當中的具體的測量過程了。這兩個還要分情況討論,因為View是屬于自己一人吃飽,全家不餓,把自己測量好就行了,可是ViewGroup不僅要測量自己,還要遍歷去調用所有子元素的measure過程,從而形成一個遞歸過程。
而measure的大流程則如下:
測量的時候父控件的
onMeasure
方法會遍歷他所有的子控件,挨個調用子控件的measure方法,measure方法會調用onMeasure,然后會調用setMeasureDimension方法保存測量的大小,一次遍歷下來,第一個子控件以及這個子控件中的所有孫控件都會完成測量工作;然后開始測量第二個子控件…;最后父控件所有的子控件都完成測量以后會調用setMeasureDimension方法保存自己的測量大小。值得注意的是,這個過程有可能重復執(zhí)行多次,因為有的時候,一輪測量下來,父控件發(fā)現(xiàn)某一個子控件的尺寸不符合要求,就會重新測量一遍。
View的measure過程
View的測量過程由measure()
來完成,measure()
方法是final
的,子類無法重寫覆蓋。它會調用onMeasure
方法。
根據(jù)
measure
的源碼,View其實也是比較喜歡偷懶的。意思是執(zhí)行了measure
方法并不一定將測量過程完整走一遍(就是調用onMeasure
方法)。具體來說,如果View發(fā)現(xiàn)不是強制測量,且本次傳入的MeasureSpec
與上次傳入的相同,那么View就沒必要重新測量一遍。如果真的需要測量,View也先查看之前是否緩存過相應的計算結果,如果有緩存,直接保存計算結果,就不用再調用onMeasure
了。這樣也是最大限度的提高效率,避免重復工作。
onMeasure
方法代碼如下:
//View#onMeasure/** * Measure the view and its content to determine the measured width and the * measured height. This method is invoked by {@link #measure(int, int)} and * should be overridden by subclasses to provide accurate and efficient * measurement of their contents. */ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }
首先需要注意,該方法注釋的一句話,
This method is invoked by {@link #measure(int, int)} and should be overridden by subclasses to provide accurate and efficient measurement of their contents.
這就意味著,我們在進行自定義View時,是應該重寫覆蓋這個方法的。
先看一下onMeasure
方法中調用的getDefaultSize
方法。
//View#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
模式一般用于系統(tǒng)內(nèi)部的測量過程,在這種情況下,View的大小為getDefaultSize
的第一個參數(shù)size,即寬度為getSuggestedMinimumWidth()
返回的值,而getSuggestedMinimumWidth()
可以用一句偽代碼來表示。
mMinWidth = android:minWidth; return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
對于測量過程而言,width,height過程都是一樣的流程。所以為了行文簡單,所以我們就簡單的只說width。
再回過頭來看 getDefaultSize
方法的源碼。可以看到,不管我們的View的LayoutParams
設置的是 match_parent
或者 wrap_content
,它的最終尺寸都是 相同的。再結合上一篇文章末尾我們根據(jù)getChildMeasureSpec
方法而整理出來的那張表。
當View為wrap_content
時,它的size就是parentSize-padding,這和match_parent
時,size是一樣的。(雖然mode可能不一樣)。
結論就是: 對于View來講,使用wrap_content
和使用match_parent
效果是一樣的,它倆的默認size是相同的。
所以各個子View,(當然也包括我們extends View
,自定義的View),在測量階段,針對wrap_content
都要做相應的處理,否則使用 wrap_content
就和使用 match_parent
效果都是一樣的, 那就是默認填充滿父View剩下的空間。
而我們在自定義View時,針對wrap_content
情況一般處理方式是,在onMeasure
中,增加對MeasureSpec.AT_MOST
的判斷語句,結合具體業(yè)務場景或情況,設定一個默認值,或計算出一個值。
wrap_content
的意思就是 包裹內(nèi)容,但是仔細思考一下,內(nèi)容又是什么呢,具體到不同的子View場景,肯定有不同的意義,所以從這個角度來思考,作為繼承結構上最頂級,同時也是最抽象的View而言,wrap_cotent
和match_parent
默認尺寸一樣,也就有道理了。
其實普通View的onMeasure邏輯大同小異,基本都是測量自身內(nèi)容和背景,然后根據(jù)父View傳遞過來的MeasureSpec進行最終的大小判定,例如TextView會根據(jù)文字的長度,大小,行高,行寬,顯示方式,背景圖片,以及父View傳遞過來的模式和大小最終確定自身的大小.
在測量結束時,調用了setMeasuredDimension
來存儲測量得到的寬高值,該方法源碼當中,注釋是這樣的。
This method must be called by {@link #onMeasure(int, int)} to store the measured width and measured height. Failing to do so will trigger an exception at measurement time.
如果我們自定義View時,重寫了onMeasure
方法,那么就需要調用setMeasuredDimension
方法來保存結果,否則就會拋出異常。
ViewGroup的measure過程
ViewGroup比View復雜,因為它要遍歷去調用所有子元素的measure過程,各個子元素再遞歸去執(zhí)行這個過程。正所謂領導都要能者多勞之,領導要干的工作也比較多。
ViewGroup是一個抽象類,它沒有重寫View的onMeasure
方法,這是合理的,因為各個不同的子容器(比如LinearLayout
, FrameLayout
,RelativeLayout
等)它們有它們特定的具體布局方式(比如如何擺放子View),所以ViewGroup沒辦法具體統(tǒng)一,onMeasure
的實現(xiàn)邏輯,都是在各個具體容器類中實現(xiàn)的。
但是ViewGroup當中,提供了三個測量子控件的方法。
/** *遍歷ViewGroup中所有的子控件,調用measuireChild測量寬高 */ 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); } } }/*** 測量某一個child的寬高*///ViewGroup中方法。protected void measureChild (View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) { final LayoutParams lp = child.getLayoutParams(); //獲取子控件的寬高約束規(guī)則 final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight, lp. width); final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom, lp. height); child.measure(childWidthMeasureSpec, childHeightMeasureSpec); }/*** 測量某一個child的寬高,考慮margin值*/protected void measureChildWithMargins (View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) { final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); //獲取子控件的寬高約束規(guī)則,相比于 measureChild方法,這里考慮了 lp.margin值 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); }
望名知意,根據(jù)方法的命名我們就能知道每個方法的作用。measureChild
,或measureChildWithMargin
,它們的作用就是取出child的layoutParams
,然后再通過getChildMeasureSpec
來創(chuàng)建child的MeasureSpec
,然后再將MeasureSpec
傳遞給child進行measure。這樣就完成了一輪遞歸。
所以我們在上一篇博客中,著重介紹了
getChildMeasureSpec
方法,指出這個方法是很重要的。
measureChildWithMargin
和measureChild
的區(qū)別就是父控件是否支持margin屬性。
因為各個不同的容器(比如LinearLayout
,FrameLayout
,RelativeLayout
等),都有各自的measure方式,所以我們就挑選LinearLayout
來看一下它的measure流程。
//LinearLayout @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (mOrientation == VERTICAL) { measureVertical(widthMeasureSpec, heightMeasureSpec); } else { measureHorizontal(widthMeasureSpec, heightMeasureSpec); } }
我們就選擇豎直方向的measure過程來分析。
源碼比較長,我們只看主流程。
//LinearLayout#measureVertical// See how tall everyone is. Also remember max width.for (int i = 0; i < count; ++i) { final View child = getVirtualChildAt(i); // .... // Determine how big this child would like to be. If this or // previous children have given a weight, then we allow it to // use all available space (and we will shrink things later // if needed). final int usedHeight = totalWeight == 0 ? mTotalLength : 0; measureChildBeforeLayout(child, i, widthMeasureSpec, 0, heightMeasureSpec, usedHeight); final int childHeight = child.getMeasuredHeight(); final int totalLength = mTotalLength; mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin + lp.bottomMargin + getNextLocationOffset(child)); }
系統(tǒng)會遍歷子元素,并對每個子元素執(zhí)行measureChildBeforeLayout
方法,該方法內(nèi)部會調用子元素的measure方法,這樣各個子元素就依次進入measure過程。系統(tǒng)通過mTotalLength
這個變量動態(tài)存儲LinearLayout在豎直方向上的高度,并且伴隨著每測量一個子元素,mTotalLength
則會逐步增加。增加的部分包括了子元素的高度以及子元素在豎直方向上的margin等。
http://www.cnblogs.com/yaoxiaowen/p/7056763.html