Android中View的繪製過程 onMeasure方法簡述

參考(1)摘抄html

AndroidView的繪製過程

  當Activity得到焦點時,它將被要求繪製本身的佈局,Android framework將會處理繪製過程,Activity只需提供它的佈局的根節點。java

  繪製過程從佈局的根節點開始,從根節點開始測量和繪製整個layout tree。android

  每個ViewGroup 負責要求它的每個孩子被繪製,每個View負責繪製本身。程序員

  由於整個樹是按順序遍歷的,因此父節點會先被繪製,而兄弟節點會按照它們在樹中出現的順序被繪製。canvas

  

  繪製是一個兩遍(two pass)的過程:一個measure pass和一個layout pass。框架

  測量過程(measuring pass)是在measure(int, int)中實現的,是從樹的頂端由上到下進行的。異步

  在這個遞歸過程當中,每個View會把本身的dimension specifications傳遞下去。ide

  在measure pass的最後,每個View都存儲好了本身的measurements,即測量結果。函數

 

  第二個是佈局過程(layout pass),它發生在 layout(int, int, int, int)中,仍然是從上到下進行(top-down)。佈局

  在這一遍中,每個parent都會負責用測量過程當中獲得的尺寸,把本身的全部孩子放在正確的地方。

 

尺寸的父子關係處理

  當一個View對象的 measure() 方法返回時,它的 getMeasuredWidth() 和 getMeasuredHeight()值應該被設置好了,而且它的全部子孫的值也應該一塊兒被設置好了。

  一個View對象的measured width 和measured height的值必須考慮到它的父容器給它的限制。

  這樣就保證了在measure pass的最後,全部的parent都接受了它的全部孩子的measurements結果。

 

  注意:一個parent可能會不止一次地對它的孩子調用measure()方法。

  好比,第一遍的時候,一個parent可能測量它的每個孩子,並無指定尺寸,parent只是爲了發現它們想要多大;

  若是第一遍以後得知,全部孩子的無限制的尺寸總和太大或者過小,parent會再次對它的孩子調用measure()方法,這時候parent會設定規則,介入這個過程,使用實際的值。

  (即,讓孩子自由發展不成,因而家長介入)。

 

佈局屬性說明

  LayoutParams是View用來告訴它的父容器它想要怎樣被放置的參數。

  最基本的LayoutParams基類僅僅描述了View想要多大,即指明瞭尺寸屬性。

  即View在XML佈局時一般須要指明的寬度和高度屬性。

  每個維度均可以指定成下列三種值之一:

  1.FILL_PARENT (API Level 8以後重命名爲MATCH_PARENT),表示View想要儘可能和它的parent同樣大(減去邊距)。

  2.WRAP_CONTENT,表示View想要恰好大到能夠包含它的內容(包括邊距)。

  3.具體的數值

  ViewGroup的不一樣子類(不一樣的佈局類)有相應的LayoutParams子類,其中會包含更多的佈局相關屬性。

 

onMeasure方法

  onMeasure方法是測量view和它的內容,決定measured width和measured height的,這個方法由 measure(int, int)方法喚起,子類能夠覆寫onMeasure來提供更加準確和有效的測量。

  有一個約定:在覆寫onMeasure方法的時候,必須調用 setMeasuredDimension(int,int)來存儲這個View通過測量獲得的measured width and height。

  若是沒有這麼作,將會由measure(int, int)方法拋出一個IllegalStateException

 

  onMeasure方法的聲明以下:

protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec)

 

  其中兩個輸入參數:

  widthMeasureSpec

  heightMeasureSpec

  分別是parent提出的水平和垂直的空間要求

  這兩個要求是按照View.MeasureSpec類來進行編碼的。

  參見View.MeasureSpec這個類的說明:這個類包裝了從parent傳遞下來的佈局要求,傳遞給這個child。

  每個MeasureSpec表明了對寬度或者高度的一個要求。

  每個MeasureSpec有一個尺寸(size)和一個模式(mode)構成。

  MeasureSpecs這個類提供了把一個<size, mode>的元組包裝進一個int型的方法,從而減小對象分配。固然也提供了逆向的解析方法,從int值中解出size和mode。

 

  有三種模式:

  UNSPECIFIED

  這說明parent沒有對child強加任何限制,child能夠是它想要的任何尺寸。

  EXACTLY

  Parent爲child決定了一個絕對尺寸,child將會被賦予這些邊界限制,無論child本身想要多大。

  AT_MOST

  Child能夠是本身任意的大小,可是有個絕對尺寸的上限。

 

  覆寫onMeasure方法的時候,子類有責任確保measured height and width至少爲這個View的最小height和width。

  (getSuggestedMinimumHeight() and getSuggestedMinimumWidth())。

 

onLayout

  這個方法是在layout pass中被調用的,用於肯定View的擺放位置和大小。方法聲明:

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

 

  其中的上下左右參數都是相對於parent的。

  若是View含有child,那麼onLayout中須要對每個child進行佈局。



參考(2)摘抄

整個View樹的繪圖流程是在ViewRoot.java類的performTraversals()函數展開的,該函數作的執行過程可簡單概況爲

 根以前狀態,判斷是否須要從新計算視圖大小(measure)、是否從新須要安置視圖的位置(layout)、以及是否須要重繪

 (draw),其框架過程以下:

                                                                                                   步驟其實爲host.layout() 

           

 

 

      接下來溫習一下整個View樹的結構,對每一個具體View對象的操做,其實就是個遞歸的實現。

 

                   

 

           關於這個 DecorView 根視圖的說明,能夠參考個人這篇博客:

               

         《Android中將佈局文件/View添加至窗口過程分析 ---- 從setContentView()談起》



  流程一:      mesarue()過程


        主要做用:爲整個View樹計算實際的大小,即設置實際的高(對應屬性:mMeasuredHeight)和寬(對應屬性:

  mMeasureWidth),每一個View的控件的實際寬高都是由父視圖和自己視圖決定的。

 

     具體的調用鏈以下

          ViewRoot根對象地屬性mView(其類型通常爲ViewGroup類型)調用measure()方法去計算View樹的大小,回調

View/ViewGroup對象的onMeasure()方法,該方法實現的功能以下:    

         一、設置本View視圖的最終大小,該功能的實現經過調用setMeasuredDimension()方法去設置實際的高(對應屬性:  

                mMeasuredHeight)和寬(對應屬性:mMeasureWidth)   ;

         2 、若是該View對象是個ViewGroup類型,須要重寫該onMeasure()方法,對其子視圖進行遍歷的measure()過程。

              

               2.1  對每一個子視圖的measure()過程,是經過調用父類ViewGroup.java類裏的measureChildWithMargins()方法去

          實現,該方法內部只是簡單地調用了View對象的measure()方法。(因爲measureChildWithMargins()方法只是一個過渡

          層更簡單的作法是直接調用View對象的measure()方法)。

              

     整個measure調用流程就是個樹形的遞歸過程

 

     measure函數原型爲 View.java 該函數不能被重載

      

[java] view plaincopyprint?

  1. public final void measure(int widthMeasureSpec, int heightMeasureSpec) {  

  2.     //....  

  3.   

  4.     //回調onMeasure()方法    

  5.     onMeasure(widthMeasureSpec, heightMeasureSpec);  

  6.      

  7.     //more  

  8. }  


     爲了你們更好的理解,採用「二B程序員」的方式利用僞代碼描述該measure流程

 

[java] view plaincopyprint?

  1. //回調View視圖裏的onMeasure過程  

  2. private void onMeasure(int height , int width){  

  3.  //設置該view的實際寬(mMeasuredWidth)高(mMeasuredHeight)  

  4.  //一、該方法必須在onMeasure調用,否者報異常。  

  5.  setMeasuredDimension(h , l) ;  

  6.    

  7.  //二、若是該View是ViewGroup類型,則對它的每一個子View進行measure()過程  

  8.  int childCount = getChildCount() ;  

  9.    

  10.  for(int i=0 ;i<childCount ;i++){  

  11.   //2.一、得到每一個子View對象引用  

  12.   View child = getChildAt(i) ;  

  13.     

  14.   //整個measure()過程就是個遞歸過程  

  15.   //該方法只是一個過濾器,最後會調用measure()過程 ;或者 measureChild(child , h, i)方法都  

  16.   measureChildWithMargins(child , h, i) ;   

  17.     

  18.   //其實,對於咱們本身寫的應用來講,最好的辦法是去掉框架裏的該方法,直接調用view.measure(),以下:  

  19.   //child.measure(h, l)  

  20.  }  

  21. }  

  22.   

  23. //該方法具體實如今ViewGroup.java裏 。  

  24. protected  void measureChildWithMargins(View v, int height , int width){  

  25.  v.measure(h,l)     

  26. }  


流程2、 layout佈局過程:

 

     主要做用 :爲將整個根據子視圖的大小以及佈局參數將View樹放到合適的位置上。

 

     具體的調用鏈以下:

       host.layout()開始View樹的佈局,繼而回調給View/ViewGroup類中的layout()方法。具體流程以下

  

        1 、layout方法會設置該View視圖位於父視圖的座標軸,即mLeft,mTop,mLeft,mBottom(調用setFrame()函數去實現)

  接下來回調onLayout()方法(若是該View是ViewGroup對象,須要實現該方法,對每一個子視圖進行佈局) ;

       

       二、若是該View是個ViewGroup類型,須要遍歷每一個子視圖chiildView,調用該子視圖的layout()方法去設置它的座標值。

 

          layout函數原型爲 ,位於View.java

[java] view plaincopyprint?

  1. /* final 標識符 , 不能被重載 , 參數爲每一個視圖位於父視圖的座標軸 

  2.  * @param l Left position, relative to parent 

  3.  * @param t Top position, relative to parent 

  4.  * @param r Right position, relative to parent 

  5.  * @param b Bottom position, relative to parent 

  6.  */  

  7. public final void layout(int l, int t, int r, int b) {  

  8.     boolean changed = setFrame(l, t, r, b); //設置每一個視圖位於父視圖的座標軸  

  9.     if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {  

  10.         if (ViewDebug.TRACE_HIERARCHY) {  

  11.             ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT);  

  12.         }  

  13.   

  14.         onLayout(changed, l, t, r, b);//回調onLayout函數 ,設置每一個子視圖的佈局  

  15.         mPrivateFlags &= ~LAYOUT_REQUIRED;  

  16.     }  

  17.     mPrivateFlags &= ~FORCE_LAYOUT;  

  18. }  



    一樣地, 將上面layout調用流程,用僞代碼描述以下: 

[java] view plaincopyprint?

  1. // layout()過程  ViewRoot.java  

  2. // 發起layout()的"發號者"在ViewRoot.java裏的performTraversals()方法, mView.layout()  

  3.   

  4. private void  performTraversals(){  

  5.    

  6.     //...  

  7.       

  8.     View mView  ;  

  9.        mView.layout(left,top,right,bottom) ;  

  10.       

  11.     //....  

  12. }  

  13.   

  14. //回調View視圖裏的onLayout過程 ,該方法只由ViewGroup類型實現  

  15. private void onLayout(int left , int top , right , bottom){  

  16.   

  17.  //若是該View不是ViewGroup類型  

  18.  //調用setFrame()方法設置該控件的在父視圖上的座標軸  

  19.    

  20.  setFrame(l ,t , r ,b) ;  

  21.    

  22.  //--------------------------  

  23.    

  24.  //若是該View是ViewGroup類型,則對它的每一個子View進行layout()過程  

  25.  int childCount = getChildCount() ;  

  26.    

  27.  for(int i=0 ;i<childCount ;i++){  

  28.   //2.一、得到每一個子View對象引用  

  29.   View child = getChildAt(i) ;  

  30.   //整個layout()過程就是個遞歸過程  

  31.   child.layout(l, t, r, b) ;  

  32.  }  

  33. }  




   流程3、 draw()繪圖過程

     由ViewRoot對象的performTraversals()方法調用draw()方法發起繪製該View樹,值得注意的是每次發起繪圖時,並不

  會從新繪製每一個View樹的視圖,而只會從新繪製那些「須要重繪」的視圖,View類內部變量包含了一個標誌位DRAWN,當該

視圖須要重繪時,就會爲該View添加該標誌位。

 

   調用流程 :

     mView.draw()開始繪製,draw()方法實現的功能以下:

          1 、繪製該View的背景

          2 、爲顯示漸變框作一些準備操做(見5,大多數狀況下,不須要改漸變框)          

          三、調用onDraw()方法繪製視圖自己   (每一個View都須要重載該方法,ViewGroup不須要實現該方法)

          四、調用dispatchDraw ()方法繪製子視圖(若是該View類型不爲ViewGroup,即不包含子視圖,不須要重載該方法)

值得說明的是,ViewGroup類已經爲咱們重寫了dispatchDraw ()的功能實現,應用程序通常不須要重寫該方法,但能夠重載父類

  函數實現具體的功能。

 

            4.1 dispatchDraw()方法內部會遍歷每一個子視圖,調用drawChild()去從新回調每一個子視圖的draw()方法(注意,這個 

地方「須要重繪」的視圖纔會調用draw()方法)。值得說明的是,ViewGroup類已經爲咱們重寫了dispatchDraw()的功能

實現,應用程序通常不須要重寫該方法,但能夠重載父類函數實現具體的功能。

    

     五、繪製滾動條

 

  因而,整個調用鏈就這樣遞歸下去了。

    

     一樣地,使用僞代碼描述以下:

    

[java] view plaincopyprint?

  1. // draw()過程     ViewRoot.java  

  2. // 發起draw()的"發號者"在ViewRoot.java裏的performTraversals()方法, 該方法會繼續調用draw()方法開始繪圖  

  3. private void  draw(){  

  4.    

  5.     //...  

  6.  View mView  ;  

  7.     mView.draw(canvas) ;    

  8.       

  9.     //....  

  10. }  

  11.   

  12. //回調View視圖裏的onLayout過程 ,該方法只由ViewGroup類型實現  

  13. private void draw(Canvas canvas){  

  14.  //該方法會作以下事情  

  15.  //1 、繪製該View的背景  

  16.  //二、爲繪製漸變框作一些準備操做  

  17.  //三、調用onDraw()方法繪製視圖自己  

  18.  //四、調用dispatchDraw()方法繪製每一個子視圖,dispatchDraw()已經在Android框架中實現了,在ViewGroup方法中。  

  19.       // 應用程序程序通常不須要重寫該方法,但能夠捕獲該方法的發生,作一些特別的事情。  

  20.  //五、繪製漸變框    

  21. }  

  22.   

  23. //ViewGroup.java中的dispatchDraw()方法,應用程序通常不須要重寫該方法  

  24. @Override  

  25. protected void dispatchDraw(Canvas canvas) {  

  26.  //   

  27.  //其實現方法相似以下:  

  28.  int childCount = getChildCount() ;  

  29.    

  30.  for(int i=0 ;i<childCount ;i++){  

  31.   View child = getChildAt(i) ;  

  32.   //調用drawChild完成  

  33.   drawChild(child,canvas) ;  

  34.  }       

  35. }  

  36. //ViewGroup.java中的dispatchDraw()方法,應用程序通常不須要重寫該方法  

  37. protected void drawChild(View child,Canvas canvas) {  

  38.  // ....  

  39.  //簡單的回調View對象的draw()方法,遞歸就這麼產生了。  

  40.  child.draw(canvas) ;  

  41.    

  42.  //.........  

  43. }  



   關於繪製背景圖片詳細的過程,請參考個人另外的博客:

           

              <<Android中View(視圖)繪製不一樣狀態背景圖片原理深刻分析以及StateListDrawable使用詳解>>


    強調一點的就是,在這三個流程中,Google已經幫咱們把draw()過程框架已經寫好了,自定義的ViewGroup只須要實現

 measure()過程和layout()過程便可 。


     這三種狀況,最終會直接或間接調用到三個函數,分別爲invalidate(),requsetLaytout()以及requestFocus() ,接着

這三個函數最終會調用到ViewRoot中的schedulTraversale()方法,該函數而後發起一個異步消息,消息處理中調用

performTraverser()方法對整個View進行遍歷。

 

 

    invalidate()方法 :

 

   說明:請求重繪View樹,即draw()過程,假如視圖發生大小沒有變化就不會調用layout()過程,而且只繪製那些「須要重繪的」

視圖,即誰(View的話,只繪製該View ;ViewGroup,則繪製整個ViewGroup)請求invalidate()方法,就繪製該視圖。

 

     通常引發invalidate()操做的函數以下:

            一、直接調用invalidate()方法,請求從新draw(),但只會繪製調用者自己。

            二、setSelection()方法 :請求從新draw(),但只會繪製調用者自己。

            三、setVisibility()方法 : 當View可視狀態在INVISIBLE轉換VISIBLE時,會間接調用invalidate()方法,

                     繼而繪製該View。

            4 、setEnabled()方法 : 請求從新draw(),但不會從新繪製任何視圖包括該調用者自己。

 

    requestLayout()方法 :會致使調用measure()過程 和 layout()過程 。

 

           說明:只是對View樹從新佈局layout過程包括measure()和layout()過程,不會調用draw()過程,但不會從新繪製

任何視圖包括該調用者自己。

 

    通常引發invalidate()操做的函數以下:

         一、setVisibility()方法:

             當View的可視狀態在INVISIBLE/ VISIBLE 轉換爲GONE狀態時,會間接調用requestLayout() 和invalidate方法。

    同時,因爲整個個View樹大小發生了變化,會請求measure()過程以及draw()過程,一樣地,只繪製須要「從新繪製」的視圖。

 

    requestFocus()函數說明:

 

          說明:請求View樹的draw()過程,但只繪製「須要重繪」的視圖。

相關文章
相關標籤/搜索