前言: 不少時候,咱們在看源碼時,看的時候能夠理解其原理,可是事後不久又容易忘記,這是由於沒有留下一個印象,知道本身看了啥,可是又感受說不出來,這就是沒有概括總結致使的緣由;
那麼當前已經有不少人寫了View 的繪製流程,這裏會經過圖文的方式來進行總結!但願對你有所幫助;html
在開始以前,咱們先來看一張圖片:git
很熟悉的淘寶首頁,而Android的大部分界面都是圖形界面,這些圖形究竟是怎麼來的呢?系統是怎麼繪製出來的呢?讓咱們帶着思考繼續看下去;github
1,首先,先來看一下View的層級關係,View的最頂層是Activity,Activity裏面是PhoneWindow,而PhoneWindow裏面則是最頂層的View(DecorView),DecorView裏面包含的就是咱們肉眼能夠看到的圖形界面了,也就是上面的淘寶首頁,而繪製的起點也正是從DecorView開始的;canvas
從上面的圖能夠清楚的看出各個層級的關係,到了DecorView這一層,就是咱們最熟悉的View樹結構了;bash
那麼到了這裏,又會有一個疑問了,DrcorView裏面是怎麼將View樹繪製出來的呢?app
別急,且聽我細細道來;less
下面咱們先來看一張圖:源碼分析
這張圖詳細代表了DecorView添加到Window的過程,這裏面看到了一個很熟悉的方法,requestLayout(),這個方式是在ViewRootIml裏面調用的,來看看官方API的解釋:佈局
Called when something has changed which has invalidated the layout of a child of this view parent.post
這句話什麼意思呢? 翻譯過來的意思就是調用這個方法會致使當前視圖的子View佈局失效,也就是說調用這個方法會致使View樹的從新layout;
在DecorView調用了requestLayout()方法以後,最終會走到View的繪製流程,前面流程的源碼我這裏就不貼出來了,建議看完本身跟着源碼走一遍;
最終的繪製流程是在performTraversals()方法裏面;
private void performTraversals() {
// Ask host how big it wants to be, 執行測量操做
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
//執行佈局操做
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
//執行繪製操做
performDraw();
}
複製代碼
performTraversals()裏面的源碼很長很複雜,這裏咱們只須要關注測量,佈局,繪製這幾個方法便可;
看一下視圖繪製的流程圖:
讓咱們回憶一下以前的問題,答案如今已經很明顯了,就是系統調用ViewRootIml類裏的performTraversals()去繪製整個界面的;
什麼?這就沒了???
這位大俠,請放下你手中的刀,我還沒講完呢!
前面已經把View繪製流程的入口已經理清楚了,那麼接下來就繼續分析performTraversals()裏的調用吧;
先來看一下performMeasure()方法的源碼:
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
複製代碼
這一段源碼很簡單,就是調用了View的mesure方法,這個mView就是最頂層的DecorView,這裏傳了(childWidthMeasureSpec, childHeightMeasureSpec)這兩個參數,那麼這兩個參數是什麼意思呢?有什麼用呢?請繼續往下看;
在調用performMeasure()方法以前,會先調用getRootMeasureSpec()方法來獲取測量規格,也就是childWidthMeasureSpec, childHeightMeasureSpec這兩個參數;
看一下getRootMeasureSpec()方法的源碼:
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
// Window cant resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// Window wants to be an exact size. Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
複製代碼
在繼續深刻分析以前,先來看一下測量模式的類型:
1.MeasureSpec.EXACTLY:肯定模式,父容器但願子視圖View的大小是固定,也就是specSize大小。這裏能夠理解爲有具體的大小,好比MATCH_PARENT或者10dp這種;
2.MeasureSpec.AT_MOST:最大模式,父容器但願子視圖View的大小不超過父容器但願的大小,也就是不超過specSize大小。這裏理解爲沒有固定的大小,由子類去計算,對應WRAP_CONTENT這種;
3.MeasureSpec.UNSPECIFIED: 不肯定模式,子視圖View請求多大就是多大,父容器不限制其大小範圍,也就是size大小。這種能夠理解爲沒有對子View添加束縛,好比列表控件,RecyclerView,ListView這種;
接下來再回到getRootMeasureSpec()這個方法中,源碼根據傳進來的寬高來獲取測量的規格;
第一個case爲ViewGroup.LayoutParams.MATCH_PARENT時:
使用了MeasureSpec.EXACTLY的測量模式,也就是有具體的大小;
第二個case爲ViewGroup.LayoutParams.WRAP_CONTENT時:
使用了MeasureSpec.AT_MOST的測量模式,也就是沒有具體的大小;
第三個case爲MeasureSpec.UNSPECIFIED:
和第一個相同;
DecorView默認的寬高爲MATCH_PARENT,那麼這裏就會走第一個case去獲取測量規格,也就是說最頂層的測量規格就是從這裏獲取的;
到這裏,測量規格弄清楚了,接下來分析View的measure()方法;
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
// 判斷是否須要強制佈局,也就是會觸發從新測量
final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
// Optimize layout by avoiding an extra EXACTLY pass when the view is
// already measured as the correct size. In API 23 and below, this
// extra pass is required to make LinearLayout re-distribute weight.
// 將當前的規格和上一次測量的規格作比較,判斷是否須要從新測量
final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
|| heightMeasureSpec != mOldHeightMeasureSpec;
final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
&& MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
&& getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
final boolean needsLayout = specChanged
&& (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);
if (forceLayout || needsLayout) {
onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
複製代碼
這裏主要判斷是否須要從新測量,若是須要則調用onMeasure()去測量;
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
複製代碼
onMeasure()方法很簡單就幾行代碼,若是子類沒有重寫這個方法去測量寬高,則使用默認的方法getDefaultSize()去獲取寬高,而後再調用setMeasuredDimension()去設置View的寬高;
看一下getDefaultSize()這個方法的源碼:
public static int getDefaultSize(int size, int measureSpec) {
int result = 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;
}
複製代碼
經過寬高size和測量的規格measureSpec來計算最終的寬高,也就是說若是佈局裏面的子View沒有從新onMeasure()時,則會使用默認的方法來獲取寬高,那麼佈局使用測量模式爲MeasureSpec.EXACTLY和MeasureSpec.AT_MOST時,寬高都是返回由測量模式和具體大小計算以後的值specSize;
那麼到這裏測量的方法差很少就分析完了,可是還有一個疑問,也就是View樹是怎麼測量的呢?
接下來繼續分析;
前面分析的是子View沒有從新onMeasure()的狀況,接下來分析子View重寫了onMeasure()的狀況;
舉個熟悉的例子,LinearLayout控件是咱們最經常使用的ViewGroup控件,下面以這個爲例子來進行分析;
看一下LinearLayout控件的onMeasure()方法:
rotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
複製代碼
再看一下measureVertical的方法:
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
// See how tall everyone is. Also remember max width.
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);\
...
measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
heightMeasureSpec, usedHeight);
}
...
// Check against our minimum width
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), heightSizeAndState);
}
複製代碼
這源碼裏面經過遍歷當前的子View,而後經過measureChildBeforeLayout()去測量子View的寬高,並經過計算子View的寬高來調用setMeasuredDimension()設置LinearLayout的寬高,而測量子View 的方法裏面最終調用的是ViewGroup的measureChildWithMargins()方法;
void measureChildBeforeLayout(View child, int childIndex,
int widthMeasureSpec, int totalWidth, int heightMeasureSpec,
int totalHeight) {
measureChildWithMargins(child, widthMeasureSpec, totalWidth,
heightMeasureSpec, totalHeight);
}
複製代碼
ViewGroup裏的方法最終調用了View裏的measure方法,而ViewGroup裏面也自定義了獲取測量模式的方法getChildMeasureSpec(); 這裏細節就不過多關注了;
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
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);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
複製代碼
那麼到這裏,ViewRootIml的performMeasure()方法的流程就能夠總結爲下面這張圖:
整個View樹的測量流程就是經過這種遞歸的方式,一步步的測量完成的;
總結:
1,View樹的測量是經過遞歸的方式測量完成的,遞歸的方法爲View的measure()方法;
2,View和ViewGroup都有本身的測量模式的方法,固然子View也能夠自定義獲取測量模式的方法;
3,View樹測量結束以後,會調用setMeasuredDimension()讓以前測量的寬高設置生效,這個方法是在遞歸結束以後,經過View樹的最底層往上傳遞的;
4,子View的大小是由父視圖和子視圖共同決定的;
測量的流程已經講完了,接下來開始講佈局的流程,既然測量的流程是經過遞歸的方式,那麼佈局的流程是否是也?
是的,沒錯,也是經過遞歸的方式;
別急,且請我細細道來!
對View進行佈局的目的是計算出View的尺寸以及在其父控件中的位置,具體來講就是計算出View的四條邊界分別到其父控件左邊界、上邊界的距離,即計算View的left、top、right、bottom的值。
先來看一下performLayout()方法的源碼:
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
// 標記佈局開始
mInLayout = true;
final View host = mView;
...
try {
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
...
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
// 標記佈局結束
mInLayout = false;
}
複製代碼
這裏調用了mView的layout()方法,這個mView就是最頂層的DecorView,而layout()方法則爲View裏的方法;
看看View的layout()方法裏面作了啥?
public void layout(int l, int t, int r, int b) {
...
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
//若是isLayoutModeOptical()返回true,那麼就會執行setOpticalFrame()方法,
//不然會執行setFrame()方法。而且setOpticalFrame()內部會調用setFrame(),
//因此不管如何都會執行setFrame()方法。
//setFrame()方法會將View新的left、top、right、bottom存儲到View的成員變量中
//而且返回一個boolean值,若是返回true表示View的位置或尺寸發生了變化,
//不然表示未發生變化
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
//若是View的佈局發生了變化,或者mPrivateFlags有須要LAYOUT的標籤PFLAG_LAYOUT_REQUIRED,
//那麼就會執行如下代碼
//首先會觸發onLayout方法的執行,View中默認的onLayout方法是個空方法
//不過繼承自ViewGroup的類都須要實現onLayout方法,從而在onLayout方法中依次循環子View,
//並調用子View的layout方法
onLayout(changed, l, t, r, b);
...
}
...
}
複製代碼
這裏只須要關注setFrame()方法和onLayout()方法便可,onLayout()方法由子類實現,先來看一下setFrame()方法;
protected boolean setFrame(int left, int top, int right, int bottom) {
boolean changed = false;
if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
//將新舊left、right、top、bottom進行對比,只要不徹底相對就說明View的佈局發生了變化,
//則將changed變量設置爲true
changed = true;
...
// 分別計算View的新舊尺寸
int oldWidth = mRight - mLeft;
int oldHeight = mBottom - mTop;
int newWidth = right - left;
int newHeight = bottom - top;
// 比較View的新舊尺寸是否相同,若是尺寸發生了變化,那麼sizeChanged的值爲true
boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);
...
// 將新的left、top、right、bottom存儲到View的成員變量中
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
...
//若是View的尺寸和以前相比發生了變化,那麼就執行sizeChange()方法,
//該方法中又會調用onSizeChanged()方法,並將View的新舊尺寸傳遞進去
if (sizeChanged) {
sizeChange(newWidth, newHeight, oldWidth, oldHeight);
}
...
}
return changed;
}
複製代碼
在該方法中,會將新舊left、right、top、bottom進行對比,只要不徹底相同就說明View的佈局發生了變化,則將changed變量設置爲true。而後比較View的新舊尺寸是否相同,若是尺寸發生了變化,並將其保存到變量sizeChanged中。若是尺寸發生了變化,那麼sizeChanged的值爲true。
而後將新的left、top、right、bottom存儲到View的成員變量中保存下來。並執行mRenderNode.setLeftTopRightBottom()方法會,其會調用RenderNode中原生方法的nSetLeftTopRightBottom()方法,該方法會根據left、top、right、bottom更新用於渲染的顯示列表。
而onLayout()方法由子類實現,若是子類是View的話,則方法不須要實現,若是是ViewGroup的話,由於方法爲抽象方法,那麼必須由子類實現;這裏經過LinearLayout的onLayout()方法來進行舉例說明;
看一下LinearLayout的onLayout()方法:
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);
}
}
複製代碼
查看其中一種方法layoutVertical();
void layoutVertical(int left, int top, int right, int bottom) {
...
for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
...
}
}
}
複製代碼
經過遍歷全部的子View,調用setChildFrame()進行佈局,再看一下setChildFrame()方法的源碼;
private void setChildFrame(View child, int left, int top, int width, int height) {
child.layout(left, top, left + width, top + height);
}
複製代碼
這裏調用了View的layout()的方法來進行子View的layout;
到這裏,佈局的流程就分析完了,看一下流程圖:
總結:
1,View樹的佈局是經過遞歸的方式測量完成的,遞歸的方法爲View的layout()方法;
2,View和ViewGroup都有onLayout()方法,可是ViewGroup的方法是抽象的,必須由子類實現,View的佈局是由ViewGroup來控制的,也就是說View並不須要進行onLayout();
3,使用View的getWidth()和getHeight()方法來獲取View測量的寬高,必須保證這兩個方法在onLayout流程以後被調用才能返回有效值;
那麼到這裏,佈局的流程就已經講完了,接下來分析繪製的流程;
既然測量,和佈局都是用遞歸的方式,那繪製豈不是也?
是的,繼續往下看,理解了一個以後,其餘理解起來也不難!
最後一步的繪製會將頁面展現在咱們面前,前面的操做都只是準備工做;
先來看一下performDraw()的源碼:
private void performDraw() {
...
try {
boolean canUseAsync = draw(fullRedrawNeeded);
...
} finally {
mIsDrawing = false;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
複製代碼
這裏調用了ViewRootIml裏面的draw()方法,跟蹤源碼發現最終調用的是drawSoftware()方法;
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
try {
// 從surface裏面獲取canvas對象
canvas = mSurface.lockCanvas(dirty);
...
} catch (Surface.OutOfResourcesException e) {
handleOutOfResourcesException(e);
return false;
} catch (IllegalArgumentException e) {
...
return false;
} finally {
dirty.offset(dirtyXOffset, dirtyYOffset); // Reset to the original value.
}
try {
if (DEBUG_ORIENTATION || DEBUG_DRAW) {
Log.v(mTag, "Surface " + surface + " drawing to bitmap w="
+ canvas.getWidth() + ", h=" + canvas.getHeight());
//canvas.drawARGB(255, 255, 0, 0);
}
...
try {
...
// 調用View的draw()方法
mView.draw(canvas);
} finally {
...
}
} finally {
...
}
return true;
}
複製代碼
drawSoftware()方法裏面先從mSurface獲取canvas對象,而後經過mView調用draw()方法時,將canvas做爲參數傳進去;最後調用的是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
int saveCount;
if (!dirtyOpaque) {
drawBackground(canvas);
}
// skip step 2 & 5 if possible (common case)
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
drawAutofilledHighlight(canvas);
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
// Step 7, draw the default focus highlight
drawDefaultFocusHighlight(canvas);
if (debugDraw()) {
debugDrawFocus(canvas);
}
// we're done... return; } /* * Here we do the full fledged routine... * (this is an uncommon case where speed matters less, * this is why we repeat some of the tests that have been * done above) */ boolean drawTop = false; boolean drawBottom = false; boolean drawLeft = false; boolean drawRight = false; float topFadeStrength = 0.0f; float bottomFadeStrength = 0.0f; float leftFadeStrength = 0.0f; float rightFadeStrength = 0.0f; // Step 2, save the canvas' layers
int paddingLeft = mPaddingLeft;
final boolean offsetRequired = isPaddingOffsetRequired();
if (offsetRequired) {
paddingLeft += getLeftPaddingOffset();
}
int left = mScrollX + paddingLeft;
int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
int top = mScrollY + getFadeTop(offsetRequired);
int bottom = top + getFadeHeight(offsetRequired);
if (offsetRequired) {
right += getRightPaddingOffset();
bottom += getBottomPaddingOffset();
}
final ScrollabilityCache scrollabilityCache = mScrollCache;
final float fadeHeight = scrollabilityCache.fadingEdgeLength;
int length = (int) fadeHeight;
// clip the fade length if top and bottom fades overlap
// overlapping fades produce odd-looking artifacts
if (verticalEdges && (top + length > bottom - length)) {
length = (bottom - top) / 2;
}
// also clip horizontal fades if necessary
if (horizontalEdges && (left + length > right - length)) {
length = (right - left) / 2;
}
if (verticalEdges) {
topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength()));
drawTop = topFadeStrength * fadeHeight > 1.0f;
bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength()));
drawBottom = bottomFadeStrength * fadeHeight > 1.0f;
}
if (horizontalEdges) {
leftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength()));
drawLeft = leftFadeStrength * fadeHeight > 1.0f;
rightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength()));
drawRight = rightFadeStrength * fadeHeight > 1.0f;
}
saveCount = canvas.getSaveCount();
int solidColor = getSolidColor();
if (solidColor == 0) {
if (drawTop) {
canvas.saveUnclippedLayer(left, top, right, top + length);
}
if (drawBottom) {
canvas.saveUnclippedLayer(left, bottom - length, right, bottom);
}
if (drawLeft) {
canvas.saveUnclippedLayer(left, top, left + length, bottom);
}
if (drawRight) {
canvas.saveUnclippedLayer(right - length, top, right, bottom);
}
} else {
scrollabilityCache.setFadeColor(solidColor);
}
// 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
final Paint p = scrollabilityCache.paint;
final Matrix matrix = scrollabilityCache.matrix;
final Shader fade = scrollabilityCache.shader;
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);
}
if (drawBottom) {
matrix.setScale(1, fadeHeight * bottomFadeStrength);
matrix.postRotate(180);
matrix.postTranslate(left, bottom);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(left, bottom - length, right, bottom, p);
}
if (drawLeft) {
matrix.setScale(1, fadeHeight * leftFadeStrength);
matrix.postRotate(-90);
matrix.postTranslate(left, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(left, top, left + length, bottom, p);
}
if (drawRight) {
matrix.setScale(1, fadeHeight * rightFadeStrength);
matrix.postRotate(90);
matrix.postTranslate(right, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(right - length, top, right, bottom, p);
}
canvas.restoreToCount(saveCount);
drawAutofilledHighlight(canvas);
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
if (debugDraw()) {
debugDrawFocus(canvas);
}
}
複製代碼
從上面的源碼分析得知,View的draw()方法總共分爲6步:
1,繪製視圖的背景;
2,若是須要,保存畫布的圖層以備漸變用;
3,繪製當前視圖的內容;
4,繪製子View的視圖;
5,若是須要,繪製視圖的漸變效果並恢復畫布;
6,繪製裝飾(好比滾動條scrollbars);
總結爲流程圖以下:
這裏須要關注的是第二步和第四步,第二步是經過調用onDraw()方法繪製當前視圖的內容,第四步是調用dispatchDraw()來繪製子View的視圖;
先來看一下View的onDraw()方法:
protected void onDraw(Canvas canvas) {}
複製代碼
是一個空方法,交由子類去實現;若是實現來自定義View,那麼就得從新該方法去實現繪製的邏輯;
再看一下dispatchDraw()方法:
protected void dispatchDraw(Canvas canvas) {}
複製代碼
這個方法也是個空方法,也是要交由子類去實現;
查看源碼的實現有不少個,這裏咱們只關注ViewGroup的實現邏輯;
看一下ViewGroup的實現邏輯:
protected void dispatchDraw(Canvas canvas) {
if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) {
final boolean buildCache = !isHardwareAccelerated();
for (int i = 0; i < childrenCount; i++) {
final View child = children[i];
// 遍歷子View設置動畫效果
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
final LayoutParams params = child.getLayoutParams();
attachLayoutAnimationParameters(child, params, i, childrenCount);
bindLayoutAnimation(child);
}
}
...
}
...
for (int i = 0; i < childrenCount; i++) {
while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
final View transientChild = mTransientViews.get(transientIndex);
if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
transientChild.getAnimation() != null) {
// 遍歷子View進行繪製
more |= drawChild(canvas, transientChild, drawingTime);
}
...
}
...
}
...
}
複製代碼
源碼裏面經過遍歷全部的子View,調用drawChild()來進行繪製,繼續跟進drawChild()方法裏面;
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
複製代碼
這裏又看到了熟悉的View,調用了View的draw()方法繪製子View;
到這裏,performDraw()方法的流程就講完了,看一下總結流程圖:
總結:
1,View樹的繪製流程也是經過遞歸的方式來進行繪製的,遞歸的方法爲View的draw()方法;
2,ViewGroup和View均可以從新onDraw()方法來實現繪製的邏輯,子View不須要重寫dispatchDraw()方法;
3,繪製視圖的背景,漸變效果和裝飾都是在View的draw()方法裏面調用的;
performTraversals()方法的流程分析完畢了,如今終於知道了View的繪製流程爲何分爲onMeasure(),onLayout(),onDraw()這三個步驟了;貼出來的源碼省略了不少細節,主要是爲了把繪製的流程理清,建議能夠本身跟着源碼去走一遍;
1,www.jianshu.com/p/58d22426e…
2,blog.csdn.net/feiduclear_…
3,blog.csdn.net/luoshengyan…
4,www.jianshu.com/p/4a68f9dc8…
5,www.2cto.com/kf/201512/4…
若是個人文章對你有幫助的話,請給我點個❤️,也能夠關注一下個人Github和博客;
歡迎和我溝通交流技術;