Android View繪製流程




原文鏈接:http://blog.csdn.net/wangjinyu501/article/details/9008271


一、官方文檔

     先是看了一下官方的文檔, 地址是http://developer.android.com/guide/topics/ui/how-android-draws.html,它大體講解了View的繪製流程。在此翻譯一下,方便大家閱讀。

     當一個Activity接收焦點,它將被要求畫出它的佈局。Android框架將處理畫圖,但Activity必須提供根節點佈局的層次結構。
     從圖的根節點開始佈局(繪製),然後開始測量和繪製佈局樹。 通過遍歷樹和渲染來繪製每個視圖,相交無效區域。反過來,每個視圖組負責請求它的每個孩子繪製(draw() method)和每個視圖負責繪畫本身。因爲樹是遍歷順序,這意味着父母將被先繪製,孩子視圖後被繪製。
     注意:該框架將不會畫視圖中所沒有的無效區域,也會注意繪圖視圖的背景。你能強迫一個視圖來畫,通過使用 invalidate()方法。
     繪製視圖(View)包含兩個過程,一個是measure,一個是 layout。measure的過程是使用measure(int, int)方法,她也是自頂向下遍歷樹。在遞歸的過程中,每一個視圖(View)將尺寸規格放入棧中,在measure過程的最後,每個視圖存儲了它的尺寸。layout的過程與measure差不多,先是調用layout(int, int, int, int)方法,然後自上而下遍歷。在這個過程中,每個父視圖(paraent)負責定位所有的孩子的位置,這個位置的數值就是由measure過程計算出來的。
     當一個視圖(View)的的measure()返回,它的getMeasuredWidth() 和 getMeasuredHeight() 的值 就必須被設置了,以及所有這些視圖的孩子節點。一個視圖的寬度和高度值必須在父視圖約束範圍之內,這可以保證最後的 measure 都通過,所有的父母都接受所有孩子的測量。父視圖可以調用measure()方法不止一次在它的孩子上。
     在measure過程中,使用了兩個類來傳遞尺寸大小。一個是ViewGroup.LayoutParams,孩子視圖使用這個類並告訴他們的父視圖他們應該怎樣被測量和放置。一個是基本的LayoutParams,用來描述視圖的高度和寬度。它的尺寸可以有三種表示方法:1、具體數值 2、FILL_PARENT 3、WRAP_CONTENT
     對於不同的ViewGroup的子類,有着各自不同的LayoutParams。例如,RelativeLayout有它自己的LayoutParams的子類,其中包括子視圖的能力水平和垂直中心。
  MeasureSpecs用於推動需求下樹的父母和孩子。 一個MeasureSpec可以在三種模式:
  UNSPECIFIED:
  EXACTLY:具體
  AT_MOST:
 
二、基本概念以及流程
     可以看到,官方的文檔只是泛泛的說了一下,沒有具體詳細的說明整個流程,然後在網上找了一些資料,結合官方文檔來詳細說了下,對於理解這個部分,效果可能會更好。在正式介紹之前,還是需要理解與UI相關的基本概念。
      Activity:Activity包含一個Window,該Window在Activity的attach方法中通過調用PolicyManager.makeNewWindo創建;

     View:最基本的UI組件,表示屏幕上的一個矩形區域;

     DecorView:是Window中View的RootView,設置窗口屬性;

     Window:表示頂層窗口,管理界面的顯示和事件的響應;每個Activity 均會創建一個 PhoneWindow對象,是Activity和整個View系統交互的接口

     WindowManager:一個interface,繼承自ViewManager。所在應用進程的窗口管理器;有一個implementation WindowManagerImpl;主要用來管理窗口的一些狀態、屬性、view增加、刪除、更新、窗口順序、消息收集和處理等。

     ViewRoot:通過IWindowSession接口與全局窗口管理器進行交互:界面控制和消息響應;

     ActivityThread:應用程序的主線程,其中會創建關聯當前Activity與Window;創建WIndowManager實現類實例,把當前DecoView加入到WindowManager;

     Android UI的架構組成如圖:

                          

    上述架構很清晰的呈現了Activity、Window、DecorView(及其組成)、ViewRoot和WMS之間的關係,通過源碼簡單理了下從啓動Activity到創建View的過程,大致如下:

                                 

     在上圖中,performLaunchActivity函數是關鍵函數,除了新建被調用的Activity實例外,還負責確保Activity所在的應用程序啓動、讀取manifest中關於此activity設置的主題信息以及上圖中對“6.onCreate”調用也是通過對mInstrumentation.callActivityOnCreate來實現的。圖中的“8. mContentParent.addView”其實就是架構圖中phoneWindow內DecorView裏面的ContentViews,該對象是一個ViewGroup類實例。在調用AddView之後,最終就會觸發ViewRoot中的scheduleTraversals這個異步函數,從而進入ViewRoot的performTraversals函數,在performTraversals函數中就啓動了View的繪製流程。performTraversals函數在2.3.5版本源碼中就有近六百行的代碼,跟我們繪製view相關的可以抽象成如下的簡單流程圖:

                        

    上述流程主要調用了View的measure、layout和draw三個函數。

    invalidate主要給需要重繪的視圖添加DIRTY標記,並通過和父視圖的矩形運算求得真正需要繪製的區域,並保存在ViewRoot中的mDirty變量中,最後調用scheduleTraversals發起重繪請求,scheduleTraversals會發送一個異步消息,最終調用performTraversals()執行重繪,。該函數可以由應用程序調用,或者由系統函數間接調用,例如setEnable(), setSelected(), setVisiblity()都會間接調用到invalidate()來請求View樹重繪,更新View樹的顯示。注:requestLayout()和requestFocus()函數也會引起視圖重繪。

    invalidate()最後會發起一個View樹遍歷的請求,並通過執行performTraersal()來響應該請求,performTraersal()正是對View樹進行遍歷和繪製的核心函數,內部的主體邏輯是判斷是否需要重新測量視圖大小(measure),是否需要重新佈局(layout),是否重新需要繪製(draw)。measure過程是遍歷的前提,只有measure後才能進行佈局(layout)和繪製(draw),因爲在layout的過程中需要用到measure過程中計算得到的每個View的測量大小,而draw過程需要layout確定每個view的位置才能進行繪製。下面我們主要來探討一下measure的主要過程,相對與layout和draw,measure過程理解起來比較困難。

    我們在編寫layout的xml文件時會碰到layout_width和layout_height兩個屬性,對於這兩個屬性我們有三種選擇:賦值成具體的數值,match_parent或者wrap_content,而measure過程就是用來處理match_parent或者wrap_content,假如layout中規定所有View的layout_width和layout_height必須賦值成具體的數值,那麼measure其實是沒有必要的,但是google在設計Android的時候考慮加入match_parent或者wrap_content肯定是有原因的,它們會使得佈局更加靈活。

    首先我們來看幾個關鍵的函數和參數:

      1、public final void measue(int widthMeasureSpec, int heightMeasureSpec);

      2、protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec);

      3、protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec)

      4、protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec)

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

   1、measure

 接着我們來看View類中measure和onMeasure函數的源碼:

 

[html] view plaincopy
  1. <span style="font-family:SimSun;">public final void measure(int widthMeasureSpec, int heightMeasureSpec) {  
  2.         if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||  
  3.                 widthMeasureSpec != mOldWidthMeasureSpec ||  
  4.                 heightMeasureSpec != mOldHeightMeasureSpec) {  
  5.   
  6.             // first clears the measured dimension flag  
  7.             mPrivateFlags &= ~MEASURED_DIMENSION_SET;  
  8.   
  9.             if (ViewDebug.TRACE_HIERARCHY) {  
  10.                 ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_MEASURE);  
  11.             }  
  12.   
  13.             // measure ourselves, this should set the measured dimension flag back  
  14.             onMeasure(widthMeasureSpec, heightMeasureSpec);  
  15.   
  16.             // flag not set, setMeasuredDimension() was not invoked, we raise  
  17.             // an exception to warn the developer  
  18.             if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {  
  19.                 throw new IllegalStateException("onMeasure() did not set the"  
  20.                         + " measured dimension by calling"  
  21.                         + " setMeasuredDimension()");  
  22.             }  
  23.   
  24.             mPrivateFlags |= LAYOUT_REQUIRED;  
  25.         }  
  26.   
  27.         mOldWidthMeasureSpec = widthMeasureSpec;  
  28.         mOldHeightMeasureSpec = heightMeasureSpec;  
  29.     }</span>  

  

    由於函數原型中有final字段,那麼measure根本沒打算被子類繼承,也就是說measure的過程是固定的,而measure中調用了onMeasure函數,因此真正有變數的是onMeasure函數,onMeasure的默認實現很簡單,源碼如下:

[java] view plaincopy
  1. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  2.         setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),  
  3.                 getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));  
  4.     }  

    onMeasure默認的實現僅僅調用了setMeasuredDimension,setMeasuredDimension函數是一個很關鍵的函數,它對View的成員變量mMeasuredWidth和mMeasuredHeight變量賦值,而measure的主要目的就是對View樹中的每個View的mMeasuredWidth和mMeasuredHeight進行賦值,一旦這兩個變量被賦值,則意味着該View的測量工作結束。

[html] view plaincopy
  1. protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {  
  2.         mMeasuredWidth = measuredWidth;  
  3.         mMeasuredHeight = measuredHeight;  
  4.   
  5.         mPrivateFlags |= MEASURED_DIMENSION_SET;  
  6.     }  

    對於非ViewGroup的View而言,通過調用上面默認的measure——>onMeasure,即可完成View的測量,當然你也可以重載onMeasure,並調用setMeasuredDimension來設置任意大小的佈局,但一般不這麼做。

    對於ViewGroup的子類而言,往往會重載onMeasure函數負責其children的measure工作,重載時不要忘記調用setMeasuredDimension來設置自身的mMeasuredWidth和mMeasuredHeight。如果我們在layout的時候不需要依賴子視圖的大小,那麼不重載onMeasure也可以,但是必須重載onLayout來安排子視圖的位置。

    再來看下measure(int widthMeasureSpec, int heightMeasureSpec)中的兩個參數, 這兩個參數分別是父視圖提供的測量規格,當父視圖調用子視圖的measure函數對子視圖進行測量時,會傳入這兩個參數,通過這兩個參數以及子視圖本身的LayoutParams來共同決定子視圖的測量規格,在ViewGroup的measureChildWithMargins函數中體現了這個過程。

     MeasureSpec參數的值爲int型,分爲高32位和低16爲,高32位保存的是specMode,低16位表示specSize,specMode分三種:

      1、MeasureSpec.UNSPECIFIED,父視圖不對子視圖施加任何限制,子視圖可以得到任意想要的大小;

      2、MeasureSpec.EXACTLY,父視圖希望子視圖的大小是specSize中指定的大小;

      3、MeasureSpec.AT_MOST,子視圖的大小最多是specSize中的大小。

      以上施加的限制只是父視圖“希望”子視圖的大小按MeasureSpec中描述的那樣,但是子視圖的具體大小取決於多方面的。

      ViewGroup中定義了measureChildren, measureChild,  measureChildWithMargins來對子視圖進行測量,measureChildren內部只是循環調用measureChild,measureChild和measureChildWithMargins的區別就是是否把margin和padding也作爲子視圖的大小,我們主要分析measureChildWithMargins的執行過程:

[html] view plaincopy
  1. protected void measureChildWithMargins(View child,  
  2.         int parentWidthMeasureSpec, int widthUsed,  
  3.         int parentHeightMeasureSpec, int heightUsed) {  
  4.     final 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. }  

總的來看該函數就是對父視圖提供的measureSpec參數進行了調整(結合自身的LayoutParams參數),然後再來調用child.measure()函數,具體通過函數getChildMeasureSpec來進行參數調整,過程如下:

[html] view plaincopy
  1. public static int getChildMeasureSpec(int spec, int padding, int childDimension) {  
  2.         int specMode = MeasureSpec.getMode(spec);  
  3.         int specSize = MeasureSpec.getSize(spec);  
  4.   
  5.         int size = Math.max(0, specSize - padding);  
  6.   
  7.         int resultSize = 0;  
  8.         int resultMode = 0;  
  9.   
  10.         switch (specMode) {  
  11.         // Parent has imposed an exact size on us  
  12.         case MeasureSpec.EXACTLY:  
  13.             if (childDimension >= 0) {  
  14.                 resultSize = childDimension;  
  15.                 resultMode = MeasureSpec.EXACTLY;  
  16.             } else if (childDimension == LayoutParams.MATCH_PARENT) {  
  17.                 // Child wants to be our size. So be it.  
  18.                 resultSize = size;  
  19.                 resultMode = MeasureSpec.EXACTLY;  
  20.             } else if (childDimension == LayoutParams.WRAP_CONTENT) {  
  21.                 // Child wants to determine its own size. It can't be  
  22.                 // bigger than us.  
  23.                 resultSize = size;  
  24.                 resultMode = MeasureSpec.AT_MOST;  
  25.             }  
  26.             break;  
  27.   
  28.         // Parent has imposed a maximum size on us  
  29.         case MeasureSpec.AT_MOST:  
  30.             if (childDimension >= 0) {  
  31.                 // Child wants a specific size... so be it  
  32.                 resultSize = childDimension;  
  33.                 resultMode = MeasureSpec.EXACTLY;  
  34.             } else if (childDimension == LayoutParams.MATCH_PARENT) {  
  35.                 // Child wants to be our size, but our size is not fixed.  
  36.                 // Constrain child to not be bigger than us.  
  37.                 resultSize = size;  
  38.                 resultMode = MeasureSpec.AT_MOST;  
  39.             } else if (childDimension == LayoutParams.WRAP_CONTENT) {  
  40.                 // Child wants to determine its own size. It can't be  
  41.                 // bigger than us.  
  42.                 resultSize = size;  
  43.                 resultMode = MeasureSpec.AT_MOST;  
  44.             }  
  45.             break;  
  46.   
  47.         // Parent asked to see how big we want to be  
  48.         case MeasureSpec.UNSPECIFIED:  
  49.             if (childDimension >= 0) {  
  50.                 // Child wants a specific size... let him have it  
  51.                 resultSize = childDimension;  
  52.                 resultMode = MeasureSpec.EXACTLY;  
  53.             } else if (childDimension == LayoutParams.MATCH_PARENT) {  
  54.                 // Child wants to be our size... find out how big it should  
  55.                 // be  
  56.                 resultSize = 0;  
  57.                 resultMode = MeasureSpec.UNSPECIFIED;  
  58.             } else if (childDimension == LayoutParams.WRAP_CONTENT) {  
  59.                 // Child wants to determine its own size.... find out how  
  60.                 // big it should be  
  61.                 resultSize = 0;  
  62.                 resultMode = MeasureSpec.UNSPECIFIED;  
  63.             }  
  64.             break;  
  65.         }  
  66.         return MeasureSpec.makeMeasureSpec(resultSize, resultMode);  
  67.     }  
  68.    

    getChildMeasureSpec的總體思路就是通過其父視圖提供的MeasureSpec參數得到specMode和specSize,並根據計算出來的specMode以及子視圖的childDimension(layout_width和layout_height中定義的)來計算自身的measureSpec,如果其本身包含子視圖,則計算出來的measureSpec將作爲調用其子視圖measure函數的參數,同時也作爲自身調用setMeasuredDimension的參數,如果其不包含子視圖則默認情況下最終會調用onMeasure的默認實現,並最終調用到setMeasuredDimension,而該函數的參數正是這裏計算出來的。

    2、layout


    正如layout的中文意思“佈局”中表達的一樣,layout的過程就是確定View在屏幕上顯示的具體位置,在代碼中就是設置其成員變量mLeft,mTop,mRight,mBottom的值,這幾個值構成的矩形區域就是該View顯示的位置,不過這裏的具體位置都是相對與父視圖的位置。與onMeasure過程類似,ViewGroup在onLayout函數中通過調用其children的layout函數來設置子視圖相對與父視圖中的位置,具體位置由函數layout的參數決定,當我們繼承ViewGroup時必須重載onLayout函數(ViewGroup中onLayout是abstract修飾),然而onMeasure並不要求必須重載,因爲相對與layout來說,measure過程並不是必須的,具體後面會提到。首先我們來看下View.java中函數layout和onLayout的源碼:
 

[html] view plaincopy
  1. public void layout(int l, int t, int r, int b) {  
  2.         int oldL = mLeft;  
  3.         int oldT = mTop;  
  4.         int oldB = mBottom;  
  5.         int oldR = mRight;  
  6.         boolean changed = setFrame(l, t, r, b);  
  7.         if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {  
  8.             if (ViewDebug.TRACE_HIERARCHY) {  
  9.                 ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT);  
  10.             }  
  11.   
  12.             onLayout(changed, l, t, r, b);  
  13.             mPrivateFlags &= ~LAYOUT_REQUIRED;  
  14.   
  15.             ListenerInfo li = mListenerInfo;  
  16.             if (li != null && li.mOnLayoutChangeListeners != null) {  
  17.                 ArrayList<OnLayoutChangeListener> listenersCopy =  
  18.                         (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();  
  19.                 int numListeners = listenersCopy.size();  
  20.                 for (int i = 0; i < numListeners; ++i) {  
  21.                     listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);  
  22.                 }  
  23.             }  
  24.         }  
  25.         mPrivateFlags &= ~FORCE_LAYOUT;  
  26.     }  


    函數layout的主體過程還是很容易理解的,首先通過調用setFrame函數來對4個成員變量(mLeft,mTop,mRight,mBottom)賦值,然後回調onLayout函數,最後回調所有註冊過的listener的onLayoutChange函數。對於View來說,onLayout只是一個空實現,一般情況下我們也不需要重載該函數:

[html] view plaincopy
  1. protected void onLayout(boolean changed, int left, int top, int right, int bottom) {  
  2.     }  

    接着我們來看下ViewGroup.java中layout的源碼:

[html] view plaincopy
  1. public final void layout(int l, int t, int r, int b) {  
  2.         if (mTransition == null || !mTransition.isChangingLayout()) {  
  3.             super.layout(l, t, r, b);  
  4.         } else {  
  5.             // record the fact that we noop'd it; request layout when transition finishes  
  6.             mLayoutSuppressed = true;  
  7.         }  
  8.     }  

    super.layout(l, t, r, b)調用的即是View.java中的layout函數,相比之下ViewGroup增加了LayoutTransition的處理,LayoutTransition是用於處理ViewGroup增加和刪除子視圖的動畫效果,也就是說如果當前ViewGroup未添加LayoutTransition動畫,或者LayoutTransition動畫此刻並未運行,那麼調用super.layout(l, t, r, b),繼而調用到ViewGroup中的onLayout,否則將mLayoutSuppressed設置爲true,等待動畫完成時再調用requestLayout()。上面super.layout(l, t, r, b)會調用到ViewGroup.java中onLayout,其源碼實現如下:

[html] view plaincopy
  1. @Override  
  2. protected abstract void onLayout(boolean changed,  
  3.         int l, int t, int r, int b);  

    和前面View.java中的onLayout實現相比,唯一的差別就是ViewGroup中多了關鍵字abstract的修飾,也就是說ViewGroup類只能用來被繼承,無法實例化,並且其子類必須重載onLayout函數,而重載onLayout的目的就是安排其children在父視圖的具體位置。重載onLayout通常做法就是起一個for循環調用每一個子視圖的layout(l, t, r, b)函數,傳入不同的參數l, t, r, b來確定每個子視圖在父視圖中的顯示位置。 那layout(l, t, r, b)中的4個參數l, t, r, b如何來確定呢?聯想到之前的measure過程,measure過程的最終結果就是確定了每個視圖的mMeasuredWidth和mMeasuredHeight,這兩個參數可以簡單理解爲視圖期望在屏幕上顯示的寬和高,而這兩個參數爲layout過程提供了一個很重要的依據(但不是必須的),爲了說明這個過程,我們來看下LinearLayout的layout過程:

[html] view plaincopy
  1. void layoutVertical() {  
  2.         ……  
  3.         for (int i = 0; i < count; i++) {  
  4.             final View child = getVirtualChildAt(i);  
  5.             if (child == null) {  
  6.                 childTop += measureNullChild(i);  
  7.             } else if (child.getVisibility() != GONE) {  
  8.                 final int childWidth = child.getMeasuredWidth();  
  9.                 final int childHeight = child.getMeasuredHeight();  
  10.                 ……  
  11.                 setChildFrame(child, childLeft, childTop + getLocationOffset(child),  
  12.                         childWidth, childHeight);  
  13.                 childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);  
  14.   
  15.                 i += getChildrenSkipCount(child, i);  
  16.             }  
  17.         }  
  18.     }  
  19. private void setChildFrame(View child, int left, int top, int width, int height) {          
  20.         child.layout(left, top, left + width, top + height);  
  21.     }  

    從setChildFrame可以看到LinearLayout中的子視圖的右邊界等於left + width,下邊界等於top+height,也就是說在LinearLayout中其子視圖顯示的寬和高由measure過程來決定的,因此measure過程的意義就是爲layout過程提供視圖顯示範圍的參考值。

      layout過程必須要依靠measure計算出來的mMeasuredWidth和mMeasuredHeight來決定視圖的顯示大小嗎?事實並非如此,layout過程中的4個參數l, t, r, b完全可以由視圖設計者任意指定,而最終視圖的佈局位置和大小完全由這4個參數決定,measure過程得到的mMeasuredWidth和mMeasuredHeight提供了視圖大小的值,但我們完全可以不使用這兩個值,可見measure過程並不是必須的。
      說到這裏就不得不提getWidth()、getHeight()和getMeasuredWidth()、getMeasuredHeight()這兩對函數之間的區別,getMeasuredWidth()、getMeasuredHeight()返回的是measure過程得到的mMeasuredWidth和mMeasuredHeight的值,而getWidth()和getHeight()返回的是mRight  - mLeft和mBottom - mTop的值,看View.java中的源碼便一清二楚了:
[html] view plaincopy
  1. public final int getMeasuredWidth() {  
  2.         return mMeasuredWidth & MEASURED_SIZE_MASK;  
  3.     }  
  4. public final int getWidth() {  
  5.         return mRight - mLeft;  
  6.     }  

    這也解釋了爲什麼有些情況下getWidth()和getMeasuredWidth()以及getHeight()和getMeasuredHeight()會得到不同的值。整個layout過程比較容易理解,一般情況下layout過程會參考measure過程中計算得到的mMeasuredWidth和mMeasuredHeight來安排子視圖在父視圖中顯示的位置,但這不是必須的,measure過程得到的結果可能完全沒有實際用處,特別是對於一些自定義的ViewGroup,其子視圖的個數、位置和大小都是固定的,這時候我們可以忽略整個measure過程,只在layout函數中傳入的4個參數來安排每個子視圖的具體位置。
     3、draw

     和measure和layout一樣,draw過程也是在ViewRoot的performTraversals()的內部發起的,其調用順序在measure()和layout()之後,同樣的,performTraversals()發起的draw過程最終會調用到mView的draw()函數,這裏的mView對於Actiity來說就是PhoneWindow.DecorView。

 

     首先來看下與draw過程相關的函數

  • ViewRootImpl.draw(),僅在ViewRootImpl.performTraversals()的內部調用
  • DecorView.draw(), 上一步中的ViewRootImpl.draw()會調用到該函數,DecorView.draw()繼承自Framelayout,DecorView和FrameLayout,以及FrameLayout的父類ViewGroup都未重載draw(),而ViewGroup的父類是View類,因此DecorView.draw()調用的是View.draw()的默認實現
  • View.onDraw(),繪製View本身,自定義View往往會重載該函數來繪製View本身的內容
  • View.dispatchDraw(), View中的dispatchDraw默認是空實現,ViewGroup重載了該函數,內部會循環調用View.drawChild()來發起對子視圖的繪製,應用程序不應該重載ViewGroup,因爲該函數的默認實現代表了View的繪製流程
  • ViewGroup.drawChild(),該函數只在ViewGroup中實現,原因就是隻有ViewGroup才需要繪製child,drawChild內部又會調用View.draw()函數來完成子視圖的繪製(有可能直接調用dispatchDraw)

         先從源碼來展現draw的整體過程,當然源碼中只展現關鍵部分,首先來看performTraversals(),因爲draw過程最先是從這裏發起的:

    [html] view plaincopy
    1. private void performTraversals() {  
    2.     final View host = mView;  
    3.     ...  
    4.     host.measure(childWidthMeasureSpec, childHeightMeasureSpec);  
    5.     ...  
    6.     host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());  
    7.     ...  
    8.     draw(fullRedrawNeeded);  
    9. }  

        注意到measure和layout過程直接調用的是mView的measure和layout函數,而draw調用的是ViewRootImpl的內部draw(boolean fullRedrawNeeded)函數,再由draw(boolean fullRedrawNeeded)函數來調用mView.draw()函數,draw(boolean fullRedrawNeeded)包含draw過程的一些前期處理,通過下面的代碼可以看出調用關係:

    [html] view plaincopy
    1. private void draw(boolean fullRedrawNeeded) {  
    2.         Surface surface = mSurface;  
    3.         if (surface == null || !surface.isValid()) {  
    4.             return;  
    5.         }  
    6.     ...  
    7.         try {  
    8.          canvas.translate(0, -yoff);  
    9.          if (mTranslator != null) {  
    10.          <span style="white-space: pre;"> </span>mTranslator.translateCanvas(canvas);  
    11.          }  
    12.          canvas.setScreenDensity(scalingRequired  
    13.                    ? DisplayMetrics.DENSITY_DEVICE : 0);  
    14.          mAttachInfo.mSetIgnoreDirtyState = false;  
    15.          mView.draw(canvas);  
    16.        } finally {  
    17.         if (!mAttachInfo.mSetIgnoreDirtyState) {  
    18.         // Only clear the flag if it was not set during the mView.draw() call  
    19.         mAttachInfo.mIgnoreDirtyState = false;  
    20.          }  
    21.       }  
    22.     ...  
    23. }  

         下面將轉到mView.draw(),之前提到mView.draw()調用的就是View.java的默認實現,View類中的draw函數體現了View繪製的核心流程,因此我們下面重點來看下View.java中draw的調用流程:

    [html] view plaincopy
    1. public void draw(Canvas canvas) {  
    2.     ...  
    3.         /*  
    4.          * Draw traversal performs several drawing steps which must be executed  
    5.          * in the appropriate order:  
    6.          *  
    7.          *      1. Draw the background  
    8.          *      2. If necessary, save the canvas' layers to prepare for fading  
    9.          *      3. Draw view's content  
    10.          *      4. Draw children  
    11.          *      5. If necessary, draw the fading edges and restore layers  
    12.          *      6. Draw decorations (scrollbars for instance)  
    13.          */  
    14.   
    15.         // Step 1, draw the background, if needed  
    16.     ...  
    17.         background.draw(canvas);  
    18.     ...  
    19.         // skip step 2 & 5 if possible (common case)  
    20.     ...  
    21.         // Step 2, save the canvas' layers  
    22.     ...  
    23.         if (solidColor == 0) {  
    24.             final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;  
    25.   
    26.             if (drawTop) {  
    27.                 canvas.saveLayer(left, top, right, top + length, null, flags);  
    28.             }  
    29.     ...  
    30.         // Step 3, draw the content  
    31.         if (!dirtyOpaque) onDraw(canvas);  
    32.   
    33.         // Step 4, draw the children  
    34.         dispatchDraw(canvas);  
    35.   
    36.         // Step 5, draw the fade effect and restore layers  
    37.   
    38.         if (drawTop) {  
    39.             matrix.setScale(1, fadeHeight * topFadeStrength);  
    40.             matrix.postTranslate(left, top);  
    41.             fade.setLocalMatrix(matrix);  
    42.             canvas.drawRect(left, top, right, top + length, p);  
    43.         }  
    44.     ...  
    45.         // Step 6, draw decorations (scrollbars)  
    46.         onDrawScrollBars(canvas);  
    47.     }  

        通過閱讀上面的代碼可以知道整個繪製過程包括View的背景繪製,View本身內容的繪製,子視圖的繪製(如果包含子視圖),漸變框的繪製以及滾動條的繪製。重點要關注的是View本身內容的繪製和子視圖的繪製,即onDraw()和dispatchDraw()函數。對於View.java和ViewGroup.java,onDraw()默認都是空實現,因爲具體View本身長什麼樣子是由View的設計者來決定的,默認不顯示任何東西。

        View.java中dispatchDraw()默認爲空實現,因爲其不包含子視圖,而ViewGroup重載了dispatchDraw()來對其子視圖進行繪製,通常應用程序不應該對dispatchDraw()進行重載,其默認實現體現了View系統繪製的流程。那麼,接下來我們繼續分析下ViewGroup中dispatchDraw()的具體流程:

    [html] view plaincopy
    1. @Override  
    2.     protected void dispatchDraw(Canvas canvas) {  
    3.        ...  
    4.   
    5.         if ((flags & FLAG_USE_CHILD_DRAWING_ORDER) == 0) {  
    6.             for (int i = 0; i < count; i++) {  
    7.                 final View child = children[i];  
    8.                 if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {  
    9.                     more |= drawChild(canvas, child, drawingTime);  
    10.                 }  
    11.             }  
    12.         } else {  
    13.             for (int i = 0; i < count; i++) {  
    14.                 final View child = children[getChildDrawingOrder(count, i)];  
    15.                 if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {  
    16.                     more |= drawChild(canvas, child, drawingTime);  
    17.                 }  
    18.             }  
    19.         }  
    20.       ......  
    21.     }  

         dispatchDraw()的核心代碼就是通過for循環調用drawChild()對ViewGroup的每個子視圖進行繪製,上述代碼中如果FLAG_USE_CHILD_DRAWING_ORDER爲true,則子視圖的繪製順序通過getChildDrawingOrder來決定,默認的繪製順序即是子視圖加入ViewGroup的順序,而我們可以重載getChildDrawingOrder函數來更改默認的繪製順序,這會影響到子視圖之間的重疊關係。

          drawChild()的核心過程就是爲子視圖分配合適的cavas剪切區,剪切區的大小正是由layout過程決定的,而剪切區的位置取決於滾動值以及子視圖當前的動畫。設置完剪切區後就會調用子視圖的draw()函數進行具體的繪製,如果子視圖的包含SKIP_DRAW標識,那麼僅調用dispatchDraw(),即跳過子視圖本身的繪製,但要繪製視圖可能包含的字視圖。完成了dispatchDraw()過程後,View系統會調用onDrawScrollBars()來繪製滾動條。
  • 發表評論
    所有評論
    還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
    相關文章