1. 簡介
-
View的繪制過程分為三部分:measure、layout、draw。
measure用來測量View的寬和高。
layout用來計算View的位置。
draw用來繪制View。
-
measure過程可以查看這篇文章:自定義View原理篇(1)- measure過程。 -
layout過程可以查看這篇文章:自定義View原理篇(2)- layout過程。 - 本章主要對
draw過程進行詳細的分析。 - 本文源碼基于android 27。
2. draw的始點
跟measure、layout一樣,draw也是始于ViewRootImpl的performTraversals():
2.1 ViewRootImpl的performTraversals
private void performTraversals() {
//...
//獲得view寬高的測量規(guī)格,mWidth和mHeight表示窗口的寬高,lp.widthhe和lp.height表示DecorView根布局寬和高
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);//執(zhí)行測量
//...
performLayout(lp, mWidth, mHeight);//執(zhí)行布局
//...
if (!cancelDraw && !newSurface) {
if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).startChangingAnimations();
}
mPendingTransitions.clear();
}
performDraw();//執(zhí)行繪制
}
//...
}
再來看看performDraw():
2.2 ViewRootImpl的performDraw
private void performDraw() {
//...
final boolean fullRedrawNeeded = mFullRedrawNeeded;
draw(fullRedrawNeeded);
//...
}
下面重點來分析draw過程。
3.draw過程分析
draw,顧名思義,就是來繪制View。
同樣,draw過程根據(jù)View的類型也可以分為兩種情況:
- 繪制單一
View時,只需View本身即可;- 繪制
ViewGroup時,不僅需要繪制ViewGroup本身,還需繪制其所有的子View。
我們對這兩種情況分別進行分析。
3.1 單一View的draw過程
3.1.1 View的draw
單一View的draw過程是從View的draw()方法開始:
public void draw(Canvas canvas) {
//...
/*
* 繪制流程如下:
*
* 1\. 繪制view背景
* 2\. 如果有需要,就保存圖層
* 3\. 繪制view內(nèi)容
* 4\. 繪制子View
* 5\. 如果有必要,繪制漸變框和恢復(fù)圖層
* 6\. 繪制裝飾(滑動條等)
*/
if (!dirtyOpaque) {
drawBackground(canvas);//步驟1\. 繪制view背景
}
// 如果可能的話,跳過第2步和第5步(常見情況)
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
if (!dirtyOpaque) onDraw(canvas);//步驟3\. 繪制view內(nèi)容
dispatchDraw(canvas);//步驟4\. 繪制子View
//...
onDrawForeground(canvas);//步驟6\. 繪制裝飾(滑動條等)
//...
// 繪制完成,返回
return;
}
//如果有需要,會執(zhí)行第2步和第5步
//...
//步驟2\. 保存圖層
if (solidColor == 0) {
if (drawTop) {
//保存圖層
canvas.saveLayer(left, top, right, top + length, null, flags);
}
//...
}
if (!dirtyOpaque) onDraw(canvas);//步驟3\. 繪制view內(nèi)容
dispatchDraw(canvas);//步驟4\. 繪制子View
//步驟5\. 繪制漸變框和恢復(fù)圖層
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);
}
//...
//恢復(fù)圖層
canvas.restoreToCount(saveCount);
//...
onDrawForeground(canvas);//步驟6\. 繪制裝飾(滑動條等)
//...
}
可以看到,中間繪制時可能會跳過第2步和第5步,這樣可以提高繪制的效率。
下面我們來分析一下drawBackground()、onDraw()、dispatchDraw()、 onDrawForeground()等方法,即步驟1、3、4、6。
3.1.2 View的drawBackground
首先來看看drawBackground(),這個方法是用來繪制背景:
private void drawBackground(Canvas canvas) {
// 獲取背景 drawable
final Drawable background = mBackground;
if (background == null) {
return;
}
// 根據(jù)在 layout 過程中獲取的 View 的位置參數(shù),來設(shè)置背景的邊界
setBackgroundBounds();
//硬件加速渲染
if (canvas.isHardwareAccelerated() && mAttachInfo != null
&& mAttachInfo.mThreadedRenderer != null) {
mBackgroundRenderNode = getDrawableRenderNode(background, mBackgroundRenderNode);
final RenderNode renderNode = mBackgroundRenderNode;
if (renderNode != null && renderNode.isValid()) {
setBackgroundRenderNodeProperties(renderNode);
((DisplayListCanvas) canvas).drawRenderNode(renderNode);
return;
}
}
// 獲取 mScrollX 和 mScrollY值
final int scrollX = mScrollX;
final int scrollY = mScrollY;
if ((scrollX | scrollY) == 0) {
// 調(diào)用 Drawable 的 draw 方法繪制背景
background.draw(canvas);
} else {
// 若 mScrollX 和 mScrollY 有值,則對 canvas 的坐標進行偏移
canvas.translate(scrollX, scrollY);
// 調(diào)用 Drawable 的 draw 方法繪制背景
background.draw(canvas);
canvas.translate(-scrollX, -scrollY);
}
}
3.1.3 View的onDraw
由于 View 的內(nèi)容各不相同,因此onDraw()方法在View類中是個空實現(xiàn),具體的View(如TextView等)需對其進行重寫,有興趣的可以去看看TextView等的onDraw()實現(xiàn)。
protected void onDraw(Canvas canvas) {
//具體內(nèi)容的繪制邏輯
}
3.1.4 View的dispatchDraw
由于單一View沒有子View,所以其dispatchDraw()方法是個空實現(xiàn):
protected void dispatchDraw(Canvas canvas) {
}
3.1.5 View的onDrawForeground
onDrawForeground()方法就是用來繪制一些裝飾,比如滑動指示器、滑動條、前景等:
public void onDrawForeground(Canvas canvas) {
//繪制滑動指示器
onDrawScrollIndicators(canvas);
//繪制滑動條
onDrawScrollBars(canvas);
//繪制前景
final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null;
if (foreground != null) {
if (mForegroundInfo.mBoundsChanged) {
mForegroundInfo.mBoundsChanged = false;
final Rect selfBounds = mForegroundInfo.mSelfBounds;
final Rect overlayBounds = mForegroundInfo.mOverlayBounds;
if (mForegroundInfo.mInsidePadding) {
selfBounds.set(0, 0, getWidth(), getHeight());
} else {
selfBounds.set(getPaddingLeft(), getPaddingTop(),
getWidth() - getPaddingRight(), getHeight() - getPaddingBottom());
}
final int ld = getLayoutDirection();
Gravity.apply(mForegroundInfo.mGravity, foreground.getIntrinsicWidth(),
foreground.getIntrinsicHeight(), selfBounds, overlayBounds, ld);
foreground.setBounds(overlayBounds);
}
foreground.draw(canvas);
}
}
3.1.6 單一View的draw過程流程圖
來張流程圖簡單總結(jié)一下:

3.2 ViewGroup的draw過程
ViewGroup的draw過程同樣是從View的draw()方法開始,ViewGroup沒有重寫draw()方法,所以跟View的draw()代碼是一樣,其drawBackground()、onDraw()、 onDrawForeground()等方法實現(xiàn)也一樣,這里就不重述了,我們來看下唯一不同的地方:dispatchDraw()。
3.2.1 ViewGroup的dispatchDraw
@Override
protected void dispatchDraw(Canvas canvas) {
//子View數(shù)量
final int childrenCount = mChildrenCount;
final View[] children = mChildren;
//...
//遍歷子View
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);
}
//...
}
//...
}
我們再來看看drawChild()方法:
3.2.2 ViewGroup的drawChild
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
drawChild()方法中就是調(diào)用了子View的draw()去繪制子View.
3.2.3 ViewGroup的draw過程流程圖
來張流程圖簡單總結(jié)一下:

4. 自定義View
4.1 自定義單一view
自定義單一View需要重寫onDraw():
@Override
protected void onDraw(Canvas canvas) {
//具體內(nèi)容的繪制邏輯
}
4.2 自定義ViewGroup
自定義的ViewGroup一般是作為容器來放置各種子View的,所以一般無需重寫onDraw()。
因此ViewGroup默認啟用了WILL_NOT_DRAW這個標志位,啟用這個標記位后系統(tǒng)會進行相應(yīng)的優(yōu)化。
所以,當(dāng)我們有特殊需求需要重寫ViewGroup的onDraw()時,應(yīng)當(dāng)關(guān)閉這個標記位??梢酝ㄟ^調(diào)用setWillNotDraw(false)來關(guān)閉它。