Android自定義View-View的繪製流程

View的繪製基本由measure()、layout()、draw()這三個函數完成

函 數 作 用 相 關 方 法
measure() 測量View的寬高 measure(),setMeasuredDimension(),onMeasure()
layout() 計算當前View以及子View的位置 layout(),onLayout(),setFrame()
draw() 視圖的繪製 draw(),onDraw()

一、Measure

(一)MeasureSpec的理解

MeasureSpec是View的內部類,它封裝了一個View的尺寸和規格。對於View的測量,肯定會和MeasureSpec接觸,MeasureSpec是兩個單詞組成,翻譯過來“測量規格”或者“測量參數”,MeasureSpec封裝父容器傳遞給子容器的佈局要求,而不是父容器對子容器的佈局要求,“傳遞”兩個字很重要,更精確的說法應該這個MeasureSpec是由父View的MeasureSpec和子View的LayoutParams通過簡單的計算得出一個針對子View的測量要求,這個測量要求就是MeasureSpec。

個MeasureSpec是大小size和模式mode的組合值,MeasureSpec中的值是一個整型(32位)將size和mode打包成一個Int型,其中前兩位是mode,後面30位存的是size。

MeasureSpec一共有三種模式:

模式 作 用
EXACTLY 父View已經爲子View設置了尺寸,子View應當服從這些邊界,不論子容器想要多大的空間
AT_MOST 子容器可以是聲明大小內的任意大小
UPSPECIFIED 父容器對於子容器沒有任何限制,子容器想要多大就多大

子View的MeasureSpec是由父View的MeasureSpec和子View的LayoutParams共同決定的。子View的LayoutParams其實就是我們在xml寫的時候設置的layout_width和layout_height轉化而來的。父View的measure的過程會先測量子View,等子View測量結果出來後,再來測量自己,下面的measureChildWithMargins就是用來測量某個子View的,我們先來看代碼來分析是怎樣測量的,具體看註釋:

measureChildWithMargins

protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, 
                                    int parentHeightMeasureSpec, int heightUsed) { 

    //子View的LayoutParams,在xml的layout_width和layout_height,
    //layout_xxx的值最後都會封裝到這個個LayoutParams。
    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();   
    
    //根據父View的測量規格 && 父View自己的Padding && 子View的Margin && 已經用掉的空間大小widthUsed,
    //就能算出子View的MeasureSpec,具體計算過程看getChildMeasureSpec方法。
    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);  
    
    //通過父View的MeasureSpec和子View的自己LayoutParams的計算,算出子View的MeasureSpec,
    //然後父容器傳遞給子容器,讓子View用這個MeasureSpec(一個測量要求,比如不能超過多大)去測量自己,
    //如果子View是ViewGroup 那還會遞歸往下測量。
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

}

getChildMeasureSpec

//spec:表示父View的MeasureSpec;
//padding:父View的Padding+子View的Margin,精確算出子View的MeasureSpec的size;
//childDimension:子View的LayoutParams屬性的值(lp.width或者lp.height),
//可以是match_parent、wrap_content、精確值;

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {  
    int specMode = MeasureSpec.getMode(spec);  //獲得父View的mode  
    int specSize = MeasureSpec.getSize(spec);  //獲得父View的大小  

    //子View的大小 = 父View的大小 - 自己的Padding
    int size = Math.max(0, specSize - padding);   
  
    int resultSize = 0;   //初始化值,最後通過這個兩個值生成子View的MeasureSpec
    int resultMode = 0;   //初始化值,最後通過這個兩個值生成子View的MeasureSpec
  
    switch (specMode) {  
     //1、父View是EXACTLY,即當父View要求一個精確值時,爲子View賦值 
    case MeasureSpec.EXACTLY:   
        //1.1 子View的width或height是個精確值,如果子view有自己的尺寸,則使用自己的尺寸 
        if (childDimension >= 0) {            
            resultSize = childDimension;         //size爲精確值  
            resultMode = MeasureSpec.EXACTLY;    //mode爲 EXACTLY 。  
        }   
        //1.2 子View的width或height爲MATCH_PARENT/FILL_PARENT,將父View的大小賦值給子View 
        else if (childDimension == LayoutParams.MATCH_PARENT) {  
            // Child wants to be our size. So be it.  
            resultSize = size;                   //size爲父視圖大小  
            resultMode = MeasureSpec.EXACTLY;    //mode爲 EXACTLY 。  
        }   
        //1.3 子View的width或height爲WRAP_CONTENT,父View的尺寸爲子View的最大尺寸  
        else if (childDimension == LayoutParams.WRAP_CONTENT) {  
            resultSize = size;                   //size爲父視圖大小  
            resultMode = MeasureSpec.AT_MOST;    //mode爲AT_MOST 。  
        }  
        break;  
  
    //2、父View是AT_MOST,即父View給子View了一個最大界限      
    case MeasureSpec.AT_MOST:  
        //2.1 子View的width或height是個精確值,如果子view有自己的尺寸,則使用自己的尺寸 
        if (childDimension >= 0) {  
            // Child wants a specific size... so be it  
            resultSize = childDimension;        //size爲精確值  
            resultMode = MeasureSpec.EXACTLY;   //mode爲 EXACTLY 。  
        }  
        //2.2 子View的width或height爲MATCH_PARENT/FILL_PARENT,父View的尺寸爲子View的最大尺寸 
        else if (childDimension == LayoutParams.MATCH_PARENT) {  
            resultSize = size;                  //size爲父視圖大小  
            resultMode = MeasureSpec.AT_MOST;   //mode爲AT_MOST  
        }  
        //2.3 子View的width或height爲WRAP_CONTENT,父View的尺寸爲子View的最大尺寸
        else if (childDimension == LayoutParams.WRAP_CONTENT) {  
            resultSize = size;                  //size爲父視圖大小  
            resultMode = MeasureSpec.AT_MOST;   //mode爲AT_MOST  
        }  
        break;  
  
    //3、父View是UNSPECIFIED,即父View對子View沒有做任何限制 
    case MeasureSpec.UNSPECIFIED:  
        //3.1 子View的width或height是個精確值,如果子view有自己的尺寸,則使用自己的尺寸
        if (childDimension >= 0) {  
            resultSize = childDimension;        //size爲精確值  
            resultMode = MeasureSpec.EXACTLY;   //mode爲 EXACTLY  
        }  
        //3.2 因父佈局沒有對子View做出限制,當子View爲MATCH_PARENT時則大小爲0
        else if (childDimension == LayoutParams.MATCH_PARENT) {  
            resultSize = 0;                        //size爲0! ,其值未定  
            resultMode = MeasureSpec.UNSPECIFIED;  //mode爲 UNSPECIFIED  
        }   
        //3.3 因父佈局沒有對子View做出限制,當子View爲WRAP_CONTENT時則大小爲0  
        else if (childDimension == LayoutParams.WRAP_CONTENT) {  
            resultSize = 0;                        //size爲0! ,其值未定  
            resultMode = MeasureSpec.UNSPECIFIED;  //mode爲 UNSPECIFIED  
        }  
        break;  
    }  
    
    //根據上面邏輯條件獲取的mode和size構建MeasureSpec對象。  
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);  
}    

通過源碼可以看出:如果在xml中把layout_width或者layout_height把值都寫死,那麼上述的測量完全就不需要了,之所以要上面的這步測量,是因爲match_parent就是充滿父容器,wrap_content就是自適應自己多大就多大,我們寫代碼的時候特別爽,,Google就要幫我們計算你match_parent的時候是多大,wrap_content的是多大,這個計算過程,就是計算出來的父View的MeasureSpec不斷往子View傳遞,結合子View的LayoutParams一起再算出子View的MeasureSpec,然後繼續傳給子View,不斷計算每個View的MeasureSpec,子View有了MeasureSpec才能更測量自己和自己的子View。

1、如果父View的MeasureSpec是EXACTLY

說明父View的大小是確切的,(確切的意思很好理解,如果一個View的MeasureSpec 是EXACTLY,那麼它的size 是多大,最後展示到屏幕就一定是那麼大)。

(1)如果子View的layout_xxxx是MATCH_PARENT,父View的大小是確切,子View的大小是MATCH_PARENT(充滿整個父View),那麼子View的大小肯定是確切的,而且大小值就是父View的size。所以子View的size=父View的size,mode=EXACTLY。

(2)如果子View的layout_xxxx是WRAP_CONTENT,也就是子View的大小是根據自己的content來決定的,但是大小不能超過父View的大小。但是子View的是WRAP_CONTENT,我們還不知道具體子View的大小是多少,要等到child.measure(childWidthMeasureSpec,childHeightMeasureSpec)調用的時候纔去真正測量子View自己content的大小(比如TextView wrap_content的時候要測量TextView content的大小,也就是字符佔用的大小,這個測量就是在 child.measure(childWidthMeasureSpec,childHeightMeasureSpec)的時候,才能測出字符的大小,MeasureSpec的意思就是假設你字符100px,但是MeasureSpec要求最大的只能50px,這時候就要截掉了)。通過上述描述,子View MeasureSpec mode應該是AT_MOST,而size暫定父View的size,最大爲父View的大小,不能超過父View的大小(這就是AT_MOST 的意思),然後這個MeasureSpec做爲子View的measure方法的參數,做爲子View的大小的約束或者說是要求,有了這個MeasureSpec子View再實現自己的測量。

(3)如果如果子View的layout_xxxx是確定的值(200dp),那麼就更簡單了,不管你父View的mode和size是什麼,我都寫死了就是200dp,那麼控件最後展示就是就是200dp,不管我的父View有多大,也不管我自己的content有多大,反正我就是這麼大,所以這種情況MeasureSpec的mode = EXACTLY,大小size=layout_xxxx的那個值。

2、如果父View的MeasureSpec是AT_MOST

說明父View的大小是不確定,最大的大小是MeasureSpec的size值,不能超過這個值。

(1)如果子View的layout_xxxx是MATCH_PARENT,父View的大小是不確定(只知道最大隻能多大),子View的大小MATCH_PARENT(充滿整個父View),那麼子View你即使充滿父容器,大小也是不確定的,父View自己都確定不了大小,即使子ViewMATCH_PARENT,但大小肯定也不能確定的,所以子View的mode=AT_MOST,size=父View的size,最大就是父View的大小。

(2)如果子View的layout_xxxx是WRAP_CONTENT,父View的大小是不確定(只知道最大隻能多大),子View又是WRAP_CONTENT,那麼在子View的content沒算出大小之前,子View的大小最大就是父View的大小,所以子View的mode=AT_MOST,而size暫定父View的size,最大就是父View的大小。

(3)如果如果子View 的layout_xxxx是確定的值(200dp),同上,寫多少就是多少,改變不了的。

3、如果父View的MeasureSpec是UNSPECIFIED(未指定)

表示沒有任何束縛和約束,子View可以得到任意想要的大小,不受約束。不像AT_MOST表示最大隻能多大,不也像EXACTLY表示父View確定的大小。

(1)如果子View的layout_xxxx是MATCH_PARENT,因爲父View的MeasureSpec是UNSPECIFIED,父View自己的大小並沒有任何約束和要求,那麼對於子View來說無論是WRAP_CONTENT還是MATCH_PARENT,子View也是沒有任何束縛的,想多大就多大,沒有不能超過多少的要求,一旦沒有任何要求和約束,size的值就沒有任何意義了,所以一般都直接設置成0。

(2)如果子View的layout_xxxx是WRAP_CONTENT,因爲父View的MeasureSpec是UNSPECIFIED,父View自己的大小並沒有任何約束和要求,那麼對於子View來說無論是WRAP_CONTENT還是MATCH_PARENT,子View也是沒有任何束縛的,想多大就多大,沒有不能超過多少的要求,一旦沒有任何要求和約束,size的值就沒有任何意義了,所以一般都直接設置成0。

(3)如果如果子View的layout_xxxx是確定的值(200dp),寫多少就是多少,改變不了的(記住,只有設置的確切的值,那麼無論怎麼測量,大小都是不變的,都是你寫的那個值)。

(二)onMeasure()

整個測量過程的入口位於View的measure方法當中,該方法做了一些參數的初始化之後調用了onMeasure方法,測量過程主要是在onMeasure()方法。onMeasure方法的源碼如下:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                             getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

很簡單這裏只有一行代碼,涉及到了三個方法:setMeasuredDimension、getDefaultSize、getSuggestedMinimumWidth

getSuggestedMinimumWidth()和getSuggestedMinimumHeight()

//當View沒有設置背景時,默認大小就是mMinWidth,這個值對應Android:minWidth屬性,如果沒有設置時默認爲0.
//如果有設置背景,則默認大小爲mMinWidth和mBackground.getMinimumWidth()當中的較大值。
protected int getSuggestedMinimumWidth() {
        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
    }

getDefaultSize(int size, int measureSpec)

該方法用來獲取View默認的寬高,結合源碼來看。

/**
*   有兩個參數size和measureSpec
*   1、size表示View的默認大小,它的值是通過`getSuggestedMinimumWidth()方法來獲取的
*   2、measureSpec則是我們之前分析的MeasureSpec,裏面存儲了View的測量值和測量模式
*/
public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        //從這裏我們看出,對於AT_MOST和EXACTLY在View當中的處理是完全相同的。
          所以在我們自定義View時要對這兩種模式做出處理。
        switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }

getDefaultSize的第一個參數size等於getSuggestedMinimumXXXX返回的的值(建議的最小寬度和高度),而建議的最小寬度和高度都是由View的Background尺寸與通過設置View的minXXX屬性共同決定的,這個size可以理解爲View的默認長度,而第二個參數measureSpec,是父View傳給自己的MeasureSpec,這個measureSpec是通過測量計算出來的,具體的計算測量過程前面在講解MeasureSpec已經講得比較清楚了(是有父View的MeasureSpec和子View自己的LayoutParams共同決定的)只要這個測試的mode不是UNSPECIFIED(未確定的),那麼默認的就會用這個測量的數值當做View的高度。

setMeasuredDimension(int measuredWidth, int measuredHeight)

該方法用來設置View的寬高,在我們自定義View時也會經常用到。 View的onMeasure方法默認實現很簡單,就是調用setMeasuredDimension(),setMeasuredDimension()可以簡單理解就是給mMeasuredWidth和mMeasuredHeight設值,如果這兩個值一旦設置了,那麼意味着對於這個View的測量結束了,這個View的寬高已經有測量的結果。如果我們想設定某個View的高寬,完全可以直接通過setMeasuredDimension(100,200)來設置死它的高寬(不建議),但是setMeasuredDimension方法必須在onMeasure方法中調用,不然會拋異常。我們來看下對於View來說它的默認高寬是怎麼獲取的。

在setMeasuredDimension()之後,纔可以調用getMeasureWidth()和getMeasuredHeight()來獲取視圖測量出來的寬高,以此之前調用兩個方法得到的都是0。

在onMeasure()方法中調用setMeasuredDimension()方法來設定測量出的大小,這樣一次measure過程就結束了。

對於View默認是測量很簡單,大部分情況就是拿計算出來的MeasureSpec的size當做最終測量的大小。而對於其他的一些View的派生類,如TextView、Button、ImageView等,它們的onMeasure方法系統了都做了重寫,不會這麼簡單直接拿 MeasureSpec 的size來當大小,而去會先去測量字符或者圖片的高度等,然後拿到View本身content這個高度(字符高度等)。如果MeasureSpec是AT_MOST,而且View本身content的高度不超出MeasureSpec的size,那麼可以直接用View本身content的高度(字符高度等),而不是像View.java直接用MeasureSpec的size做爲View的大小。

(三)ViewGroup的Measure過程

ViewGroup的測量過程與View有一點點區別,其本身是繼承自View,它沒有對View的measure方法以及onMeasure方法進行重寫。

爲什麼沒有重寫onMeasure呢?ViewGroup除了要測量自身寬高外還需要測量各個子View的大小,而不同的佈局測量方式也都不同(可參考LinearLayout以及FrameLayout),所以沒有辦法統一設置。因此它提供了測量子View的方法measureChildren()和measureChild()幫助我們對子View進行測量。大致流程就是遍歷所有的子View,然後調用View的measure()方法,讓子View測量自身大小。

ViewGroup中定義了一個measureChildren()方法來去測量子視圖的大小,如下所示:

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);
        }
    }
}

首先去遍歷當前佈局下的所有子視圖,然後逐個調用measureChild()方法來測量相應子視圖的大小,如下所示:

protected void measureChild(View child, int parentWidthMeasureSpec,
        int parentHeightMeasureSpec) {
    final LayoutParams lp = child.getLayoutParams();
    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
            mPaddingLeft + mPaddingRight, lp.width);
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
            mPaddingTop + mPaddingBottom, lp.height);
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

上面的源碼可以看到,分別調用了getChildMeasureSpec()方法來去計算子視圖的MeasureSpec,然後調用子視圖的measure()方法,並把計算出的MeasureSpec傳遞進去,之後的流程就和前面所介紹的一樣了。

當然,onMeasure()方法是可以重寫的,也就是說,如果你不想使用系統默認的測量方式,可以按照自己的意願進行定製。measure過程會因爲佈局的不同或者需求的不同而呈現不同的形式,使用時還是要根據業務場景來具體分析,如果想再深入研究可以看一下LinearLayout的onMeasure方法。

二、Layout

layout()過程,對於View來說用來計算View的位置參數,對於ViewGroup來說,除了要測量自身位置,還需要測量子View的位置。

mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); 
......
mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());

performTraversals方法執行完mView.measure計算出mMeasuredXXX後就開始執行layout函數來確定View具體的位置,我們計算出來的View目前只知道view矩陣的大小,具體這個矩陣放在哪裏,這就是layout的工作了。layout的主要作用:根據子視圖的大小以及佈局參數將View樹放到合適的位置上。

(一)layout(l,t,r,b)

通過mView.layout(0, 0,mView.getMeasuredWidth(),mView.getMeasuredHeight());確定視圖的位置。既然layout()方法是整個Layout流程的入口,看一下這部分源碼:

/**
*  這裏的四個參數l、t、r、b分別代表View的左、上、右、下四個邊界相對於其父View的距離。
*
*/
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;

        //這裏通過setFrame或setOpticalFrame方法確定View在父容器當中的位置。
        //即初始化四個頂點的值,然後判斷當前View大小和位置是否發生了變化並返回
        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

        //如果視圖的大小和位置發生變化,會調用onLayout()確定該View所有的子View在父容器的位置
        //調用onLayout方法。onLayout方法是一個空實現,不同的佈局會有不同的實現。
        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            onLayout(changed, l, t, r, b);

        }

    }

從源碼看出,在layout()方法中已經通過:

1、setOpticalFrame(l,t,r,b)或setFrame(l,t,r,b)方法來判斷視圖的大小是否發生過變化,以確定有沒有必要對當前的視圖進行重繪。setOpticalFrame()內部也是調用了setFrame()。看下setFrame的源碼:

 

protected boolean setFrame(int left, int top, int right, int bottom) {
    ...
// 通過以下賦值語句記錄下了視圖的位置信息,即確定View的四個頂點
// 即確定了視圖的位置
    mLeft = left;
    mTop = top;
    mRight = right;
    mBottom = bottom;

    mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
}

2、onLayout(changed, l, t, r, b)方法主要是ViewGroup對子View的位置進行計算。 確定了自身的位置後,就要通過onLayout()確定子View的佈局。onLayout()是一個可繼承的空方法。

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}

如果當前View就是一個單一的View,那麼沒有子View,就不需要實現該方法。

如果當前View是一個ViewGroup,就需要實現onLayout方法,該方法的實現個自定義ViewGroup時其特性有關,必須自己實現。

在onLayout()過程結束後,我們就可以調用getWidth()方法和getHeight()方法來獲取視圖的寬高了。

getMeasureWidth()和getWidth()區別:

getMeasureWidth() getWidth()
在measure()過程結束後就可以獲取到了 在layout()過程結束後才能獲取到
值是通過setMeasuredDimension()方法來進行設置的 值則是通過視圖右邊的座標減去左邊的座標計算出來的

(二)總結View的佈局流程

 

 

 

三、Draw

draw流程也就是的View繪製到屏幕上的過程,整個流程的入口在View的draw()方法之中,而源碼註釋也寫的很明白,整個過程可以分爲6個步驟。除了2和5很少用到之外,通過各個步驟的源碼做分析:

public void draw(Canvas canvas) {
    ...
        /*
         * Draw traversal performs several drawing steps which must be executed
         * in the appropriate order:
         *
         *      1. 如果需要,繪製背景
         *      2. 有過有必要,保存當前canvas
         *      3. 繪製View的內容
         *      4. 繪製子View
         *      5. 如果有必要,繪製邊緣、陰影等效果
         *      6. 繪製裝飾,如滾動條等
         */

        // Step 1 如果需要,繪製背景
    ...
        background.draw(canvas);
    ...
        // skip step 2 & 5 if possible (common case)
    ...
        // Step 2 有過有必要,保存當前canvas
    ...
        if (solidColor == 0) {
            final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;

            if (drawTop) {
                canvas.saveLayer(left, top, right, top + length, null, flags);
            }
    ...
        // Step 3 繪製View的內容
        if (!dirtyOpaque) onDraw(canvas);

        // Step 4 繪製子View
        dispatchDraw(canvas);

        // Step 5 如果有必要,繪製邊緣、陰影等效果

        if (drawTop) {
            matrix.setScale(1, fadeHeight * topFadeStrength);
            matrix.postTranslate(left, top);
            fade.setLocalMatrix(matrix);
            canvas.drawRect(left, top, right, top + length, p);
        }
    ...
        // Step 6 繪製裝飾,如滾動條等
        onDrawScrollBars(canvas);
    }

Step1 背景繪製

看註釋即可,不是重點

private void drawBackground(Canvas canvas) { 
     Drawable final Drawable background = mBackground; 
      ...... 
     //mRight - mLeft, mBottom - mTop layout確定的四個點來設置背景的繪製區域 
     if (mBackgroundSizeChanged) { 
        background.setBounds(0, 0, mRight - mLeft, mBottom - mTop);   
        mBackgroundSizeChanged = false; rebuildOutline(); 
     } 
     ...... 
     //調用Drawable的draw() 把背景圖片畫到畫布上
     background.draw(canvas); 
     ...... 
}

Step3 對View的內容進行繪製

   /**
    * 3.繪製View的內容,該方法是一個空的實現,在各個業務當中自行處理。
    */
    protected void onDraw(Canvas canvas) {
    }

onDraw(canvas)方法是view用來draw自己的,具體如何繪製,顏色線條什麼樣式就需要子View自己去實現,View.java的onDraw(canvas) 是空實現。

Step4 對當前View的所有子View進行繪製

   /**
    * 4. 繪製子View。該方法在View當中是一個空的實現,在各個業務當中自行處理。
    *  在ViewGroup當中對dispatchDraw方法做了實現,主要是遍歷子View,並調用子類的draw方法,
    *  一般我們不需要自己重寫該方法。
    */
    protected void dispatchDraw(Canvas canvas) {

    }

在ViewGroup當中對dispatchDraw方法做了實現,主要是遍歷子View,並調用子類的draw方法,一般我們不需要自己重寫該方法。

@Override
 protected void dispatchDraw(Canvas canvas) {
       ...
        if ((flags & FLAG_USE_CHILD_DRAWING_ORDER) == 0) {
            for (int i = 0; i < count; i++) {
                final View child = children[i];
                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
                    more |= drawChild(canvas, child, drawingTime);
                }
            }
        } else {
            for (int i = 0; i < count; i++) {
                final View child = children[getChildDrawingOrder(count, i)];
                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
                    more |= drawChild(canvas, child, drawingTime);
                }
            }
        }
      ......
    }

代碼一眼看出,就是遍歷子View然後drawChild(),drawChild()方法實際調用的是子View.draw()方法,ViewGroup類已經爲我們實現繪製子View的默認過程,這個實現基本能滿足大部分需求,所以ViewGroup類的子類(LinearLayout,FrameLayout)也基本沒有去重寫dispatchDraw方法。

我們在實現自定義控件,除非比較特別,不然一般也不需要去重寫它,drawChild()的核心過程就是爲子View分配合適的canvas剪切區,剪切區的大小正是由layout過程決定的,而剪切區的位置取決於滾動值以及子視圖當前的動畫。設置完剪切區後就會調用子視圖的draw()函數進行具體的繪製了。

Step6 對View的滾動條進行繪製

不是重點,onDrawScrollBars(canvas);

總結View的繪製流程

 

 

 

四、總結

View的繪製流程:OnMeasure()——>OnLayout()——>OnDraw()

各步驟的主要工作:

模式 作 用
onMeasure() 測量視圖大小。從頂層父View到子View遞歸調用measure方法,measure方法又回調OnMeasure
onLayout() 確定View位置,進行頁面佈局。從頂層父View向子View的遞歸調用view.layout方法的過程,即父View根據上一步measure子View所得到的佈局大小和佈局參數,將子View放在合適的位置上
onDraw() 繪製視圖,ViewRoot創建一個Canvas對象,然後調用OnDraw()。六個步驟:①、繪製背景;②、保存畫布(canvas)的圖層(Layer);③、繪製View的內容;④、繪製子View,如果沒有就不用;⑤、繪製邊緣、陰影效果&還原圖層(Layer);⑥、繪製滾動條

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章