上一篇文章中,我們介紹了DecorViewMeasureSpec, 下面的文章就開始討論View的三大流程。

View的三大流程都是通過ViewRoot來完成的。ViewRoot對應于ViewRootImpl類,它是連接WindowManagerDecorView的紐帶。在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()依次調用performMeasureperformLayout,performDraw三個方法。這三個方法依次完成View的 measure,layout,draw過程。

借用網(wǎng)上的一張圖,可以清晰的表達這個過程。

大數(shù)據(jù)培訓,云培訓,數(shù)據(jù)挖掘培訓,云計算培訓,高端軟件開發(fā)培訓,項目經(jī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)某一個子控件的尺寸不符合要求,就會重新測量一遍。

借用某個大神博客上的一張圖
大數(shù)據(jù)培訓,云培訓,數(shù)據(jù)挖掘培訓,云計算培訓,高端軟件開發(fā)培訓,項目經(jīng)理培訓

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方法而整理出來的那張表。

大數(shù)據(jù)培訓,云培訓,數(shù)據(jù)挖掘培訓,云計算培訓,高端軟件開發(fā)培訓,項目經(jīng)理培訓

當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_cotentmatch_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方法,這是合理的,因為各個不同的子容器(比如LinearLayoutFrameLayout,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方法,指出這個方法是很重要的。

  • measureChildWithMarginmeasureChild的區(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