Android View的繪製流程

View的繪製和事件處理是兩個重要的主題, 之前說過View的事件分發處理機制,如果還不太清楚的同學可以先看一下Android Touch事件的傳遞機制,這裏不再多說了。對於一些系統自帶控件實現不了的功能,就需要我們自己去自己繪製,前提是你需要熟練掌握View的繪製流程。

1 在正式說View的繪製流程之前,先了解一下Android的UI管理系統的層級關係。

這裏寫圖片描述

下面來一一介紹各個層級的含義與作用

1.1 PhoneWindow

PhoneWindow是Android中的最基本的窗口系統,每個Activity 均會創建一個PhoneWindow對象,是Activity和整個View系統交互的接口。

1.2 DecorView

DecorView本質上是一個FrameLayout,DecorView是當前Activity所有View的祖先,它並不會向用戶呈現任何東西,它主要有如下幾個功能,可能不全:

A. Dispatch ViewRoot分發來的key、touch、trackball等外部事件;
B. DecorView有一個直接的子View,我們稱之爲System Layout,這個View是從系統的Layout.xml中解析出的,它包含當前UI的風格,如是否帶title、是否帶process bar等。可以稱這些屬性爲Window decorations。
C. 作爲PhoneWindow與ViewRoot之間的橋樑,ViewRoot通過DecorView設置窗口屬性。

1.3 System Layout

目前android根據用戶需求預設了幾種UI 風格,通過PhoneWindow通過解析預置的layout.xml來獲得包含有不同Window decorations的layout,我們稱之爲System Layout,我們將這個System Layout添加到DecorView中,目前android提供了8種System Layout,如下圖。

預設風格可以通過PhoneWindow方法requestFeature()來設置,需要注意的是這個方法需要在setContentView()方法調用之前調用。

1.4 Content Parent

Content Parent這個ViewGroup對象纔是真真正正的ContentView的parent,我們的ContentView終於找到了寄主,它其實對應的是System Layout中的id爲”content”的一個FrameLayout。這個FrameLayout對象包括的纔是我們的Activity的layout(每個System Layout都會有這麼一個id爲”content”的一個FrameLayout)。

1.5 Activity Layout

這個ActivityLayout便是我們需要向窗口設置的ContentView,現在我們發現其實它的地位很低,同時這一部分纔是和user交互的UI部分,其上的幾層並不能響應並完成user輸入所期望達到的目的。

2 繪製的整體流程

既然我們知道整個View的Root是DecorView,那麼View的繪製是從哪裏開始的呢,我們知道每個Activity 均會創建一個 PhoneWindow對象,是Activity和整個View系統交互的接口,每個Window都對應着一個View和一個ViewRootImpl,Window和View通過ViewRootImpl來建立聯繫,對於Activity來說,ViewRootImpl是連接WindowManager和DecorView的紐帶,繪製的入口是由ViewRootImpl的performTraversals方法來發起Measure,Layout,Draw等流程的。

我們來看下ViewRootImpl的performTraversals 方法:

private void performTraversals() { 
...... 
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); 
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); 
...... 
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); 
......
mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
...... 
mView.draw(canvas); 
......
}

private static int getRootMeasureSpec(int windowSize, int rootDimension) { 
   int measureSpec; 
   switch (rootDimension) { 
   case ViewGroup.LayoutParams.MATCH_PARENT: 
   // Window can't resize. Force root view to be windowSize.   
   measureSpec = MeasureSpec.makeMeasureSpec(windowSize,MeasureSpec.EXACTLY);
   break; 
   ...... 
  } 
 return measureSpec; 
}

performTraversals 中我們看到的mView其實就是DecorView,View的繪製從DecorView開始, 在mView.measure()的時候調用getRootMeasureSpec獲得兩個MeasureSpec做爲參數,getRootMeasureSpec的兩個參數(mWidth, lp.width)mWith和mHeight 是屏幕的寬度和高度, lp是WindowManager.LayoutParams,它的lp.width和lp.height的默認值是MATCH_PARENT,所以通過getRootMeasureSpec 生成的測量規格MeasureSpec 的mode是MATCH_PARENT ,size是屏幕的高寬。

繪製會從根視圖ViewRoot的performTraversals()方法開始,從上到下遍歷整個視圖樹,每個View控件繪製自己,而ViewGroup還需要通知自己的子View進行繪製操作。視圖繪製的過程可以分爲三個操作,分別是測量(Measure),佈局(Layout)和繪製(Draw),首先說下Measure過程。

3 Measure()過程

想要更好的學習Measure()過程,必須要知道MeasureSpec,這是什麼鬼?這是測量模式,View類中的一個靜態內部類,用來說明應該如何測量這個View。

大家都知道一個MeasureSpec是一個大小跟模式的組合值,MeasureSpec中的值是一個整型(32位)將size和mode打包成一個Int型,其中高兩位是mode,後面30位存的是size,是爲了減少對象的分配開支。MeasureSpec 類似於下圖,只不過這邊用的是十進制的數,而MeasureSpec 是二進制存儲的。

這裏寫圖片描述

注:-1 代表的是EXACTLY,-2 是AT_MOST

3.1 MeasureSpec共三種測量模式:

  • UPSPECIFIED : 不指定測量模式,父容器對於子容器沒有任何限制,子容器想要多大就多大,通常用於系統開發,應用開發中很少用到。
  • EXACTLY: 精確測量模式,當該視圖的layout_weight或者layout_height指定爲具體數值或者match_parent時生效,父容器已經爲子容器設置了尺寸,子容器應當服從這些邊界,不論子容器想要多大的空間。
  • AT_MOST:最大值模式,當該視圖的layout_weight或者layout_height指定爲具體數值或者wrap_content時生效,此時子視圖的尺寸可以是不超過父視圖允許的最大尺寸的任何單位。

對於DecorView而言,它的MeasureSpec由窗口尺寸和其自身的LayoutParams共同決定;對於普通的View,它的MeasureSpec由父視圖的MeasureSpec和其自身的LayoutParams共同決定。

3.2 Measure

很多文章都會把這三個模式說成這樣,當然其實包括官方文檔也是這樣表達的,但是這樣並不好理解。特別是如果把這三種模式又和MATCH_PARENT和WRAP_CONTENT 聯繫到一起,很多人就懵逼了。如果從代碼上來看view.measure(int widthMeasureSpec, int heightMeasureSpec) 的兩個MeasureSpec是父類傳遞過來的,但並不是完全是父View的要求,而是父View的MeasureSpec和子View自己的LayoutParams共同決定的,而子View的LayoutParams其實就是我們在xml寫的時候設置的layout_width和layout_height 轉化而來的。我們先來看代碼會清晰一些:

父View的measure的過程會先測量子View,等子View測量結果出來後,再來測量自己,上面的measureChildWithMargins就是用來測量某個子View的,我們來分析是怎樣測量的,具體看註釋:

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

}

// spec參數   表示父View的MeasureSpec 
// padding參數    父View的Padding+子View的Margin,父View的大小減去這些邊距,才能精確算出
//               子View的MeasureSpec的size
// childDimension參數  表示該子View內部LayoutParams屬性的值(lp.width或者lp.height)
//                    可以是wrap_content、match_parent、一個精確指(an exactly size),  
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {  
    int specMode = MeasureSpec.getMode(spec);  //獲得父View的mode  
    int specSize = MeasureSpec.getSize(spec);  //獲得父View的大小  

   //父View的大小-自己的Padding+子View的Margin,得到值纔是子View的大小。
    int size = Math.max(0, specSize - padding);   

    int resultSize = 0;    //初始化值,最後通過這個兩個值生成子View的MeasureSpec
    int resultMode = 0;    //初始化值,最後通過這個兩個值生成子View的MeasureSpec

    switch (specMode) {  
    // Parent has imposed an exact size on us  
    //1、父View是EXACTLY的 !  
    case MeasureSpec.EXACTLY:   
        //1.1、子View的width或height是個精確值 (an exactly size)  
        if (childDimension >= 0) {            
            resultSize = childDimension;         //size爲精確值  
            resultMode = MeasureSpec.EXACTLY;    //mode爲 EXACTLY 。  
        }   
        //1.2、子View的width或height爲 MATCH_PARENT/FILL_PARENT   
        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  
        else if (childDimension == LayoutParams.WRAP_CONTENT) {  
            // Child wants to determine its own size. It can't be  
            // bigger than us.  
            resultSize = size;                   //size爲父視圖大小  
            resultMode = MeasureSpec.AT_MOST;    //mode爲AT_MOST 。  
        }  
        break;  

    // Parent has imposed a maximum size on us  
    //2、父View是AT_MOST的 !      
    case MeasureSpec.AT_MOST:  
        //2.1、子View的width或height是個精確值 (an exactly size)  
        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  
        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;                  //size爲父視圖大小  
            resultMode = MeasureSpec.AT_MOST;   //mode爲AT_MOST  
        }  
        //2.3、子View的width或height爲 WRAP_CONTENT  
        else if (childDimension == LayoutParams.WRAP_CONTENT) {  
            // Child wants to determine its own size. It can't be  
            // bigger than us.  
            resultSize = size;                  //size爲父視圖大小  
            resultMode = MeasureSpec.AT_MOST;   //mode爲AT_MOST  
        }  
        break;  

    // Parent asked to see how big we want to be  
    //3、父View是UNSPECIFIED的 !  
    case MeasureSpec.UNSPECIFIED:  
        //3.1、子View的width或height是個精確值 (an exactly size)  
        if (childDimension >= 0) {  
            // Child wants a specific size... let him have it  
            resultSize = childDimension;        //size爲精確值  
            resultMode = MeasureSpec.EXACTLY;   //mode爲 EXACTLY  
        }  
        //3.2、子View的width或height爲 MATCH_PARENT/FILL_PARENT  
        else if (childDimension == LayoutParams.MATCH_PARENT) {  
            // Child wants to be our size... find out how big it should  
            // be  
            resultSize = 0;                        //size爲0! ,其值未定  
            resultMode = MeasureSpec.UNSPECIFIED;  //mode爲 UNSPECIFIED  
        }   
        //3.3、子View的width或height爲 WRAP_CONTENT  
        else if (childDimension == LayoutParams.WRAP_CONTENT) {  
            // Child wants to determine its own size.... find out how  
            // big it should be  
            resultSize = 0;                        //size爲0! ,其值未定  
            resultMode = MeasureSpec.UNSPECIFIED;  //mode爲 UNSPECIFIED  
        }  
        break;  
    }  
    //根據上面邏輯條件獲取的mode和size構建MeasureSpec對象。  
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);  
}

上面的代碼有點多,希望你仔細看一些註釋,代碼寫得很多,其實計算原理很簡單:
1、如果我們在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。

上述代碼如果這麼來理解就簡單了

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

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

    b. 如果子View 的layout_xxxx是WRAP_CONTENT,也就是子View的大小是根據自己的content 來決定的,但是子View的畢竟是子View,大小不能超過父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的大小最大爲父View的大小,不能超過父View的大小(這就是AT_MOST 的意思),然後這個MeasureSpec 做爲子View measure方法 的參數,做爲子View的大小的約束或者說是要求,有了這個MeasureSpec子View再實現自己的測量。

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

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

    a. 如果子View 的layout_xxxx是MATCH_PARENT,父View的大小是不確定(只知道最大隻能多大),子View的大小MATCH_PARENT(充滿整個父View),那麼子View你即使充滿父容器,你的大小也是不確定的,父View自己都確定不了自己的大小,你MATCH_PARENT你的大小肯定也不能確定的,所以子View的mode=AT_MOST,size=父View的size,也就是你在佈局雖然寫的是MATCH_PARENT,但是由於你的父容器自己的大小不確定,導致子View的大小也不確定,只知道最大就是父View的大小。

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

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

  • 如果父View的MeasureSpec 是UNSPECIFIED(未指定),表示沒有任何束縛和約束,不像AT_MOST表示最大隻能多大,不也像EXACTLY表示父View確定的大小,子View可以得到任意想要的大小,不受約束

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

    b. 同上…

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

到此爲止,關於MeasureSpec和三種模式還有WRAP_CONTENT和MATCH_PARENT和固定值已經說完了,下面主要在說下onMeasure()方法。

3.3 View的測量過程主要是在onMeasure()方法

打開View的源碼,找到measure方法,這個方法代碼不少,但是測量工作都是在onMeasure()做的,measure方法是final的所以這個方法也不可重寫,如果想自定義View的測量,你應該去重寫onMeasure()方法

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
  ......
  onMeasure(widthMeasureSpec,heightMeasureSpec);
  .....
}

繼續追蹤onMeasure()方法:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {    
  setMeasuredDimension(
  getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),            
  getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

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

//獲取的是android:minHeight屬性的值或者View背景圖片的大小值
protected int getSuggestedMinimumWidth() { 
   return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth()); 
} 
//@param size參數一般表示設置了android:minHeight屬性或者該View背景圖片的大小值  
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:        //表示該View的大小父視圖未定,設置爲默認值 
     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的高度。

對於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的大小。

3.4 ViewGroup的Measure過程

ViewGroup 類並沒有實現onMeasure,我們知道測量過程其實都是在onMeasure方法裏面做的,我們來看下FrameLayout 的onMeasure 方法,具體分析看註釋哦。

//FrameLayout 的測量
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
....
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) {   
    // 遍歷自己的子View,只要不是GONE的都會參與測量,measureChildWithMargins方法在最上面
    // 的源碼已經講過了,如果忘了回頭去看看,基本思想就是父View把自己的MeasureSpec 
    // 傳給子View結合子View自己的LayoutParams 算出子View 的MeasureSpec,然後繼續往下傳,
    // 傳遞葉子節點,葉子節點沒有子View,根據傳下來的這個MeasureSpec測量自己就好了。
     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);  
     ....
     ....
   }
}
.....
.....
//所有的孩子測量之後,經過一系類的計算之後通過setMeasuredDimension設置自己的寬高,
//對於FrameLayout 可能用最大的字View的大小,對於LinearLayout,可能是高度的累加,
//具體測量的原理去看看源碼。總的來說,父View是等所有的子View測量結束之後,再來測量自己。
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),        
resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT));
....
}

到這裏,基本把Measure 主要原理都過了一遍。有不懂得地方或者錯誤可以在下面留言

4. layout過程

private void performTraversals() { 
......
mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
...... 

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

既然是通過mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight()); 那我們來看下layout 函數做了什麼,mView肯定是個ViewGroup,不會是View,我們直接看下ViewGroup 的layout函數

public final void layout(int l, int t, int r, int b) {    
   if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {        
       if (mTransition != null) {            
           mTransition.layoutChange(this);        
        }       
        super.layout(l, t, r, b);    
    } else {        
    // record the fact that we noop'd it; request layout when transition finishes        
      mLayoutCalledWhileSuppressed = true;    
   }
}

代碼可以看個大概,LayoutTransition是用於處理ViewGroup增加和刪除子視圖的動畫效果,也就是說如果當前ViewGroup未添加LayoutTransition動畫,或者LayoutTransition動畫此刻並未運行,那麼調用super.layout(l, t, r, b),繼而調用到ViewGroup中的onLayout,否則將mLayoutSuppressed設置爲true,等待動畫完成時再調用requestLayout()。
這個函數是final 不能重寫,所以ViewGroup的子類都會調用這個函數,layout 的具體實現是在super.layout(l, t, r, b)裏面做的,那麼我接下來看一下View類的layout函數

public final void layout(int l, int t, int r, int b) {
       .....
      //設置View位於父視圖的座標軸
       boolean changed = setFrame(l, t, r, b); 
       //判斷View的位置是否發生過變化,看有必要進行重新layout嗎
       if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {
           if (ViewDebug.TRACE_HIERARCHY) {
               ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT);
           }
           //調用onLayout(changed, l, t, r, b); 函數
           onLayout(changed, l, t, r, b);
           mPrivateFlags &= ~LAYOUT_REQUIRED;
       }
       mPrivateFlags &= ~FORCE_LAYOUT;
       .....
   }

1、setFrame(l, t, r, b) 可以理解爲給mLeft 、mTop、mRight、mBottom賦值,然後基本就能確定View自己在父視圖的位置了,這幾個值構成的矩形區域就是該View顯示的位置,這裏的具體位置都是相對與父視圖的位置。

2、回調onLayout,對於View來說,onLayout只是一個空實現,一般情況下我們也不需要重載該函數,:

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

}

對於ViewGroup 來說,唯一的差別就是ViewGroup中多了關鍵字abstract的修飾,要求其子類必須重載onLayout函數。

@Override  
protected abstract void onLayout(boolean changed,  
        int l, int t, int r, int b);

而重載onLayout的目的就是安排其children在父視圖的具體位置,那麼如何安排子View的具體位置呢?

 int childCount = getChildCount() ; 
  for(int i=0 ;i<childCount ;i++){
       View child = getChildAt(i) ;
       //整個layout()過程就是個遞歸過程
       child.layout(l, t, r, b) ;
    }

代碼很簡單,就是遍歷自己的孩子,然後調用 child.layout(l, t, r, b) ,給子view 通過setFrame(l, t, r, b) 確定位置,而重點是(l, t, r, b) 怎麼計算出來的呢。還記得我們之前測量過程,測量出來的MeasuredWidth和MeasuredHeight嗎?還記得你在xml 設置的Gravity嗎?還有RelativeLayout 的其他參數嗎,沒錯,就是這些參數和MeasuredHeight、MeasuredWidth 一起來確定子View在父視圖的具體位置的。具體的計算過程大家可以看下最簡單FrameLayout 的onLayout 函數的源碼,每個不同的ViewGroup 的實現都不一樣,這邊不做具體分析了吧。

3、MeasuredWidth和MeasuredHeight這兩個參數爲layout過程提供了一個很重要的依據(如果不知道View的大小,你怎麼固定四個點的位置呢),但是這兩個參數也不是必須的,layout過程中的4個參數l, t, r, b完全可以由我們任意指定,而View的最終的佈局位置和大小(mRight - mLeft=實際寬或者mBottom-mTop=實際高)完全由這4個參數決定,measure過程得到的mMeasuredWidth和mMeasuredHeight提供了視圖大小測量的值,但我們完全可以不使用這兩個值,所以measure過程並不是必須的。如果我們不使用這兩個值,那麼getMeasuredWidth() 和getWidth() 就很有可能不是同一個值,它們的計算是不一樣的:

public final int getMeasuredWidth() {  
        return mMeasuredWidth & MEASURED_SIZE_MASK;  
    }  
public final int getWidth() {  
        return mRight - mLeft;  
    }

到此,Layout過程也已經說完了,比較簡單,有不懂得地方或者錯誤可以在下面留言。

5.draw過程

performTraversals 方法的下一步就是mView.draw(canvas); 因爲View的draw 方法一般不去重寫,官網文檔也建議不要去重寫draw 方法,所以下一步執行就是View.java的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
        // 步驟一:繪製View的背景
    ...
        background.draw(canvas);
    ...
        // skip step 2 & 5 if possible (common case)
    ...
        // Step 2, save the canvas' layers
        // 步驟二:如果需要的話,保存canvas的圖層,爲fading做準備
    ...

        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, draw the content
        // 步驟三:繪製View的內容
        if (!dirtyOpaque) onDraw(canvas);

        // Step 4, draw the children
        // 步驟四:繪製View的子View
        dispatchDraw(canvas);

        // Step 5, draw the fade effect and restore layers
        // 步驟五:如果需要的話,繪製View 的fading邊緣並恢復圖層

        if (drawTop) {
            matrix.setScale(1, fadeHeight * topFadeStrength);
            matrix.postTranslate(left, top);
            fade.setLocalMatrix(matrix);
            canvas.drawRect(left, top, right, top + length, p);
        }
    ...
        // Step 6, draw decorations (scrollbars)
        // 步驟一:繪製View的裝飾(例如滾動條)

        onDrawScrollBars(canvas);
    }

寫的比較清楚,咱們一個一個來
1、第一步:背景繪製
看註釋即可,不是重點

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

2、第三步,對View的內容進行繪製。
onDraw(canvas) 方法是view用來draw 自己的,具體如何繪製,顏色線條什麼樣式就需要子View自己去實現,View.java 的onDraw(canvas) 是空實現,ViewGroup 也沒有實現,每個View的內容是各不相同的,所以需要由子類去實現具體邏輯。

3、第4步 對當前View的所有子View進行繪製
dispatchDraw(canvas) 方法是用來繪製子View的,View.java 的dispatchDraw()方法是一個空方法,因爲View沒有子View,不需要實現dispatchDraw ()方法,ViewGroup就不一樣了,它實現了dispatchDraw ()方法:

@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()的核心過程就是爲子視圖分配合適的cavas剪切區,剪切區的大小正是由layout過程決定的,而剪切區的位置取決於滾動值以及子視圖當前的動畫。設置完剪切區後就會調用子視圖的draw()函數進行具體的繪製了。

4、第6步 對View的滾動條進行繪製
不是重點,知道有這東西就行,onDrawScrollBars 的一句註釋 :Request the drawing of the horizontal and the vertical scrollbar. The scrollbars are painted only if they have been awakened first.

一張圖看下整個draw的遞歸流程。

這裏寫圖片描述

到這裏真個View的繪製過程已經說完了,篇幅有點長,還請大家靜下心來仔細閱讀,有不懂得地方或者錯誤可以在下面留言。

參考:
android的窗口機制分析——UI管理系統

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