(轉)Android應用層View繪製流程與源碼分析

【工匠若水 http://blog.csdn.net/yanbober 轉載煩請註明出處,尊重分享成果】java

1 背景

還記得前面《Android應用setContentView與LayoutInflater加載解析機制源碼分析》這篇文章嗎?咱們有分析到Activity中界面加載顯示的基本流程原理,記不記得最終分析結果就是下面的關係:android

這裏寫圖片描述

看見沒有,如上圖中id爲content的內容就是整個View樹的結構,因此對每一個具體View對象的操做,其實就是個遞歸的實現。canvas

前面《Android觸摸屏事件派發機制詳解與源碼分析一(View篇)》文章的3-1小節說過Android中的任何一個佈局、任何一個控件其實都是直接或間接繼承自View實現的,固然也包括咱們後面一步一步引出的自定義控件也不例外,因此說這些View應該都具備相同的繪製流程與機制才能顯示到屏幕上(由於他們都具有相同的父類View,可能每一個控件的具體繪製邏輯有差別,可是主流程都是同樣的)。通過總結髮現每個View的繪製過程都必須經歷三個最主要的過程,也就是measure、layout和draw。c#

既然一個View的繪製主要流程是這三步,那必定有一個開始地方呀,就像一個類從main函數執行同樣呀。對於View的繪製開始調運地方這裏先給出結論,本文後面會反過來分析緣由的,先往下看就行。具體結論以下:app

整個View樹的繪圖流程是在ViewRootImpl類的performTraversals()方法(這個方法巨長)開始的,該函數作的執行過程主要是根據以前設置的狀態,判斷是否從新計算視圖大小(measure)、是否從新放置視圖的位置(layout)、以及是否重繪 (draw),其核心也就是經過判斷來選擇順序執行這三個方法中的哪一個,以下:less

private void performTraversals() { ...... //最外層的根視圖的widthMeasureSpec和heightMeasureSpec由來 //lp.width和lp.height在建立ViewGroup實例時等於MATCH_PARENT int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); ...... mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); ...... mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight()); ...... mView.draw(canvas); ...... }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
/** * Figures out the measure spec for the root view in a window based on it's * layout params. * * @param windowSize * The available width or height of the window * * @param rootDimension * The layout params for one dimension (width or height) of the * window. * * @return The measure spec to use to measure the root view. */ private static int getRootMeasureSpec(int windowSize, int rootDimension) { int measureSpec; switch (rootDimension) { case ViewGroup.LayoutParams.MATCH_PARENT: // Window can't resize. Force root view to be windowSize. measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY); break; ...... } return measureSpec; }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

能夠看見這個方法的註釋說是用來測Root View的。上面傳入參數後這個函數走的是MATCH_PARENT,使用MeasureSpec.makeMeasureSpec方法組裝一個MeasureSpec,MeasureSpec的specMode等於EXACTLY,specSize等於windowSize,也就是爲什麼根視圖老是全屏的緣由。異步

其中的mView就是View對象。以下就是整個流程的大體流程圖:ide

這裏寫圖片描述

以下咱們就依據View繪製的這三個主要流程進行詳細剖析(基於Android5.1.1 API 22源碼進行分析)。函數

【工匠若水 http://blog.csdn.net/yanbober 轉載煩請註明出處,尊重分享成果】源碼分析

2 View繪製流程第一步:遞歸measure源碼分析

整個View樹的源碼measure流程圖以下:

這裏寫圖片描述

2-1 measure源碼分析

先看下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) */ //final方法,子類不可重寫 public final void measure(int widthMeasureSpec, int heightMeasureSpec) { ...... //回調onMeasure()方法 onMeasure(widthMeasureSpec, heightMeasureSpec); ...... }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

看見註釋信息沒有,他告訴你了不少重要信息。爲整個View樹計算實際的大小,而後設置實際的高和寬,每一個View控件的實際寬高都是由父視圖和自身決定的。實際的測量是在onMeasure方法進行,因此在View的子類須要重寫onMeasure方法,這是由於measure方法是final的,不容許重載,因此View子類只能經過重載onMeasure來實現本身的測量邏輯。

這個方法的兩個參數都是父View傳遞過來的,也就是表明了父view的規格。他由兩部分組成,高16位表示MODE,定義在MeasureSpec類(View的內部類)中,有三種類型,MeasureSpec.EXACTLY表示肯定大小, MeasureSpec.AT_MOST表示最大大小, MeasureSpec.UNSPECIFIED不肯定。低16位表示size,也就是父View的大小。對於系統Window類的DecorVIew對象Mode通常都爲MeasureSpec.EXACTLY ,而size分別對應屏幕寬高。對於子View來講大小是由父View和子View共同決定的。

在這裏能夠看出measure方法最終回調了View的onMeasure方法,咱們來看下View的onMeasure源碼,以下:

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

看見沒有,其實註釋已經很詳細了(自定義View重寫該方法的指導操做註釋都有說明),不作過多解釋。

對於非ViewGroup的View而言,經過調用上面默認的onMeasure便可完成View的測量,固然你也能夠重載onMeasure並調用setMeasuredDimension來設置任意大小的佈局,但通常不這麼作,由於這種作法不太好,至於爲什麼很差,後面分析完你就明白了。

咱們能夠看見onMeasure默認的實現僅僅調用了setMeasuredDimension,setMeasuredDimension函數是一個很關鍵的函數,它對View的成員變量mMeasuredWidth和mMeasuredHeight變量賦值,measure的主要目的就是對View樹中的每一個View的mMeasuredWidth和mMeasuredHeight進行賦值,因此一旦這兩個變量被賦值意味着該View的測量工做結束。既然這樣那咱們就看看設置的默認尺寸大小吧,能夠看見setMeasuredDimension傳入的參數都是經過getDefaultSize返回的,因此再來看下getDefaultSize方法源碼,以下:

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

看見沒有,若是specMode等於AT_MOST或EXACTLY就返回specSize,這就是系統默認的規格。

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

protected int getSuggestedMinimumWidth() { return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth()); } protected int getSuggestedMinimumHeight() { return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight()); }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

看見沒有,建議的最小寬度和高度都是由View的Background尺寸與經過設置View的miniXXX屬性共同決定的。

到此一次最基礎的元素View的measure過程就完成了。上面說了View實際是嵌套的,並且measure是遞歸傳遞的,因此每一個View都須要measure。實際可以嵌套的View通常都是ViewGroup的子類,因此在ViewGroup中定義了measureChildren, measureChild, measureChildWithMargins方法來對子視圖進行測量,measureChildren內部實質只是循環調用measureChild,measureChild和measureChildWithMargins的區別就是是否把margin和padding也做爲子視圖的大小。以下咱們以ViewGroup中稍微複雜的measureChildWithMargins方法來分析:

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

關於該方法的參數等說明註釋已經描述的夠清楚了。該方法就是對父視圖提供的measureSpec參數結合自身的LayoutParams參數進行了調整,而後再來調用child.measure()方法,具體經過方法getChildMeasureSpec來進行參數調整。因此咱們繼續看下getChildMeasureSpec方法代碼,以下:

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

還記得前面《Android應用setContentView與LayoutInflater加載解析機制源碼分析》文章3-3小節探討的inflate方法加載一些佈局顯示時指定的大小失效問題嗎?當時只給出告終論,如今給出了詳細緣由分析,我想不須要再作過多解釋了吧。

至此整個View繪製流程的第一步就分析完成了,能夠看見,相對來講仍是比較複雜的,接下來進行小結。

2-2 measure原理總結

經過上面分析能夠看出measure過程主要就是從頂層父View向子View遞歸調用view.measure方法(measure中又回調onMeasure方法)的過程。具體measure核心主要有以下幾點:

  • MeasureSpec(View的內部類)測量規格爲int型,值由高16位規格模式specMode和低16位具體尺寸specSize組成。其中specMode只有三種值:
MeasureSpec.EXACTLY //肯定模式,父View但願子View的大小是肯定的,由specSize決定; MeasureSpec.AT_MOST //最多模式,父View但願子View的大小最可能是specSize指定的值; MeasureSpec.UNSPECIFIED //未指定模式,父View徹底依據子View的設計值來決定; 
  • 1
  • 2
  • 3
  • 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流程以後被調用才能返回有效值。

【工匠若水 http://blog.csdn.net/yanbober 轉載煩請註明出處,尊重分享成果】

3 View繪製流程第二步:遞歸layout源碼分析

在上面的背景介紹就說過,當ViewRootImpl的performTraversals中measure執行完成之後會接着執行mView.layout,具體以下:

private void performTraversals() { ...... mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight()); ...... }
  • 1
  • 2
  • 3
  • 4
  • 5

能夠看見layout方法接收四個參數,這四個參數分別表明相對Parent的左、上、右、下座標。並且還能夠看見左上都爲0,右下分別爲上面剛剛測量的width和height。

至此又迴歸到View的layout(int l, int t, int r, int b)方法中去實現具體邏輯了,因此接下來咱們開始分析View的layout過程。

整個View樹的layout遞歸流程圖以下:

這裏寫圖片描述

3-1 layout源碼分析

layout既然也是遞歸結構,那咱們先看下ViewGroup的layout方法,以下:

@Override public final void layout(int l, int t, int r, int b) { ...... super.layout(l, t, r, b); ...... }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

看着沒有?ViewGroup的layout方法實質仍是調運了View父類的layout方法,因此咱們看下View的layout源碼,以下:

public void layout(int l, int t, int r, int b) { ...... //實質都是調用setFrame方法把參數分別賦值給mLeft、mTop、mRight和mBottom這幾個變量 //判斷View的位置是否發生過變化,以肯定有沒有必要對當前的View進行從新layout boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); //須要從新layout if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) { //回調onLayout onLayout(changed, l, t, r, b); ...... } ...... }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

看見沒有,相似measure過程,lauout調運了onLayout方法。

對比上面View的layout和ViewGroup的layout方法能夠發現,View的layout方法是能夠在子類重寫的,而ViewGroup的layout是不能在子類重寫的,言外之意就是說ViewGroup中只能經過重寫onLayout方法。那咱們接下來看下ViewGroup的onLayout方法,以下:

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

看見沒有?ViewGroup的onLayout()方法居然是一個抽象方法,這就是說全部ViewGroup的子類都必須重寫這個方法。因此在自定義ViewGroup控件中,onLayout配合onMeasure方法一塊兒使用能夠實現自定義View的複雜佈局。自定義View首先調用onMeasure進行測量,而後調用onLayout方法動態獲取子View和子View的測量大小,而後進行layout佈局。重載onLayout的目的就是安排其children在父View的具體位置,重載onLayout一般作法就是寫一個for循環調用每個子視圖的layout(l, t, r, b)函數,傳入不一樣的參數l, t, r, b來肯定每一個子視圖在父視圖中的顯示位置。

再看下View的onLayout方法源碼,以下:

protected void onLayout(boolean changed, int left, int top, int right, int bottom) { }
  • 1
  • 2

我勒個去!是一個空方法,沒啥可看的。

既然這樣那咱們只能分析一個現有的繼承ViewGroup的控件了,就拿LinearLayout來講吧,以下是LinearLayout中onLayout的一些代碼:

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

看見沒有,LinearLayout的layout過程是分Vertical和Horizontal的,這個就是xml佈局的orientation屬性設置的,咱們爲例說明ViewGroup的onLayout重寫通常步驟就拿這裏的VERTICAL模式來解釋吧,以下是layoutVertical方法源碼:

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

從上面分析的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源碼中這些方法的實現吧,以下:

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

這也解釋了爲何有些狀況下getWidth()和getMeasuredWidth()以及getHeight()和getMeasuredHeight()會獲得不一樣的值,因此這裏不作過多解釋。

到此整個View的layout過程分析就算結束了,接下來進行一些總結工做。

3-2 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流程以後被調用才能返回有效值。

【工匠若水 http://blog.csdn.net/yanbober 轉載煩請註明出處,尊重分享成果】

4 View繪製流程第三步:遞歸draw源碼分析

在上面的背景介紹就說過,當ViewRootImpl的performTraversals中measure和layout執行完成之後會接着執行mView.layout,具體以下:

private void performTraversals() { ...... final Rect dirty = mDirty; ...... canvas = mSurface.lockCanvas(dirty); ...... mView.draw(canvas); ...... }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

draw過程也是在ViewRootImpl的performTraversals()內部調運的,其調用順序在measure()和layout()以後,這裏的mView對於Actiity來講就是PhoneWindow.DecorView,ViewRootImpl中的代碼會建立一個Canvas對象,而後調用View的draw()方法來執行具體的繪製工。因此又迴歸到了ViewGroup與View的樹狀遞歸draw過程。

先來看下View樹的遞歸draw流程圖,以下:

這裏寫圖片描述

以下咱們詳細分析這一過程。

4-1 draw源碼分析

因爲ViewGroup沒有重寫View的draw方法,因此以下直接從View的draw方法開始分析:

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

看見整個View的draw方法很複雜,可是源碼註釋也很明顯。從註釋能夠看出整個draw過程分爲了6步。源碼註釋說(」skip step 2 & 5 if possible (common case)」)第2和5步能夠跳過,因此咱們接下來重點剩餘四步。以下:

第一步,對View的背景進行繪製。

能夠看見,draw方法經過調運drawBackground(canvas);方法實現了背景繪製。咱們來看下這個方法源碼,以下:

private void drawBackground(Canvas canvas) { //獲取xml中經過android:background屬性或者代碼中setBackgroundColor()、setBackgroundResource()等方法進行賦值的背景Drawable final Drawable background = mBackground; ...... //根據layout過程肯定的View位置來設置背景的繪製區域 if (mBackgroundSizeChanged) { background.setBounds(0, 0, mRight - mLeft, mBottom - mTop); mBackgroundSizeChanged = false; rebuildOutline(); } ...... //調用Drawable的draw()方法來完成背景的繪製工做 background.draw(canvas); ...... }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

第三步,對View的內容進行繪製。

能夠看到,這裏去調用了一下View的onDraw()方法,因此咱們看下View的onDraw方法(ViewGroup也沒有重寫該方法),以下:

/** * Implement this to do your drawing. * * @param canvas the canvas on which the background will be drawn */ protected void onDraw(Canvas canvas) { }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

能夠看見,這是一個空方法。由於每一個View的內容部分是各不相同的,因此須要由子類去實現具體邏輯。

第四步,對當前View的全部子View進行繪製,若是當前的View沒有子View就不須要進行繪製。

咱們來看下View的draw方法中的dispatchDraw(canvas);方法源碼,能夠看見以下:

/** * Called by draw to draw the child views. This may be overridden * by derived classes to gain control just before its children are drawn * (but after its own view has been drawn). * @param canvas the canvas on which to draw the view */ protected void dispatchDraw(Canvas canvas) { }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

看見沒有,View的dispatchDraw()方法是一個空方法,並且註釋說明了若是View包含子類須要重寫他,因此咱們有必要看下ViewGroup的dispatchDraw方法源碼(這也就是剛剛說的對當前View的全部子View進行繪製,若是當前的View沒有子View就不須要進行繪製的緣由,由於若是是View調運該方法是空的,而ViewGroup纔有實現),以下:

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

能夠看見,ViewGroup確實重寫了View的dispatchDraw()方法,該方法內部會遍歷每一個子View,而後調用drawChild()方法,咱們能夠看下ViewGroup的drawChild方法,以下:

protected boolean drawChild(Canvas canvas, View child, long drawingTime) { return child.draw(canvas, this, drawingTime); }
  • 1
  • 2
  • 3

能夠看見drawChild()方法調運了子View的draw()方法。因此說ViewGroup類已經爲咱們重寫了dispatchDraw()的功能實現,咱們通常不須要重寫該方法,但能夠重載父類函數實現具體的功能。

第六步,對View的滾動條進行繪製。

能夠看到,這裏去調用了一下View的onDrawScrollBars()方法,因此咱們看下View的onDrawScrollBars(canvas);方法,以下:

/** * <p>Request the drawing of the horizontal and the vertical scrollbar. The * scrollbars are painted only if they have been awakened first.</p> * * @param canvas the canvas on which to draw the scrollbars * * @see #awakenScrollBars(int) */ protected final void onDrawScrollBars(Canvas canvas) { //繪製ScrollBars分析不是咱們這篇的重點,因此暫時不作分析 ...... }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

能夠看見其實任何一個View都是有(水平垂直)滾動條的,只是通常狀況下沒讓它顯示而已。

到此,View的draw繪製部分源碼分析完畢,咱們接下來進行一些總結。

4-2 draw原理總結

能夠看見,繪製過程就是把View對象繪製到屏幕上,整個draw過程須要注意以下細節:

  • 若是該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()方法提供不一樣順序。

【工匠若水 http://blog.csdn.net/yanbober 轉載煩請註明出處,尊重分享成果】

5 View的invalidate和postInvalidate方法源碼分析

你可能已經看見了,在上面分析View的三步繪製流程中最後都有調運一個叫invalidate的方法,這個方法是啥玩意?爲什麼出現頻率這麼高?很簡單,咱們拿出來分析分析不就得了。

5-1 invalidate方法源碼分析

來看一下View類中的一些invalidate方法(ViewGroup沒有重寫這些方法),以下:

/** * Mark the area defined by dirty as needing to be drawn. If the view is * visible, {@link #onDraw(android.graphics.Canvas)} will be called at some * point in the future. * <p> * This must be called from a UI thread. To call from a non-UI thread, call * {@link #postInvalidate()}. * <p> * <b>WARNING:</b> In API 19 and below, this method may be destructive to * {@code dirty}. * * @param dirty the rectangle representing the bounds of the dirty region */ //看見上面註釋沒有?public,只能在UI Thread中使用,別的Thread用postInvalidate方法,View是可見的纔有效,回調onDraw方法,針對局部View public void invalidate(Rect dirty) { final int scrollX = mScrollX; final int scrollY = mScrollY; //實質仍是調運invalidateInternal方法 invalidateInternal(dirty.left - scrollX, dirty.top - scrollY, dirty.right - scrollX, dirty.bottom - scrollY, true, false); } /** * Mark the area defined by the rect (l,t,r,b) as needing to be drawn. The * coordinates of the dirty rect are relative to the view. If the view is * visible, {@link #onDraw(android.graphics.Canvas)} will be called at some * point in the future. * <p> * This must be called from a UI thread. To call from a non-UI thread, call * {@link #postInvalidate()}. * * @param l the left position of the dirty region * @param t the top position of the dirty region * @param r the right position of the dirty region * @param b the bottom position of the dirty region */ //看見上面註釋沒有?public,只能在UI Thread中使用,別的Thread用postInvalidate方法,View是可見的纔有效,回調onDraw方法,針對局部View public void invalidate(int l, int t, int r, int b) { final int scrollX = mScrollX; final int scrollY = mScrollY; //實質仍是調運invalidateInternal方法 invalidateInternal(l - scrollX, t - scrollY, r - scrollX, b - scrollY, true, false); } /** * Invalidate the whole view. If the view is visible, * {@link #onDraw(android.graphics.Canvas)} will be called at some point in * the future. * <p> * This must be called from a UI thread. To call from a non-UI thread, call * {@link #postInvalidate()}. */ //看見上面註釋沒有?public,只能在UI Thread中使用,別的Thread用postInvalidate方法,View是可見的纔有效,回調onDraw方法,針對整個View public void invalidate() { //invalidate的實質仍是調運invalidateInternal方法 invalidate(true); } /** * This is where the invalidate() work actually happens. A full invalidate() * causes the drawing cache to be invalidated, but this function can be * called with invalidateCache set to false to skip that invalidation step * for cases that do not need it (for example, a component that remains at * the same dimensions with the same content). * * @param invalidateCache Whether the drawing cache for this view should be * invalidated as well. This is usually true for a full * invalidate, but may be set to false if the View's contents or * dimensions have not changed. */ //看見上面註釋沒有?default的權限,只能在UI Thread中使用,別的Thread用postInvalidate方法,View是可見的纔有效,回調onDraw方法,針對整個View void invalidate(boolean invalidateCache) { //實質仍是調運invalidateInternal方法 invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true); } //!!!!!!看見沒有,這是全部invalidate的終極調運方法!!!!!! void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache, boolean fullInvalidate) { ...... // Propagate the damage rectangle to the parent view. final AttachInfo ai = mAttachInfo; final ViewParent p = mParent; if (p != null && ai != null && l < r && t < b) { final Rect damage = ai.mTmpInvalRect; //設置刷新區域 damage.set(l, t, r, b); //傳遞調運Parent ViewGroup的invalidateChild方法 p.invalidateChild(this, damage); } ...... }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92

看見沒有,View的invalidate(invalidateInternal)方法實質是將要刷新區域直接傳遞給了父ViewGroup的invalidateChild方法,在invalidate中,調用父View的invalidateChild,這是一個從當前向上級父View回溯的過程,每一層的父View都將本身的顯示區域與傳入的刷新Rect作交集 。因此咱們看下ViewGroup的invalidateChild方法,源碼以下:

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

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

@Override public ViewParent invalidateChildInParent(int[] location, Rect dirty) { ...... //View調運invalidate最終層層上傳到ViewRootImpl後最終觸發了該方法 scheduleTraversals(); ...... return null; }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 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方法原理就分析完成了。

5-2 postInvalidate方法源碼分析

上面分析invalidate方法時註釋中說該方法只能在UI Thread中執行,其餘線程中須要使用postInvalidate方法,因此咱們來分析分析postInvalidate這個方法源碼。以下:

public void postInvalidate() { postInvalidateDelayed(0); }
  • 1
  • 2
  • 3

繼續看下他的調運方法postInvalidateDelayed,以下:

public void postInvalidateDelayed(long delayMilliseconds) { // We try only with the AttachInfo because there's no point in invalidating // if we are not attached to our window final AttachInfo attachInfo = mAttachInfo; //核心,實質就是調運了ViewRootImpl.dispatchInvalidateDelayed方法 if (attachInfo != null) { attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds); } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

咱們繼續看他調運的ViewRootImpl類的dispatchInvalidateDelayed方法,以下源碼:

public void dispatchInvalidateDelayed(View view, long delayMilliseconds) { Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view); mHandler.sendMessageDelayed(msg, delayMilliseconds); }
  • 1
  • 2
  • 3
  • 4

看見沒有,經過ViewRootImpl類的Handler發送了一條MSG_INVALIDATE消息,繼續追蹤這條消息的處理能夠發現:

public void handleMessage(Message msg) { ...... switch (msg.what) { case MSG_INVALIDATE: ((View) msg.obj).invalidate(); break; ...... } ...... }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

看見沒有,實質就是又在UI Thread中調運了View的invalidate();方法,那接下來View的invalidate();方法咱們就不說了,上名已經分析過了。

到此整個View的postInvalidate方法就分析完成了。

5-3 invalidate與postInvalidate方法總結

依據上面對View的invalidate分析我總結繪製以下流程圖:

這裏寫圖片描述

依據上面對View的postInvalidate分析我總結繪製以下流程圖:

這裏寫圖片描述

關於這兩個方法的具體流程和原理上面也分析過了,流程圖也給出了,相信已經很明確了,沒啥須要解釋的了。因此咱們對其作一個總體總結,概括出重點以下:

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。

5-4 經過invalidate方法分析結果回過頭去解決一個背景介紹中的疑惑

分析完invalidate後須要你回過頭去想一個問題。還記不記得這篇文章的開頭背景介紹,咱們說整個View繪製流程的最初代碼是在ViewRootImpl類的performTraversals()方法中開始的。上面當時只是告訴你了這個結論,至於這個ViewRootImpl類的performTraversals()方法爲什麼會被觸發沒有說明緣由。如今咱們就來分析一下這個觸發的源頭。

讓咱們先把大腦思考暫時挪回到《Android應用setContentView與LayoutInflater加載解析機制源碼分析》這篇博文的setContentView機制分析中(不清楚的請點擊先看這篇文章再回過頭來繼續看)。咱們先來看下那篇博文分析的PhoneWindow的setContentView方法源碼,以下:

@Override public void setContentView(View view, ViewGroup.LayoutParams params) { ...... //若是mContentParent爲空進行一些初始化,實質mContentParent是經過findViewById(ID_ANDROID_CONTENT);獲取的id爲content的FrameLayout的佈局(不清楚的請先看《Android應用setContentView與LayoutInflater加載解析機制源碼分析》文章) if (mContentParent == null) { installDecor(); } ...... //把咱們的view追加到mContentParent mContentParent.addView(view, params); ...... }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

這個方法是Activity中setContentView的實現,咱們繼續看下這個方法裏調運的addView方法,也就是ViewGroup的addView方法,以下:

public void addView(View child) { addView(child, -1); } public void addView(View child, int index) { ...... addView(child, index, params); } public void addView(View child, int index, LayoutParams params) { ...... //該方法稍後後面會詳細分析 requestLayout(); //重點關注!!! invalidate(true); ...... }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

看見addView調運invalidate方法沒有?這不就真相大白了。當咱們寫一個Activity時,咱們必定會經過setContentView方法將咱們要展現的界面傳入該方法,該方法會講咱們界面經過addView追加到id爲content的一個FrameLayout(ViewGroup)中,而後addView方法中經過調運invalidate(true)去通知觸發ViewRootImpl類的performTraversals()方法,至此遞歸繪製咱們自定義的全部佈局。

【工匠若水 http://blog.csdn.net/yanbober 轉載煩請註明出處,尊重分享成果】

6 View的requestLayout方法源碼分析

6-1 requestLayout方法分析

和invalidate相似,其實在上面分析View繪製流程時或多或少都調運到了這個方法,並且這個方法對於View來講也比較重要,因此咱們接下來分析一下他。以下View的requestLayout源碼:

public void requestLayout() { ...... if (mParent != null && !mParent.isLayoutRequested()) { //由此向ViewParent請求佈局 //從這個View開始向上一直requestLayout,最終到達ViewRootImpl的requestLayout mParent.requestLayout(); } ...... }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

看見沒有,當咱們觸發View的requestLayout時其實質就是層層向上傳遞,直到ViewRootImpl爲止,而後觸發ViewRootImpl的requestLayout方法,以下就是ViewRootImpl的requestLayout方法:

@Override public void requestLayout() { if (!mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested = true; //View調運requestLayout最終層層上傳到ViewRootImpl後最終觸發了該方法 scheduleTraversals(); } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

看見沒有,相似於上面分析的invalidate過程,只是設置的標記不一樣,致使對於View的繪製流程中觸發的方法不一樣而已。

6-2 requestLayout方法總結

能夠看見,這些方法都是大同小異。對於requestLayout方法來講總結以下:

requestLayout()方法會調用measure過程和layout過程,不會調用draw過程,也不會從新繪製任何View包括該調用者自己。

7 View繪製流程總結

至此整個關於Android應用程序開發中的View繪製機制及相關重要方法都已經分析完畢。關於各個方法的總結這裏再也不重複,直接經過該文章前面的目錄索引到相應方法的總結小節進行查閱便可。

【工匠若水 http://blog.csdn.net/yanbober 轉載煩請註明出處,尊重分享成果】

相關文章
相關標籤/搜索