自定義控件(View的繪製流程源碼解析)

一直以來咱們都是在用Andriod自帶的控件,好比TextView,Im ageView等來構建咱們的應用,有的時候並不能知足咱們的須要,接下來就來研究一下如何自定義咱們本身的控件,固然本身造新的輪子,天然先要搞懂系統自帶的輪子的實現原理,下面將從源碼的角度深刻分析view的繪製流程,進而來分析一下如何自定義新的控件:
下面首先從源碼的角度來分析一下view的繪製流程:
先看一下view樹的結構圖:
能夠看到android中任何一個佈局或控件都是直接或間接繼承於view實現的,對於咱們後面要自定義的控件天然也不例外,因此想要自定義控件首先必須得先搞明白android自帶的view佈局和控件究竟是如何繪製並顯示到屏幕上的。
一、既然要把view繪製到屏幕上,那天然首先要搞清楚繪製是從哪開始的?
整個View樹的繪圖流程是在ViewRootImpl類的performTraversals()方法開始的,這個方法很長,咱們抽取主要的代碼邏輯展現出來:
1 childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
2                 childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
3                 performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);


......
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
......
mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
......
mView.draw(canvas);
      ......
 1 /**
 2      * Figures out the measure spec for the root view in a window based on it's
 3      * layout params.
 4      *
 5      * @param windowSize
 6      *            The available width or height of the window
 7      *
 8      * @param rootDimension
 9      *            The layout params for one dimension (width or height) of the
10      *            window.
11      *
12      * @return The measure spec to use to measure the root view.
13      */
14     private static int getRootMeasureSpec(int windowSize, int rootDimension) {
15         int measureSpec;
16         switch (rootDimension) {
17 
18         case ViewGroup.LayoutParams.MATCH_PARENT:
19             // Window can't resize. Force root view to be windowSize.
20             measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
21             break;
22         case ViewGroup.LayoutParams.WRAP_CONTENT:
23             // Window can resize. Set max size for root view.
24             measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
25             break;
26         default:
27             // Window wants to be an exact size. Force root view to be that size.
28             measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
29             break;
30         }
31         return measureSpec;
32     }
能夠看到該方法內部會調用view的measure方法,getRootMeasureSpec方法中傳入了兩個參數,其中lp.width和lp.height在建立ViewGroup實例時等於MATCH_PARENT,也就是說在ViewGroup實例建立的時候就被賦值了
那這裏lp是什麼呢?    WindowManager.LayoutParams lp = mWindowAttributes;  (注: WindowManager.LayoutParams 是 WindowManager 接口的嵌套類;它繼承於 ViewGroup.LayoutParams; 它用於向WindowManager描述Window的管理策略。)實際上lp即爲屏幕參數,從getRootMeasureSpec方法的實現中能夠看出不管rootDimension是MATCH_PARENT或WRAP_CONTENT,最後都會強制將root view轉換爲全屏的,查看上面的case語句分支能夠知道經過調用makeMeasureSpec方法根據傳入的specSize:windowSize和specMode參數對MeasureSpec從新封裝,尺寸所有爲windowSize,因此根視圖老是全屏的
至此咱們也便明白了最外層根視圖的widthMeasureSpec和heightMeasureSpec是從哪裏來的了。
接下來就是對view的繪製流程,下面就是繪製流程圖:
 首先看一下measure源碼:
/**
     * <p>
     * This is called to find out how big a view should be. The parent
     * supplies constraint information in the width and height parameters.
     * </p>
     *
     * <p>
     * The actual measurement work of a view is performed in
     * {@link #onMeasure(int, int)}, called by this method. Therefore, only
     * {@link #onMeasure(int, int)} can and must be overridden by subclasses.
     * </p>
     *
     *
     * @param widthMeasureSpec Horizontal space requirements as imposed by the
     *        parent
     * @param heightMeasureSpec Vertical space requirements as imposed by the
     *        parent
     *
     * @see #onMeasure(int, int)
     */
 1 public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
 2         boolean optical = isLayoutModeOptical(this);
 3         if (optical != isLayoutModeOptical(mParent)) {
 4             Insets insets = getOpticalInsets();
 5             int oWidth  = insets.left + insets.right;
 6             int oHeight = insets.top  + insets.bottom;
 7             widthMeasureSpec  = MeasureSpec.adjust(widthMeasureSpec,  optical ? -oWidth  : oWidth);
 8             heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
 9         }
10 
11         // Suppress sign extension for the low bytes
12         long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
13         if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
14 
15         if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
16                 widthMeasureSpec != mOldWidthMeasureSpec ||
17                 heightMeasureSpec != mOldHeightMeasureSpec) {
18 
19             // first clears the measured dimension flag
20             mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
21 
22             resolveRtlPropertiesIfNeeded();
23 
24             int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 :
25                     mMeasureCache.indexOfKey(key);
26             if (cacheIndex < 0 || sIgnoreMeasureCache) {
27                 // measure ourselves, this should set the measured dimension flag back
28  onMeasure(widthMeasureSpec, heightMeasureSpec); 29                 mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
30             } else {
31                 long value = mMeasureCache.valueAt(cacheIndex);
32                 // Casting a long to int drops the high 32 bits, no mask needed
33                 setMeasuredDimensionRaw((int) (value >> 32), (int) value);
34                 mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
35             }
36 
37             // flag not set, setMeasuredDimension() was not invoked, we raise
38             // an exception to warn the developer
39             if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
40                 throw new IllegalStateException("View with id " + getId() + ": "
41                         + getClass().getName() + "#onMeasure() did not set the"
42                         + " measured dimension by calling"
43                         + " setMeasuredDimension()");
44             }
45 
46             mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
47         }
48 
49         mOldWidthMeasureSpec = widthMeasureSpec;
50         mOldHeightMeasureSpec = heightMeasureSpec;
51 
52         mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
53                 (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
54     }

仔細看measure方法聲明能夠看到該方法是final的,也就是子view不能對其進行重寫,根據註釋能夠知道:爲整個View樹計算實際的大小,而後設置實際的高和寬,每一個View控件的實際寬高都是由父視圖和自身決定的。實際的測量是在onMeasure方法進行,因此在View的子類須要重寫onMeasure方法,這是由於measure方法是final的,不容許重載,因此View子類只能經過重載onMeasure來實現本身的測量邏輯java

該方法中的兩個參數都是父類傳遞進來的,表示父類的規格;android

下面看一下View的onMeasure源碼:canvas

 1 /**
 2      * <p>
 3      * Measure the view and its content to determine the measured width and the
 4      * measured height. This method is invoked by {@link #measure(int, int)} and
 5      * should be overridden by subclasses to provide accurate and efficient
 6      * measurement of their contents.
 7      * </p>
 8      *
 9      * <p>
10      * <strong>CONTRACT:</strong> When overriding this method, you
11      * <em>must</em> call {@link #setMeasuredDimension(int, int)} to store the
12      * measured width and height of this view. Failure to do so will trigger an
13      * <code>IllegalStateException</code>, thrown by
14      * {@link #measure(int, int)}. Calling the superclass'
15      * {@link #onMeasure(int, int)} is a valid use.
16      * </p>
17      *
18      * <p>
19      * The base class implementation of measure defaults to the background size,
20      * unless a larger size is allowed by the MeasureSpec. Subclasses should
21      * override {@link #onMeasure(int, int)} to provide better measurements of
22      * their content.
23      * </p>
24      *
25      * <p>
26      * If this method is overridden, it is the subclass's responsibility to make
27      * sure the measured height and width are at least the view's minimum height
28      * and width ({@link #getSuggestedMinimumHeight()} and
29      * {@link #getSuggestedMinimumWidth()}).
30      * </p>
31      *
32      * @param widthMeasureSpec horizontal space requirements as imposed by the parent.
33      *                         The requirements are encoded with
34      *                         {@link android.view.View.MeasureSpec}.
35      * @param heightMeasureSpec vertical space requirements as imposed by the parent.
36      *                         The requirements are encoded with
37      *                         {@link android.view.View.MeasureSpec}.
38      *
39      * @see #getMeasuredWidth()
40      * @see #getMeasuredHeight()
41      * @see #setMeasuredDimension(int, int)
42      * @see #getSuggestedMinimumHeight()
43      * @see #getSuggestedMinimumWidth()
44      * @see android.view.View.MeasureSpec#getMode(int)
45      * @see android.view.View.MeasureSpec#getSize(int)
46      */
47     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
48         setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
49                 getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
50     }
View Code

代碼中的註釋對於onMeasure的重寫有詳細的說明,該方法是由measure觸發的,子類必須重寫以提供精確而有效的測量c#

當重寫該方法時必須調用setMeasuredDimension(int, int)來存儲這個view測量的寬和高,不然會報異常,調用超類的onMeasure方法能夠正常運行,不過是按照系統即超類的測量方式進行的app

對於非ViewGroup的view,經過調用上面默認的onMeasure就能夠實現view的測量,也能夠重載onMeasure並調用setMeasuredDimension來設置任意大小的佈局,但建議通常不這樣作,後面會分析緣由less

從上面的代碼能夠看出,默認的onMeasure只實現了調用setMeasuredDimension方法,該方法對View的成員變量mMeasuredWidth和mMeasuredHeight變量賦值,measure的主要目的就是對View樹中的每一個View的mMeasuredWidth和mMeasuredHeight進行賦值,因此一旦這兩個變量被賦值意味着該View的測量工做結束異步

默認實現中參數是經過調用getDefaultSize方法來傳遞的,接下來看一下該方法的源碼:ide

 1     public static int getDefaultSize(int size, int measureSpec) {
 2         int result = size;
 3         //經過MeasureSpec解析獲取mode與size
 4         int specMode = MeasureSpec.getMode(measureSpec);
 5         int specSize = MeasureSpec.getSize(measureSpec);
 6 
 7         switch (specMode) {
 8         case MeasureSpec.UNSPECIFIED:
 9             result = size;
10             break;
11         case MeasureSpec.AT_MOST:
12         case MeasureSpec.EXACTLY:
13             result = specSize;
14             break;
15         }
16         return result;
17     }

能夠看到若是specMode等於AT_MOST或EXACTLY就返回specSize,這是系統默認的格式函數

回過頭繼續看上面onMeasure方法,其中getDefaultSize參數的widthMeasureSpec和heightMeasureSpec都是由父View傳遞進來的。getSuggestedMinimumWidth與getSuggestedMinimumHeight都是View的方法,具體以下:源碼分析

 1  /**
 2      * Returns the suggested minimum width that the view should use. This
 3      * returns the maximum of the view's minimum width)
 4      * and the background's minimum width
 5      *  ({@link android.graphics.drawable.Drawable#getMinimumWidth()}).
 6      * <p>
 7      * When being used in {@link #onMeasure(int, int)}, the caller should still
 8      * ensure the returned width is within the requirements of the parent.
 9      *
10      * @return The suggested minimum width of the view.
11      */
12     protected int getSuggestedMinimumWidth() {
13         return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
14     }
15 
16 
17 protected int getSuggestedMinimumHeight() {
18         return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
19 
20     }

能夠看到:建議的最小寬度和高度都是由View的Background尺寸與經過設置View的miniXXX屬性共同決定的

到這裏就完成了view的measure過程

 對於一個界面可能包括不少次measure,由於對於一個佈局而言,通常會包括不少子view,每一個子視圖都須要measure,根據view的繼承結構能夠知道能夠嵌套的view都是繼承於ViewGroup的,故而在ViewGroup中定義了不少方法:measureChildren, measureChild, measureChildWithMargins,來對子視圖進行測量,measureChildren內部實質只是循環調用measureChild,measureChild和measureChildWithMargins的區別就是是否把margin和padding也做爲子視圖的大小

下面首先來看一下measureChildren的源碼:

 1 protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
 2     final int size = mChildrenCount;
 3     final View[] children = mChildren;
 4     for (int i = 0; i < size; ++i) {
 5         final View child = children[i];
 6         if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
 7  measureChild(child, widthMeasureSpec, heightMeasureSpec);
 8         }
 9     }
10 }

能夠看到循環遍歷子視圖,而後第7行調用measureChild方法,和上面講的一致,接下來先來看一下measureChild的源碼:

1 protected void measureChild(View child, int parentWidthMeasureSpec,
2         int parentHeightMeasureSpec) {
3     final LayoutParams lp = child.getLayoutParams();
4     final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
5             mPaddingLeft + mPaddingRight, lp.width);
6     final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
7             mPaddingTop + mPaddingBottom, lp.height);
8  child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
9 }

能夠看到,在第4行和第6行分別調用了getChildMeasureSpec()方法來去計算子視圖的MeasureSpec,計算的依據就是佈局文件中定義的MATCH_PARENT、WRAP_CONTENT等值,這個方法的內部細節就再也不貼出;而後在第8行調用子視圖的measure方法,並把計算出的MeasureSpec傳遞進去,這樣就和前面分析單獨view的測量過程是同樣的了,至此就能夠明白整個測量過程了,接着再來看一下measureChilldWithMargins源碼:

 1  /**
 2      * Ask one of the children of this view to measure itself, taking into
 3      * account both the MeasureSpec requirements for this view and its padding
 4      * and margins. The child must have MarginLayoutParams The heavy lifting is
 5      * done in getChildMeasureSpec.
 6      *
 7      * @param child The child to measure
 8      * @param parentWidthMeasureSpec The width requirements for this view
 9      * @param widthUsed Extra space that has been used up by the parent
10      *        horizontally (possibly by other children of the parent)
11      * @param parentHeightMeasureSpec The height requirements for this view
12      * @param heightUsed Extra space that has been used up by the parent
13      *        vertically (possibly by other children of the parent)
14      */
15     protected void measureChildWithMargins(View child,
16             int parentWidthMeasureSpec, int widthUsed,
17             int parentHeightMeasureSpec, int heightUsed) {
18         //獲取子視圖的LayoutParams
19         final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
20         //調整MeasureSpec
21         //經過這兩個參數以及子視圖自己的LayoutParams來共同決定子視圖的測量規格
22         final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
23                 mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin 24                         + widthUsed, lp.width);
25         final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
26                 mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
27                         + heightUsed, lp.height);
28         //調運子View的measure方法,子View的measure中會回調子View的onMeasure方法
29         child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
30     }

能夠看出和上面的measureChild方法相似,惟一不一樣的就是加上了左右邊距margin,剛纔前面沒有對getChildMeasureSpec方法源碼進行分析,這裏爲了全面理解,仍是來查看一下源碼:

 1   public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
 2         //獲取當前Parent View的Mode和Size
 3         int specMode = MeasureSpec.getMode(spec);
 4         int specSize = MeasureSpec.getSize(spec);
 5         //獲取Parent size與padding差值(也就是Parent剩餘大小),若差值小於0直接返回0
 6         int size = Math.max(0, specSize - padding);
 7         //定義返回值存儲變量
 8         int resultSize = 0;
 9         int resultMode = 0;
10         //依據當前Parent的Mode進行switch分支邏輯
11         switch (specMode) {
12         // Parent has imposed an exact size on us
13         //默認Root View的Mode就是EXACTLY
14         case MeasureSpec.EXACTLY:
15             if (childDimension >= 0) {
16                 //若是child的layout_width屬性在xml或者java中給予具體大於等於0的數值
17                 //設置child的size爲真實layout_width屬性值,mode爲EXACTLY
18                 resultSize = childDimension;
19                 resultMode = MeasureSpec.EXACTLY;
20             } else if (childDimension == LayoutParams.MATCH_PARENT) {
21                 //若是child的layout_width屬性在xml或者java中給予MATCH_PARENT
22                 // Child wants to be our size. So be it.
23                 //設置child的size爲size,mode爲EXACTLY
24                 resultSize = size;
25                 resultMode = MeasureSpec.EXACTLY;
26             } else if (childDimension == LayoutParams.WRAP_CONTENT) {
27                 //若是child的layout_width屬性在xml或者java中給予WRAP_CONTENT
28                 //設置child的size爲size,mode爲AT_MOST
29                 // Child wants to determine its own size. It can't be
30                 // bigger than us.
31                 resultSize = size;
32                 resultMode = MeasureSpec.AT_MOST;
33             }
34             break;
35         ......
36         //其餘Mode分支相似
37         }
38         //將mode與size經過MeasureSpec方法整合爲32位整數返回
39         return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
40     }

能夠看見,getChildMeasureSpec的邏輯是經過其父View提供的MeasureSpec參數獲得specMode和specSize,而後根據計算出來的specMode以及子View的childDimension(layout_width或layout_height)來計算自身的measureSpec,若是其自己包含子視圖,則計算出來的measureSpec將做爲調用其子視圖measure函數的參數,同時也做爲自身調用setMeasuredDimension的參數,若是其不包含子視圖則默認狀況下最終會調用onMeasure的默認實現,並最終調用到setMeasuredDimension。

因此能夠看見onMeasure的參數其實就是這麼計算出來的。同時從上面的分析能夠看出來,最終決定View的measure大小是View的setMeasuredDimension方法,因此咱們能夠經過setMeasuredDimension設定死值來設置View的mMeasuredWidth和mMeasuredHeight的大小,可是一個好的自定義View應該會根據子視圖的measureSpec來設置mMeasuredWidth和mMeasuredHeight的大小,這樣的靈活性更大,因此這也就是上面分析onMeasure時說View的onMeasure最好不要重寫死值的緣由

能夠看見當經過setMeasuredDimension方法最終設置完成View的measure以後View的mMeasuredWidth和mMeasuredHeight成員纔會有具體的數值,因此若是咱們自定義的View或者使用現成的View想經過getMeasuredWidth()和getMeasuredHeight()方法來獲取View測量的寬高,必須保證這兩個方法在onMeasure流程以後被調用才能返回有效值

 

經過上面的分析,其實view的measure過程就是從頂層向下依次測量子視圖的過程,即調用子視圖的measure方法(measure又會回調onMeasure方法)。

核心點:

  • MeasureSpec(View的內部類)測量規格爲int型,值由高16位規格模式specMode和低16位具體尺寸specSize組成。其中specMode只有三種值:
MeasureSpec.EXACTLY //肯定模式,父View但願子View的大小是肯定的,由specSize決定; MeasureSpec.AT_MOST //最多模式,父View但願子View的大小最可能是specSize指定的值; MeasureSpec.UNSPECIFIED //未指定模式,父View徹底依據子View的設計值來決定; 
  • View的measure方法是final的,不容許重載,View子類只能重載onMeasure來完成本身的測量邏輯。

  • 最頂層DecorView測量時的MeasureSpec是由ViewRootImpl中getRootMeasureSpec方法肯定的(LayoutParams寬高參數均爲MATCH_PARENT,specMode是EXACTLY,specSize爲物理屏幕大小)。

  • ViewGroup類提供了measureChild,measureChild和measureChildWithMargins方法,簡化了父子View的尺寸計算。

  • 只要是ViewGroup的子類就必需要求LayoutParams繼承子MarginLayoutParams,不然沒法使用layout_margin參數。

  • View的佈局大小由父View和子View共同決定。

  • 使用View的getMeasuredWidth()和getMeasuredHeight()方法來獲取View測量的寬高,必須保證這兩個方法在onMeasure流程以後被調用才能返回有效值。

由上綜述即分析完了整個測量過程

 接下來咱們來分析layout的過程:

首先大體說一下layout的執行過程:子視圖的具體位置是相對於父視圖的。View的onLayout是一個空方法,而ViewGroup的onLayout是一個抽象方法,因此若是自定義的View要繼承於ViewGroup就必須重寫該方法

由最開始的ViewRootImpl的performTraversals方法中能夠看出在measure執行完畢後就會執行layout:

mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());

 

這裏先來看一下layout的源碼:

 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  onLayout(changed, l, t, r, b); 12         mPrivateFlags &= ~LAYOUT_REQUIRED;
13         if (mOnLayoutChangeListeners != null) {
14             ArrayList<OnLayoutChangeListener> listenersCopy =
15                     (ArrayList<OnLayoutChangeListener>) mOnLayoutChangeListeners.clone();
16             int numListeners = listenersCopy.size();
17             for (int i = 0; i < numListeners; ++i) {
18                 listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
19             }
20         }
21     }
22     mPrivateFlags &= ~FORCE_LAYOUT;
23 }

第6行調用setFrame方法,傳入新的view座標參數,返回一個boolean值,用來決定是否改變了,若是改變了就須要從新佈局,第11行調用onLayout,到這裏你會發現和上面分析measure的過程很相似,也是會回調,接着咱們就來看一下onLayout的源碼,結果會發現方法實現爲空:由於onLayout()過程是爲了肯定視圖在佈局中所在的位置,而這個操做應該是由佈局來完成的,即父視圖決定子視圖的顯示位置

那咱們再來看一下ViewGroup的onLayout方法實現,你會發現是個抽象方法

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

因此ViewGroup的全部子視圖都必須重寫該方法,事實上,LinearLayout和RelativeLayout都是重寫了這個方法,而後在內部按照各自的規則對子視圖進行佈局的

那既然這樣,咱們要知道onLayout的編寫實現就須要來參考一下系統控件的實現了,這裏以LinearLayout爲例:

 1 @RemoteView
 2 public class LinearLayout extends ViewGroup {
 3 
 4 @Override
 5     protected void onLayout(boolean changed, int l, int t, int r, int b) {
 6         if (mOrientation == VERTICAL) {
 7             layoutVertical(l, t, r, b);
 8         } else {
 9             layoutHorizontal(l, t, r, b);
10         }
11     }
12     .....
13 }

能夠看到對於LinearLayout的onLayout方法內部會根據佈局方向來調用不一樣的方法,這也正說明了咱們再使用LinearLayout時設置方向了

這裏咱們就來分析Vertical的狀況吧:

 1 /**
 2      * Position the children during a layout pass if the orientation of this
 3      * LinearLayout is set to {@link #VERTICAL}.
 4      *
 5      * @see #getOrientation()
 6      * @see #setOrientation(int)
 7      * @see #onLayout(boolean, int, int, int, int)
 8      * @param left
 9      * @param top
10      * @param right
11      * @param bottom
12      */
13     void layoutVertical(int left, int top, int right, int bottom) {
14         final int paddingLeft = mPaddingLeft;
15 
16         int childTop;
17         int childLeft;
18         
19         // Where right end of child should go   計算父窗口推薦的子View寬度
20         final int width = right - left;
21         int childRight = width - mPaddingRight;  //計算父窗口推薦的子View右側位置
22         
23         // Space available for child    child可以使用空間大小
24         int childSpace = width - paddingLeft - mPaddingRight;
25         // 經過ViewGroup的getChildCount方法獲取ViewGroup的子View個數
26         final int count = getVirtualChildCount();
27      // 獲取Gravity屬性設置
28         final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
29         final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
30         //依據majorGravity計算childTop的位置值
31         switch (majorGravity) {
32            case Gravity.BOTTOM:
33                // mTotalLength contains the padding already
34                childTop = mPaddingTop + bottom - top - mTotalLength;
35                break;
36 
37                // mTotalLength contains the padding already
38            case Gravity.CENTER_VERTICAL:
39                childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;
40                break;
41 
42            case Gravity.TOP:
43            default:
44                childTop = mPaddingTop;
45                break;
46         }
47         //開始遍歷整個ViewGroup
48         for (int i = 0; i < count; i++) {
49             final View child = getVirtualChildAt(i);
50             if (child == null) {
51                 childTop += measureNullChild(i);
52             } else if (child.getVisibility() != GONE) {//LinearLayout中其子視圖顯示的寬和高由measure過程來決定的
53                 final int childWidth = child.getMeasuredWidth();
54                 final int childHeight = child.getMeasuredHeight();
55                 //獲取子View的LayoutParams
56                 final LinearLayout.LayoutParams lp =
57                         (LinearLayout.LayoutParams) child.getLayoutParams();
58                 
59                 int gravity = lp.gravity;
60                 if (gravity < 0) {
61                     gravity = minorGravity;
62                 }
63                 final int layoutDirection = getLayoutDirection();
64                 final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
65                 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {  //依據不一樣的absoluteGravity計算childLeft位置
66                     case Gravity.CENTER_HORIZONTAL:
67                         childLeft = paddingLeft + ((childSpace - childWidth) / 2)
68                                 + lp.leftMargin - lp.rightMargin;
69                         break;
70 
71                     case Gravity.RIGHT:
72                         childLeft = childRight - childWidth - lp.rightMargin;
73                         break;
74 
75                     case Gravity.LEFT:
76                     default:
77                         childLeft = paddingLeft + lp.leftMargin;
78                         break;
79                 }
80 
81                 if (hasDividerBeforeChildAt(i)) {
82                     childTop += mDividerHeight;
83                 }
84 
85                 childTop += lp.topMargin;////經過垂直排列計算調用child的layout設置child的位置
86                 setChildFrame(child, childLeft, childTop + getLocationOffset(child),
87                         childWidth, childHeight);
88                 childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
89 
90                 i += getChildrenSkipCount(child, i);
91             }
92         }
93     }

從上面分析的ViewGroup子類LinearLayout的onLayout實現代碼能夠看出,通常狀況下layout過程會參考measure過程當中計算獲得的mMeasuredWidth和mMeasuredHeight來安排子View在父View中顯示的位置,但這不是必須的,measure過程獲得的結果可能徹底沒有實際用處,特別是對於一些自定義的ViewGroup,其子View的個數、位置和大小都是固定的,這時候咱們能夠忽略整個measure過程,只在layout函數中傳入的4個參數來安排每一個子View的具體位置。

到這裏就不得不提getWidth()、getHeight()和getMeasuredWidth()、getMeasuredHeight()這兩對方法之間的區別(上面分析measure過程已經說過getMeasuredWidth()、getMeasuredHeight()必須在onMeasure以後使用纔有效)。能夠看出來getWidth()與getHeight()方法必須在layout(int l, int t, int r, int b)執行以後纔有效。那咱們看下View源碼中這些方法的實現吧,以下:

 1 public final int getMeasuredWidth() {
 2         return mMeasuredWidth & MEASURED_SIZE_MASK;
 3     }
 4 
 5     public final int getMeasuredHeight() {
 6         return mMeasuredHeight & MEASURED_SIZE_MASK;
 7     }
 8 
 9     public final int getWidth() {
10         return mRight - mLeft;
11     }
12 
13     public final int getHeight() {
14         return mBottom - mTop;
15     }
16 
17     public final int getLeft() {
18         return mLeft;
19     }
20 
21     public final int getRight() {
22         return mRight;
23     }
24 
25     public final int getTop() {
26         return mTop;
27     }
28 
29     public final int getBottom() {
30         return mBottom;
31     }

layout總結:

整個layout過程比較容易理解,從上面分析能夠看出layout也是從頂層父View向子View的遞歸調用view.layout方法的過程,即父View根據上一步measure子View所獲得的佈局大小和佈局參數,將子View放在合適的位置上。具體layout核心主要有如下幾點:

  • View.layout方法可被重載,ViewGroup.layout爲final的不可重載,ViewGroup.onLayout爲abstract的,子類必須重載實現本身的位置邏輯。

  • measure操做完成後獲得的是對每一個View經測量過的measuredWidth和measuredHeight,layout操做完成以後獲得的是對每一個View進行位置分配後的mLeft、mTop、mRight、mBottom,這些值都是相對於父View來講的。

  • 凡是layout_XXX的佈局屬性基本都針對的是包含子View的ViewGroup的,當對一個沒有父容器的View設置相關layout_XXX屬性是沒有任何意義的(前面《Android應用setContentView與LayoutInflater加載解析機制源碼分析》也有提到過)。

  • 使用View的getWidth()和getHeight()方法來獲取View測量的寬高,必須保證這兩個方法在onLayout流程以後被調用才能返回有效值

那分析了layout的具體原理,那接下來附上一個簡單的實例來看一下實際應用中如何使用onLayout:這一塊實例是引用郭大神的博文中的

 1 public class SimpleLayout extends ViewGroup {
 2 
 3     public SimpleLayout(Context context, AttributeSet attrs) {
 4         super(context, attrs);
 5     }
 6 
 7     @Override
 8     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 9         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
10         if (getChildCount() > 0) {
11             View childView = getChildAt(0);
12             measureChild(childView, widthMeasureSpec, heightMeasureSpec);
13         }
14     }
15 
16  @Override 17     protected void onLayout(boolean changed, int l, int t, int r, int b) { 18         if (getChildCount() > 0) { 19             View childView = getChildAt(0); 20             childView.layout(0, 0, childView.getMeasuredWidth(), childView.getMeasuredHeight()); 21  } 22  } 23 
24 }

上面的例子只包含一個子view,onLayout的實現也很簡單,就是判斷子視圖個數不爲空,而後獲取子視圖,根據measure測量的寬高直接調用layout方法便可

具體顯示的位置能夠自由改變layout中的參數

最後來分析draw的詳細過程:

由最開始的ViewRootImpl的performTraversals()方法能夠看出在執行完measure和layout以後就要執行draw方法了(ViewRoot中的代碼會繼續執行並建立出一個Canvas對象,而後調用View的draw()方法來執行具體的繪製工做),由前面measure和layout的比較咱們知道二者總體執行過程很類似,都會在遞歸執行measure或layout 之中回調相應的onMeasure或onLayout方法,因而到這裏咱們能夠猜測draw是否是也相似呢?實際上draw的過程要相對複雜一些,除了會調用onDraw方法以外,還有一些其餘的方法須要調用,接下來咱們就源碼來具體分析一下:
這裏咱們首先來畫一下整個view樹結構的遍歷過程:

 接下來先來看一下draw的源碼:代碼較長

  1    /**
  2      * Manually render this view (and all of its children) to the given Canvas.
  3      * The view must have already done a full layout before this function is
  4      * called.  When implementing a view, implement
  5      * {@link #onDraw(android.graphics.Canvas)} instead of overriding this method.
  6      * If you do need to override this method, call the superclass version.
  7      *
  8      * @param canvas The Canvas to which the View is rendered.
  9      */
 10     @CallSuper
 11     public void draw(Canvas canvas) {
 12         final int privateFlags = mPrivateFlags;
 13         final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
 14                 (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
 15         mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
 16 
 17         /*
 18          * Draw traversal performs several drawing steps which must be executed
 19          * in the appropriate order:
 20          *
 21          *      1. Draw the background
 22          *      2. If necessary, save the canvas' layers to prepare for fading
 23          *      3. Draw view's content
 24          *      4. Draw children
 25          *      5. If necessary, draw the fading edges and restore layers
 26          *      6. Draw decorations (scrollbars for instance)
 27          */
 28 
 29         // Step 1, draw the background, if needed
 30         int saveCount;
 31 
 32         if (!dirtyOpaque) {
 33             drawBackground(canvas);
 34         }
 35 
 36         // skip step 2 & 5 if possible (common case)
 37         final int viewFlags = mViewFlags;
 38         boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
 39         boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
 40         if (!verticalEdges && !horizontalEdges) {
 41             // Step 3, draw the content
 42             if (!dirtyOpaque) onDraw(canvas);
 43 
 44             // Step 4, draw the children
 45             dispatchDraw(canvas);
 46 
 47             // Overlay is part of the content and draws beneath Foreground
 48             if (mOverlay != null && !mOverlay.isEmpty()) {
 49                 mOverlay.getOverlayView().dispatchDraw(canvas);
 50             }
 51 
 52             // Step 6, draw decorations (foreground, scrollbars)
 53             onDrawForeground(canvas);
 54 
 55             // we're done...
 56             return;
 57         }
 58 
 59         /*
 60          * Here we do the full fledged routine...
 61          * (this is an uncommon case where speed matters less,
 62          * this is why we repeat some of the tests that have been
 63          * done above)
 64          */
 65 
 66         boolean drawTop = false;
 67         boolean drawBottom = false;
 68         boolean drawLeft = false;
 69         boolean drawRight = false;
 70 
 71         float topFadeStrength = 0.0f;
 72         float bottomFadeStrength = 0.0f;
 73         float leftFadeStrength = 0.0f;
 74         float rightFadeStrength = 0.0f;
 75 
 76         // Step 2, save the canvas' layers
 77         int paddingLeft = mPaddingLeft;
 78 
 79         final boolean offsetRequired = isPaddingOffsetRequired();
 80         if (offsetRequired) {
 81             paddingLeft += getLeftPaddingOffset();
 82         }
 83 
 84         int left = mScrollX + paddingLeft;
 85         int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
 86         int top = mScrollY + getFadeTop(offsetRequired);
 87         int bottom = top + getFadeHeight(offsetRequired);
 88 
 89         if (offsetRequired) {
 90             right += getRightPaddingOffset();
 91             bottom += getBottomPaddingOffset();
 92         }
 93 
 94         final ScrollabilityCache scrollabilityCache = mScrollCache;
 95         final float fadeHeight = scrollabilityCache.fadingEdgeLength;
 96         int length = (int) fadeHeight;
 97 
 98         // clip the fade length if top and bottom fades overlap
 99         // overlapping fades produce odd-looking artifacts
100         if (verticalEdges && (top + length > bottom - length)) {
101             length = (bottom - top) / 2;
102         }
103 
104         // also clip horizontal fades if necessary
105         if (horizontalEdges && (left + length > right - length)) {
106             length = (right - left) / 2;
107         }
108 
109         if (verticalEdges) {
110             topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength()));
111             drawTop = topFadeStrength * fadeHeight > 1.0f;
112             bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength()));
113             drawBottom = bottomFadeStrength * fadeHeight > 1.0f;
114         }
115 
116         if (horizontalEdges) {
117             leftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength()));
118             drawLeft = leftFadeStrength * fadeHeight > 1.0f;
119             rightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength()));
120             drawRight = rightFadeStrength * fadeHeight > 1.0f;
121         }
122 
123         saveCount = canvas.getSaveCount();
124 
125         int solidColor = getSolidColor();
126         if (solidColor == 0) {
127             final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;
128 
129             if (drawTop) {
130                 canvas.saveLayer(left, top, right, top + length, null, flags);
131             }
132 
133             if (drawBottom) {
134                 canvas.saveLayer(left, bottom - length, right, bottom, null, flags);
135             }
136 
137             if (drawLeft) {
138                 canvas.saveLayer(left, top, left + length, bottom, null, flags);
139             }
140 
141             if (drawRight) {
142                 canvas.saveLayer(right - length, top, right, bottom, null, flags);
143             }
144         } else {
145             scrollabilityCache.setFadeColor(solidColor);
146         }
147 
148         // Step 3, draw the content
149         if (!dirtyOpaque) onDraw(canvas);
150 
151         // Step 4, draw the children
152         dispatchDraw(canvas);
153 
154         // Step 5, draw the fade effect and restore layers
155         final Paint p = scrollabilityCache.paint;
156         final Matrix matrix = scrollabilityCache.matrix;
157         final Shader fade = scrollabilityCache.shader;
158 
159         if (drawTop) {
160             matrix.setScale(1, fadeHeight * topFadeStrength);
161             matrix.postTranslate(left, top);
162             fade.setLocalMatrix(matrix);
163             p.setShader(fade);
164             canvas.drawRect(left, top, right, top + length, p);
165         }
166 
167         if (drawBottom) {
168             matrix.setScale(1, fadeHeight * bottomFadeStrength);
169             matrix.postRotate(180);
170             matrix.postTranslate(left, bottom);
171             fade.setLocalMatrix(matrix);
172             p.setShader(fade);
173             canvas.drawRect(left, bottom - length, right, bottom, p);
174         }
175 
176         if (drawLeft) {
177             matrix.setScale(1, fadeHeight * leftFadeStrength);
178             matrix.postRotate(-90);
179             matrix.postTranslate(left, top);
180             fade.setLocalMatrix(matrix);
181             p.setShader(fade);
182             canvas.drawRect(left, top, left + length, bottom, p);
183         }
184 
185         if (drawRight) {
186             matrix.setScale(1, fadeHeight * rightFadeStrength);
187             matrix.postRotate(90);
188             matrix.postTranslate(right, top);
189             fade.setLocalMatrix(matrix);
190             p.setShader(fade);
191             canvas.drawRect(right - length, top, right, bottom, p);
192         }
193 
194         canvas.restoreToCount(saveCount);
195 
196         // Overlay is part of the content and draws beneath Foreground
197         if (mOverlay != null && !mOverlay.isEmpty()) {
198             mOverlay.getOverlayView().dispatchDraw(canvas);
199         }
200 
201         // Step 6, draw decorations (foreground, scrollbars)
202         onDrawForeground(canvas);
203     }
View Code

 代碼註釋中已經詳細描述了繪製的步驟:

/*
 18          * Draw traversal performs several drawing steps which must be executed
 19          * in the appropriate order:
 20          *
 21          *      1. Draw the background
 22          *      2. If necessary, save the canvas' layers to prepare for fading
 23          *      3. Draw view's content
 24          *      4. Draw children
 25          *      5. If necessary, draw the fading edges and restore layers
 26          *      6. Draw decorations (scrollbars for instance)
 27          */

源碼註釋中提到步驟2和5能夠跳過,那咱們接下來主要分析剩下4步:

第一步:繪製背景

主要邏輯在方法drawBackground(canvas);中,分析一下源碼:

 1  /**
 2      * Draws the background onto the specified canvas.
 3      *
 4      * @param canvas Canvas on which to draw the background
 5      */
 6     private void drawBackground(Canvas canvas) {
      //獲取xml中經過android:background屬性或者代碼中setBackgroundColor()、setBackgroundResource()等方法進行賦值的背景Drawable
7 final Drawable background = mBackground; 8 if (background == null) { 9 return; 10 } 11 //根據layout過程肯定的View位置來設置背景的繪製區域 12 setBackgroundBounds(); 13 14 // Attempt to use a display list if requested. 15 if (canvas.isHardwareAccelerated() && mAttachInfo != null 16 && mAttachInfo.mHardwareRenderer != null) { 17 mBackgroundRenderNode = getDrawableRenderNode(background, mBackgroundRenderNode); 18 19 final RenderNode renderNode = mBackgroundRenderNode; 20 if (renderNode != null && renderNode.isValid()) { 21 setBackgroundRenderNodeProperties(renderNode); 22 ((DisplayListCanvas) canvas).drawRenderNode(renderNode); 23 return; 24 } 25 } 26 27 final int scrollX = mScrollX; 28 final int scrollY = mScrollY; 29 if ((scrollX | scrollY) == 0) { 30 background.draw(canvas); 31 } else { 32 canvas.translate(scrollX, scrollY); 33 background.draw(canvas);//調用Drawable的draw()方法來完成背景的繪製工做 34 canvas.translate(-scrollX, -scrollY); 35 } 36 }

簡要分析:

這裏會先獲得一個mBGDrawable對象,而後根據layout過程肯定的視圖位置來設置背景的繪製區域,以後再調用Drawable的draw()方法來完成背景的繪製工做。那麼這個mBGDrawable對象是從哪裏來的呢?其實就是在XML中經過android:background屬性設置的圖片或顏色。固然你也能夠在代碼中經過setBackgroundColor()、setBackgroundResource()等方法進行賦值

二、對View內容的繪製

從代碼中看出就是調用onDraw(canvas)

這裏就是一個空方法,須要子類去實現,由於每一個子視圖的所要繪製的內容都不同

三、對當前View的全部子view進行繪製,若是沒有則不繪製

dispatchDraw(canvas);

這裏是個空方法,對單個view而言是沒有子view的,也就沒有實現,這裏能夠看一下ViewGroup中該方法的實現:
因爲代碼較長,這裏只抽取主要的邏輯代碼:

 1 @Override
 2     protected void dispatchDraw(Canvas canvas) {
 3         ......
 4         final int childrenCount = mChildrenCount;
 5         final View[] children = mChildren;
 6         ......
 7         for (int i = 0; i < childrenCount; i++) {
 8             ......
 9             if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
10                 more |= drawChild(canvas, child, drawingTime);
11             }
12         }
13         ......
14         // Draw any disappearing views that have animations
15         if (mDisappearingChildren != null) {
16             ......
17             for (int i = disappearingCount; i >= 0; i--) {
18                 ......
19                 more |= drawChild(canvas, child, drawingTime);
20             }
21         }
22         ......
23     }

能夠看到ViewGroup確實重寫了dispatchDraw方法,從第17行能夠看出會循環遍歷子view,而後調用drawChild方法繪製子view,而drawChild方法內部其實就是調用draw方法,至此咱們搞清楚了ViewGroup已經爲咱們重寫了dispatchDraw的功能實現,因此通常不用重寫該方法,可是能夠重載父類方法實現具體的功能

到這裏咱們能夠明白,若是咱們要讓ViewGroup繪製的話,就能夠在兩個地方進行:一、將繪製邏輯添加到重寫的dispatchDraw方法中,可是在該方法中首先要調用超類的該方法,即super.dispatchDraw(canvas);而後才能執行其餘的繪製邏輯

二、直接重寫onDraw方法,將繪製邏輯添加在該方法內

對於方法2,針對ViewGroup有一點要注意:

The second is using the same onDraw() callback as we saw before with View. Anything we draw here will be drawn before the child views, and thus will show up underneath them. 首先繪製,位於子view的下面 This can be helpful for drawing any type of dynamic backgrounds or selector states.適合繪製任何類型的動態背景和選擇器狀態

 

If you wish to put code in the onDraw() of a ViewGroup, you must also remember to enable drawing callbacks with setWillNotDraw(false). Otherwise your onDraw() method will never be triggered. This is because ViewGroups have self-drawing disabled by default.

調用onDraw以前須要調用setWillNotDraw(false)方法使能drawing回調,由於ViewGroup默認是禁止自繪的

 

四、而後就是第6步,也就是最後一步,對視圖的滾動條進行繪製。咱們要明白其實全部的view都是有滾動條的,只是通常狀況咱們都隱藏掉了

這一塊不是重點,代碼也比較多,就不作過多分析了

綜上,其實這裏的重點就是上面的第2點,對內容的繪製,也即調用onDraw。這裏面會有不少的繪製邏輯,繪製的方式主要是藉助Canvas這個類,它會做爲參數傳入到onDraw()方法中,供給每一個視圖使用

這裏咱們先總結一下draw的整個過程,而後再着重來研究一下onDraw的實現:
這裏引用工匠若水的博文:

  • 若是該View是一個ViewGroup,則須要遞歸繪製其所包含的全部子View。

  • View默認不會繪製任何內容,真正的繪製都須要本身在子類中實現。

  • View的繪製是藉助onDraw方法傳入的Canvas類來進行的

  • 區分View動畫和ViewGroup佈局動畫,前者指的是View自身的動畫,能夠經過setAnimation添加,後者是專門針對ViewGroup顯示內部子視圖時設置的動畫,能夠在xml佈局文件中對ViewGroup設置layoutAnimation屬性(譬如對LinearLayout設置子View在顯示時出現逐行、隨機、下等顯示等不一樣動畫效果)

  • 在獲取畫布剪切區(每一個View的draw中傳入的Canvas)時會自動處理掉padding,子View獲取Canvas不用關注這些邏輯,只用關心如何繪製便可。

  • 默認狀況下子View的ViewGroup.drawChild繪製順序和子View被添加的順序一致,可是你也能夠重載ViewGroup.getChildDrawingOrder()方法提供不一樣順序。

對於onDraw的簡單實用,這裏貼上一個樣例:

 1 public class MyView extends View {
 2 
 3     private Paint mPaint;
 4 
 5     public MyView(Context context, AttributeSet attrs) {
 6         super(context, attrs);
 7         mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
 8     }
 9 
10     @Override
11     protected void onDraw(Canvas canvas) {
12         mPaint.setColor(Color.YELLOW);
13         canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint);
14         mPaint.setColor(Color.BLUE);
15         mPaint.setTextSize(20);
16         String text = "Hello View";
17         canvas.drawText(text, 0, getHeight() / 2, mPaint);
18     }
19 }

至此,咱們便將測量,佈局,繪製三個大的自定義view的步驟詳細分析了一遍

 下面來分析一下invalidate方法:注:該方法只能在UI線程中調用
View的invalidate方法最終都會歸於調用invalidateInternal方法,這裏貼上源碼:

 1 void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
 2             boolean fullInvalidate) {
 3         ......
 4             // Propagate the damage rectangle to the parent view.
 5             final AttachInfo ai = mAttachInfo;
 6             final ViewParent p = mParent;
 7             if (p != null && ai != null && l < r && t < b) {
 8                 final Rect damage = ai.mTmpInvalRect;
 9                 //設置刷新區域
10                 damage.set(l, t, r, b);
11                 //傳遞調運Parent ViewGroup的invalidateChild方法
12                 p.invalidateChild(this, damage);
13             }
14             ......
15     }

第12行會調用父類的invalidateChild方法,View的invalidate(invalidateInternal)方法實質是將要刷新區域直接傳遞給了父ViewGroup的invalidateChild方法,在invalidate中,調用父View的invalidateChild,這是一個從當前向上級父View回溯的過程,每一層的父View都將本身的顯示區域與傳入的刷新Rect作交集

這裏查看一下invalidateChild的源碼:

 1 public final void invalidateChild(View child, final Rect dirty) {
 2         ViewParent parent = this;
 3         final AttachInfo attachInfo = mAttachInfo;
 4         ......
 5         do {
 6             ......
 7             //循環層層上級調運,直到ViewRootImpl會返回null
 8             parent = parent.invalidateChildInParent(location, dirty);  9             ......
10         } while (parent != null);
11     }

這個過程最後傳遞到ViewRootImpl的invalidateChildInParent方法結束,因此咱們看下ViewRootImpl的invalidateChildInParent方法

1    @Override
2     public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
3         ......
4         //View調運invalidate最終層層上傳到ViewRootImpl後最終觸發了該方法
5  scheduleTraversals(); 6         ......
7         return null;
8     }

這個ViewRootImpl類的invalidateChildInParent方法直接返回了null,也就是上面ViewGroup中說的,層層上級傳遞到ViewRootImpl的invalidateChildInParent方法結束了那個do while循環。看見這裏調運的scheduleTraversals這個方法嗎?scheduleTraversals會經過Handler的Runnable發送一個異步消息,調運doTraversal方法,而後最終調用performTraversals()執行重繪。開頭背景知識介紹說過的,performTraversals就是整個View數開始繪製的起始調運地方,因此說View調運invalidate方法的實質是層層上傳到父級,直到傳遞到ViewRootImpl後觸發了scheduleTraversals方法,而後整個View樹開始從新按照上面分析的View繪製流程進行重繪任務

到此View的invalidate方法原理就分析完成了

對於非U線程須要使用postInvalidate方法:

這裏不作具體分析了,只要明白一點就好,最後該方法的調用最終會回到UI線程中調用invalidate方法

接下來總結一下:這裏也是參考工匠若水的博文

invalidate系列方法請求重繪View樹(也就是draw方法),若是View大小沒有發生變化就不會調用layout過程,而且只繪製那些「須要重繪的」View,也就是哪一個View(View只繪製該View,ViewGroup繪製整個ViewGroup)請求invalidate系列方法,就繪製該View。

常見的引發invalidate方法操做的緣由主要有:

  • 直接調用invalidate方法.請求從新draw,但只會繪製調用者自己。
  • 觸發setSelection方法。請求從新draw,但只會繪製調用者自己。
  • 觸發setVisibility方法。 當View可視狀態在INVISIBLE轉換VISIBLE時會間接調用invalidate方法,繼而繪製該View。當View的可視狀態在INVISIBLE\VISIBLE 轉換爲GONE狀態時會間接調用requestLayout和invalidate方法,同時因爲View樹大小發生了變化,因此會請求measure過程以及draw過程,一樣只繪製須要「從新繪製」的視圖。
  • 觸發setEnabled方法。請求從新draw,但不會從新繪製任何View包括該調用者自己。
  • 觸發requestFocus方法。請求View樹的draw過程,只繪製「須要重繪」的View。
相關文章
相關標籤/搜索