Android View繪製的三大流程

本文講的是Android視圖的繪製的三大流程,視圖的工做流程主要是指測量,佈局,繪製這三大流程,即測量,佈局和繪製,其中測量肯定查看的測量寬高,佈局根據測量的寬高肯定視圖在其父視圖中的四個頂點的位置,而平局則將查看繪製到屏幕上,這樣經過一個ViewGroup的遞歸遍歷,一個景觀樹就展示在屏幕上了。說的簡單,下面帶你們一步一步從源碼中分析:java

安卓的視圖是樹形結構的:微信

基本概念佈局

在介紹視圖的三大流程以前,咱們必須先介紹一些基本的概念,才能更好地理解這整個過程。this

窗口的概念orm

窗口表示的是一個窗口的概念,它是站在WindowManagerService角度上的一個抽象的概念,安卓中全部的視圖都是經過窗口來呈現的,不論是活動,對話仍是麪包,只要有瀏覽的地方就必定有窗口。對象

這裏須要注意的是,這個抽象的窗口概念和PhoneWindow這個類並非同一個東西,PhoneWindow表示的是手機屏幕的抽象,它充當活動和DecorView之間的媒介,就算沒有PhoneWindow也是能夠展現查看的。blog

拋開一切,僅站在WindowManagerService的角度上,安卓的界面就是由一個個窗口層疊展示的,而窗戶又是一個抽象的概念,它並非實際存在的,它是以景觀的形式存在,這個視圖就是DecorView。繼承

關於窗口這方面的內容,咱們這裏先了解一個大概遞歸

DecorView的概念事件

DecorView是整個窗口界面的最頂層視圖,視圖的測量,佈局,繪製,事件分發都是由DecorView往下遍歷這個景觀樹.DecorView做爲頂級景觀,通常狀況下它內部會包含一個豎直方向的LinearLayout中,在這個的LinearLayout裏面有上下兩個部分(具體狀況和的Android的版本及主題有關),上面是【標題欄】,下面是【內容欄】。在活動中咱們經過的setContentView所設置的佈局文件其實就是被加載到【內容欄】中的,而內容欄的ID是內容,所以指定佈局的方法叫setContent()。

的ViewRoot的概念

的ViewRoot對應於ViewRootImpl類,它是鏈接的WindowManager和DecorView的紐帶,查看的三大流程均是經過的ViewRoot來完成的。在ActivityThread中,當活動對象被建立完以後,會講DecorView添加到窗口中,同時會建立對應的ViewRootImpl,並將ViewRootImpl和DecorView創建關聯,並保存到WindowManagerGlobal對象中。

 
  1. WindowManagerGlobal.java 
  2.  
  3. root = new ViewRootImpl(view .getContext(),display);   
  4. root.setView(view ,wparams,panelParentView);  

Java的

查看的繪製流程是從的ViewRoot的performTraversals方法開始的,它通過測量,佈局和繪製三個過程才能最終將一個視圖繪製出來,大體流程以下圖:

測量測量

爲了更好地理解查看的測量過程,咱們還須要理解MeasureSpec,它是搜索的一個內部類,它表示對查看的測量規格.MeasureSpec表明一個32位INT值,高2位表明SpecMode(測量模式),低30位表明SpecSize(測量大小),咱們能夠看看它的具體實現:

 
  1. MeasureSpec.java 
  2.  
  3. 公共靜態 類MeasureSpec {    
  4.         private  static  final  int  MODE_SHIFT = 30; 
  5.         private  static  final  int  MODE_MASK = 0x3 << MODE_SHIFT; 
  6.  
  7.         / ** 
  8.           *未知模式: 
  9.           *父查看不對子查看有任何限制,子查看須要多大就多大 
  10.           * /  
  11.         公共靜態 最終  詮釋 Unknown = 0 << MODE_SHIFT;  
  12.  
  13.         / ** 
  14.           *精確模式: 
  15.           *父查看已經測量出子Viwe所須要的精確大小,這時候查看的最終大小 
  16.           *就是SpecSize所指定的值。對應於match_parent和精確數值這兩種模式 
  17.           * /  
  18.         public static  final  int  EXACTLY = 1 << MODE_SHIFT;  
  19.  
  20.         / ** 
  21.           * AT_MOST模式: 
  22.           *子查看的最終大小是父查看指定的SpecSize值,而且子查看的大小不能大於這個值, 
  23.           *即對應wrap_content這種模式 
  24.           * /  
  25.         public static  final  int  AT_MOST = 2 << MODE_SHIFT;  
  26.  
  27.         //將大小和模式打包成一個32位的INT 型數值 
  28.         //高2位表示SpecMode,測量模式,低30位表示SpecSize,某種測量模式下的規格大小 
  29.         public static int  makeMeasureSpec(int size ,  int  mode){    
  30.             if(sUseBrokenMakeMeasureSpec){ 
  31.                 返回大小 +模式;  
  32.             }  else  { 
  33.                 返回 (size  &〜MODE_MASK)| (mode&MODE_MASK); 
  34.             } 
  35.         } 
  36.  
  37.         //將32位的MeasureSpec解包,返回SpecMode,測量模式 
  38.         public static int  getMode(int  measureSpec){   
  39.             return  (measureSpec&MODE_MASK); 
  40.         } 
  41.  
  42.         //將32位的MeasureSpec解包,返回SpecSize,某種測量模式下的規格大小 
  43.         public static int  getSize(int  measureSpec){   
  44.             return  (measureSpec&〜MODE_MASK); 
  45.         } 
  46.         // ... 
  47.     }  

Java的

MeasureSpec經過將SpecMode和SpecSize打包成一個INT值來避免過多的對象內存分配,並提供了打包和解包的方法。

SpecMode有三種類型,每一類都表示特殊的含義:

UNSPECIFIED

父容器不對視圖有任何限制,要多大就給多大,這種狀況通常用於系統內部,表示一種測量的狀態;

究竟

父容器已經檢測出查看所需的精確大小,這個時候搜索的最終打消就是SpecSize所指定的值。它對應於的LayoutParams中的match_parent和具體數值這兩種模式。

最多

父容器指定了一個可用大小即SpecSize,查看的大小不能大於這個值,具體是什麼值要看不一樣觀的具體實現。它對應於的LayoutParams中WRAP_CONTENT。

查看的MeasureSpec是由父容器的MeasureSpec和本身的的LayoutParams決定的,可是對於DecorView來講有點不一樣,由於它沒有父類。在ViewRootImpl中的measureHierarchy方法中有以下一段代碼展現了DecorView的MeasureSpec的建立過程,其中desiredWindowWidth和desireWindowHeight是屏幕的尺寸大小:

ViewGroup中的措施

 
  1. childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth,lp.width); 
  2.  
  3. childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight,lp.height); 
  4.  
  5. performMeasure(childWidthMeasureSpec,childHeightMeasureSpec);  

Java的

再看看getRootMeasureSpec方法:

 
  1. private  static int  getRootMeasureSpec(int  windowSize,  int  rootDimension){  
  2.         int  measureSpec; 
  3.         開關(rootDimension){ 
  4.  
  5.         case  ViewGroup.LayoutParams.MATCH_PARENT: 
  6.             //窗口沒法調整大小。 強制 根  視圖爲 windowSize。  
  7.             measureSpec = MeasureSpec.makeMeasureSpec(windowSize,MeasureSpec.EXACTLY); 
  8.             打破; 
  9.         案例 ViewGroup.LayoutParams.WRAP_CONTENT: 
  10.             //窗口能夠調整大小 設置最大尺寸爲 根  視圖。    
  11.             measureSpec = MeasureSpec.makeMeasureSpec(windowSize,MeasureSpec.AT_MOST); 
  12.             打破; 
  13.         默認: 
  14.             //窗口要  到 成爲一門精確  的尺寸。 力 根  視圖到 是  尺寸。  
  15.             measureSpec = MeasureSpec.makeMeasureSpec(rootDimension,MeasureSpec.EXACTLY); 
  16.             打破; 
  17.         } 
  18.         返回 measureSpec; 
  19.     }  

Java的

經過以上代碼,DecorView的MeasureSpec的產生過程就很明確了,由於DecorView是FrameLyaout的子類,屬於ViewGroup中,對於ViewGroup中來講,除了完成本身的測量過程外,還會遍歷去調用全部子元素的測量方法,各個子元素再遞歸去執行這個過程。和查看不一樣的是,一個ViewGroup是一個抽象類,他沒有重寫查看的onMeasure方法,這裏很好理解,由於每一個具體的ViewGroup中實現類的功能是不一樣的,如何測量應該讓它本身決定,好比LinearLayout中和RelativeLayout的。

所以在具體的一個ViewGroup中須要遍歷去測量子查看,這裏咱們看看一個ViewGroup中提供的測量子視圖的measureChildWithMargins方法:

 
  1. 保護無效measureChildWithMargins(查看 孩子, 
  2.             int  parentWidthMeasureSpec,  int  widthUsed, 
  3.             int  parentHeightMeasureSpec,  int  heightUsed){ 
  4.         最終MarginLayoutParams lp =(MarginLayoutParams)child.getLayoutParams(); 
  5.  
  6.         final  int  childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, 
  7.                 mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin 
  8.                         + widthUsed,lp.width); 
  9.         final  int  childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, 
  10.                 mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin 
  11.                         + heightUsed,lp.height); 
  12.  
  13.         child.measure(childWidthMeasureSpec,childHeightMeasureSpec); 
  14.     }  

Java的

上述方法會對子元素進行測量,在調用子元素的測量方法以前會先經過getChildMeasureSpec方法來獲得子元素的MeasureSpec。從代碼上看,子元素的MeasureSpec的建立與父容器的MeasureSpec和自己的的LayoutParams有關,此外和查看的保證金和父類的填充有關,如今看看getChildMeasureSpec的具體實現:

 
  1. ViewGroup.java 
  2.  
  3. public static int  getChildMeasureSpec(int  spec,  int  padding,  int  childDimension){     
  4.     int  specMode = MeasureSpec.getMode(spec); 
  5.     int  specSize = MeasureSpec.getSize(spec); 
  6.  
  7.     int size  = Math。max (0,specSize - padding);  
  8.  
  9.     int  resultSize = 0; 
  10.     int  resultMode = 0; 
  11.  
  12.     開關(specMode){ 
  13.     //父母對 咱們 施加了一個確切的  大小 
  14.     case  MeasureSpec.EXACTLY: 
  15.         if(childDimension> = 0){ 
  16.             resultSize = childDimension; 
  17.             resultMode = MeasureSpec.EXACTLY; 
  18.         }  else  if(childDimension == LayoutParams.MATCH_PARENT){ 
  19.             //孩子要  到 是咱們的  大小。就這樣吧。 
  20.             resultSize =  size ; 
  21.             resultMode = MeasureSpec.EXACTLY; 
  22.         }  else  if(childDimension == LayoutParams.WRAP_CONTENT){ 
  23.             //孩子想  以 肯定本身的  大小。不可能 
  24.             //比咱們大 
  25.             resultSize =  size ; 
  26.             resultMode = MeasureSpec.AT_MOST; 
  27.         } 
  28.         打破; 
  29.  
  30.     //父母對 咱們 施加了最大的  尺寸 
  31.     case  MeasureSpec.AT_MOST: 
  32.         if(childDimension> = 0){ 
  33.             //孩子想要一個特定的  尺寸...就這樣吧 
  34.             resultSize = childDimension; 
  35.             resultMode = MeasureSpec.EXACTLY; 
  36.         }  else  if(childDimension == LayoutParams.MATCH_PARENT){ 
  37.             //孩子要  到 是咱們的  規模,但咱們的  規模是不 固定的。   
  38.             //約束孩子  到不 比咱們更大。  
  39.             resultSize =  size ; 
  40.             resultMode = MeasureSpec.AT_MOST; 
  41.         }  else  if(childDimension == LayoutParams.WRAP_CONTENT){ 
  42.             //孩子想  以 肯定本身的  大小。不可能 
  43.             //比咱們大 
  44.             resultSize =  size ; 
  45.             resultMode = MeasureSpec.AT_MOST; 
  46.         } 
  47.         打破; 
  48.  
  49.     //家長問  到 看到咱們想有多大  ,以 成爲 
  50.     案例 MeasureSpec.UNSPECIFIED: 
  51.         if(childDimension> = 0){ 
  52.             //孩子想要一個特定的  尺寸...讓他擁有它 
  53.             resultSize = childDimension; 
  54.             resultMode = MeasureSpec.EXACTLY; 
  55.         }  else  if(childDimension == LayoutParams.MATCH_PARENT){ 
  56.             //孩子要  到 是咱們的  規模......發現  了 它有多大應該 
  57.             // be 
  58.             resultSize =  查看.sUseZeroUnspecifiedMeasureSpec?0:  尺寸; 
  59.             resultMode = MeasureSpec.UNSPECIFIED; 
  60.         }  else  if(childDimension == LayoutParams.WRAP_CONTENT){ 
  61.             //孩子想  以 肯定本身的  大小....找到  了 如何 
  62.             //它應該是大的 
  63.             resultSize =  查看.sUseZeroUnspecifiedMeasureSpec?0:  尺寸; 
  64.             resultMode = MeasureSpec.UNSPECIFIED; 
  65.         } 
  66.         打破; 
  67.     } 
  68.     //沒有檢查ResourceType 
  69.     返回 MeasureSpec.makeMeasureSpec(resultSize,resultMode); 
  70. }  

Java的

上述代碼根據父類的MeasureSpec和自身的的LayoutParams建立子元素的MeasureSpec,具體過程同窗們自行分析,最終的建立規則以下表:

ViewGroup中在遍歷完子視圖後,須要根據子元素的測量結果來決定本身最終的測量大小,並調用setMeasuredDimension方法保存測量寬高值。

 
  1. setMeasuredDimension(resolveSizeAndState(maxWidth,widthMeasureSpec,childState),heightSizeAndState); 

Java的

這裏調用了resolveSizeAndState來肯定最終的大小,主要是保證測量的大小不能超過父容器的最大剩餘空間maxWidth,這裏咱們看看它裏面的實現:

 
  1. public static int  resolveSizeAndState(int size ,  int  measureSpec,  int  childMeasuredState){    
  2.         final  int  specMode = MeasureSpec.getMode(measureSpec); 
  3.         final  int  specSize = MeasureSpec.getSize(measureSpec); 
  4.         最終  INT  結果; 
  5.         開關(specMode){ 
  6.             case  MeasureSpec.AT_MOST: 
  7.                 if(specSize <  size ){ 
  8.                     結果= specSize | MEASURED_STATE_TOO_SMALL; 
  9.                 }  else  { 
  10.                     結果=  大小; 
  11.                 } 
  12.                 打破; 
  13.             case  MeasureSpec.EXACTLY: 
  14.                 結果= specSize; 
  15.                 打破; 
  16.             案例 MeasureSpec.UNSPECIFIED: 
  17.             默認: 
  18.                 結果=  大小; 
  19.         } 
  20.         返回 結果| (childMeasuredState&MEASURED_STATE_MASK); 
  21.     }  

Java的

關於具體的ViewGroup的onMeasure過程這裏不作分析,因爲每種佈局的測量方式不同,不可能逐個分析,但在它們的onMeasure裏面的步驟是有必定規律的:

1.根據各自的測量規則遍歷兒童元素,調用getChildMeasureSpec方法獲得孩子的measureSpec;

2.調用兒童的度量方法;

3.調用setMeasuredDimension肯定最終的大小。

查看的措施

查看的測量過程由其測量方法來完成,測量方法是一個最終的類型的方法,這意味着子類不能重寫此方法,在景觀的措施方法裏面會去調用onMeasure方法,咱們這裏只要看onMeasure的實現便可,以下:

 
  1. 查看.java 
  2.  
  3.     protected void onMeasure(int  widthMeasureSpec,  int  heightMeasureSpec){ 
  4.         setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(),widthMeasureSpec)) 
  5.                 getDefaultSize(getSuggestedMinimumHeight(),heightMeasureSpec)); 
  6.     }  

Java的

代碼很簡單,咱們繼續看看getDefaultSize方法的實現:

 
  1. 查看.java 
  2.  
  3.     public static int  getDefaultSize(int size ,  int  measureSpec){    
  4.         int  result =  size ; 
  5.         int  specMode = MeasureSpec.getMode(measureSpec); 
  6.         int  specSize = MeasureSpec.getSize(measureSpec); 
  7.  
  8.         開關(specMode){ 
  9.         案例 MeasureSpec.UNSPECIFIED: 
  10.             結果=  大小; 
  11.             打破; 
  12.         case  MeasureSpec.AT_MOST: 
  13.         case  MeasureSpec.EXACTLY: 
  14.             結果= specSize; 
  15.             打破; 
  16.         } 
  17.         返回 結果; 
  18.     } 

Java的

從上述代碼能夠得出,景觀的寬/高由specSize決定,直接繼承視圖的自定義控件須要重寫onMeasure方法並設置WRAP_CONTENT時的自身大小,不然在佈局中使用WRAP_CONTENT就至關於使用match_parent。

上述就是查看的量度大體過程,在測量完成以後,經過getMeasuredWidth /高度方法就能夠得到測量後的寬高,這個寬高通常狀況下就等於查看的最終寬高了,由於搜索的佈局佈局的時候就是根據measureWidth /高度來設置寬高的,除非在佈局中修改了測量值。

佈局佈局

佈局的做用是一個ViewGroup用來肯定子元素的位置,當ViewGroup中的位置被肯定後,它在onLayout中會遍歷全部的子元素並調用其佈局方法。簡單的來講就是,佈局方法肯定視圖自己的位置,而onLayout方法則會肯定全部子元素的位置。

先看看查看的佈局方法:

 
  1. public  void layout(int  l,  int  t,  int  r,  int  b){ 
  2.         if((mPrivateFlags3&PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT)!= 0){ 
  3.             onMeasure(mOldWidthMeasureSpec,mOldHeightMeasureSpec); 
  4.             mPrivateFlags3&=〜PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; 
  5.         } 
  6.  
  7.         int  oldL = mLeft; 
  8.         int  oldT = mTop; 
  9.         int  oldB = mBottom; 
  10.         int  oldR = mRight; 
  11.  
  12.         boolean changed = isLayoutModeOptical(mParent)? 
  13.                 setOpticalFrame(l,t,r,b):setFrame(l,t,r,b); 
  14.  
  15.         if(changed ||(mPrivateFlags&PFLAG_LAYOUT_REQUIRED)== PFLAG_LAYOUT_REQUIRED){ 
  16.             onLayout(改變,l,t,r,b); 
  17.  
  18.             if(shouldDrawRoundScrollbar()){ 
  19.                 if(mRoundScrollbarRenderer ==  null ){ 
  20.                     mRoundScrollbarRenderer = new RoundScrollbarRenderer(this); 
  21.                 } 
  22.             }  else  { 
  23.                 mRoundScrollbarRenderer =  null ; 
  24.             } 
  25.  
  26.             mPrivateFlags&=〜PFLAG_LAYOUT_REQUIRED; 
  27.  
  28.             ListenerInfo li = mListenerInfo; 
  29.             if(li!=  null  && li.mOnLayoutChangeListeners!=  null ){ 
  30.                 ArrayList <OnLayoutChangeListener> listenersCopy = 
  31.                         (ArrayList的<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone(); 
  32.                 int  numListeners = listenersCopy。size (); 
  33.                 for  (int  i = 0; i <numListeners; ++ i){ 
  34.                     listenerCopy.get(i).onLayoutChange(this,l,t,r,b,oldL,oldT,oldR,oldB); 
  35.                 } 
  36.             } 
  37.         } 
  38.  
  39.         mPrivateFlags&=〜PFLAG_FORCE_LAYOUT; 
  40.         mPrivateFlags3 | = PFLAG3_IS_LAID_OUT; 
  41.     }  

因微信字數限制,請點擊原文連接查看完整內容

總結

到這裏,查看的措施,佈局,繪製三大流程就說完了,這裏作一下總結:

  • 若是是自定義的ViewGroup的話,須要重寫onMeasure方法,在onMeasure方法裏面遍歷測量子元素,同理onLayout方法也是同樣,最後實現的onDraw方法繪製本身;
  • 若是自定義視圖的話,則須要從寫onMeasure方法,處理WRAP_CONTENT的狀況,不須要處理onLayout,最後實現的onDraw方法繪製本身; 

 

 

閱讀原文

相關文章
相關標籤/搜索