移步自定義View系列
內容
- 作用
- draw過程詳解
- 單一view的draw過程
- ViewGroup的draw過程
- 其他細節問題view.setWillNotDraw();
1 作用
繪制View視圖
2 draw過程詳解
類似measure過程、layout過程,draw過程根據View的類型分為2種情況:
image
2.1 單一View的draw過程
- 原理(步驟)
- View繪制自身(含背景、內容);
- 繪制裝飾(滾動指示器、滾動條、和前景)
-
具體流程
image
/**
* 源碼分析:draw()
* 作用:根據給定的 Canvas 自動渲染 View(包括其所有子 View)。
* 繪制過程:
* 1. 繪制view背景
* 2. 繪制view內容
* 3. 繪制子View
* 4. 繪制裝飾(漸變框,滑動條等等)
* 注:
* a. 在調用該方法之前必須要完成 layout 過程
* b. 所有的視圖最終都是調用 View 的 draw ()繪制視圖( ViewGroup 沒有復寫此方法)
* c. 在自定義View時,不應該復寫該方法,而是復寫 onDraw(Canvas) 方法進行繪制
* d. 若自定義的視圖確實要復寫該方法,那么需先調用 super.draw(canvas)完成系統的繪制,然后再進行自定義的繪制
*/
public void draw(Canvas canvas) {
...// 僅貼出關鍵代碼
int saveCount;
// 步驟1: 繪制本身View背景
if (!dirtyOpaque) {
drawBackground(canvas);
}
// 若有必要,則保存圖層(還有一個復原圖層)
// 優化技巧:當不需繪制 Layer 時,“保存圖層“和“復原圖層“這兩步會跳過
// 因此在繪制時,節省 layer 可以提高繪制效率
final int viewFlags = mViewFlags;
if (!verticalEdges && !horizontalEdges) {
// 步驟2:繪制本身View內容
if (!dirtyOpaque)
onDraw(canvas);
// View 中:默認為空實現,需復寫
// ViewGroup中:需復寫
// 步驟3:繪制子View
// 由于單一View無子View,故View 中:默認為空實現
// ViewGroup中:系統已經復寫好對其子視圖進行繪制我們不需要復寫
dispatchDraw(canvas);
// 步驟4:繪制裝飾,如滑動條、前景色等等
onDrawScrollBars(canvas);
return;
}
...
}
繼續分析在draw()中4個步驟調用的drawBackground()、 onDraw()、dispatchDraw()、onDrawScrollBars(canvas)
/**
* 步驟1:drawBackground(canvas)
* 作用:繪制View本身的背景
*/
private void drawBackground(Canvas canvas) {
// 獲取背景 drawable
final Drawable background = mBackground;
if (background == null) {
return;
}
// 根據在 layout 過程中獲取的 View 的位置參數,來設置背景的邊界
setBackgroundBounds();
.....
// 獲取 mScrollX 和 mScrollY值
final int scrollX = mScrollX;
final int scrollY = mScrollY;
if ((scrollX | scrollY) == 0) {
background.draw(canvas);
} else {
// 若 mScrollX 和 mScrollY 有值,則對 canvas 的坐標進行偏移
canvas.translate(scrollX, scrollY);
// 調用 Drawable 的 draw 方法繪制背景
background.draw(canvas);
canvas.translate(-scrollX, -scrollY);
}
}
/**
* 步驟2:onDraw(canvas)
* 作用:繪制View本身的內容
* 注:
* a. 由于 View 的內容各不相同,所以該方法是一個空實現
* b. 在自定義繪制過程中,需由子類去實現復寫該方法,從而繪制自身的內容
* c. 謹記:自定義View中 必須 且 只需復寫onDraw()
*/
protected void onDraw(Canvas canvas) {
... // 復寫從而實現繪制邏輯
}
/**
* 步驟3: dispatchDraw(canvas)
* 作用:繪制子View
* 注:由于單一View中無子View,故為空實現
*/
protected void dispatchDraw(Canvas canvas) {
... // 空實現
}
/**
* 步驟4: onDrawScrollBars(canvas)
* 作用:繪制裝飾,如 滾動指示器、滾動條、和前景等
*/
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);
}
}
2.2 ViewGroup的draw過程
- 應用場景
利用現有的組件根據特定的布局方式來組成新的組件 - 具體使用
繼承自ViewGroup 或 各種Layout;含有子 View - 原理(步驟)
- ViewGroup繪制自身(含背景、內容);
- ViewGroup遍歷子View & 繪制其所有子View;
類似于單一View的draw過程 - ViewGroup繪制裝飾(滾動指示器、滾動條、和前景)
-
具體流程
image
/**
* 源碼分析:draw()
* 與單一View的draw()流程類似
* 作用:根據給定的 Canvas 自動渲染 View(包括其所有子 View)
* 繪制過程:
* 1. 繪制view背景
* 2. 繪制view內容
* 3. 繪制子View
* 4. 繪制裝飾(漸變框,滑動條等等)
* 注:
* a. 在調用該方法之前必須要完成 layout 過程
* b. 所有的視圖最終都是調用 View 的 draw ()繪制視圖( ViewGroup 沒有復寫此方法)
* c. 在自定義View時,不應該復寫該方法,而是復寫 onDraw(Canvas) 方法進行繪制
* d. 若自定義的視圖確實要復寫該方法,那么需先調用 super.draw(canvas)完成系統的繪制,然后再進行自定義的繪制
*/
public void draw(Canvas canvas) {
...// 僅貼出關鍵代碼
int saveCount;
// 步驟1: 繪制本身View背景
if (!dirtyOpaque) {
drawBackground(canvas);
}
// 若有必要,則保存圖層(還有一個復原圖層)
// 優化技巧:當不需繪制 Layer 時,“保存圖層“和“復原圖層“這兩步會跳過
// 因此在繪制時,節省 layer 可以提高繪制效率
final int viewFlags = mViewFlags;
if (!verticalEdges && !horizontalEdges) {
// 步驟2:繪制本身View內容
if (!dirtyOpaque)
onDraw(canvas);
// View 中:默認為空實現,需復寫
// ViewGroup中:需復寫
// 步驟3:繪制子View
// ViewGroup中:系統已復寫好對其子視圖進行繪制,不需復寫
dispatchDraw(canvas);
// 步驟4:繪制裝飾,如滑動條、前景色等等
onDrawScrollBars(canvas);
return;
}
...
}
由于 步驟2:drawBackground()、步驟3:onDraw()、步驟5:onDrawForeground(),與單一View的draw過程類似,此處不作過多描述
- 直接進入與單一View draw過程最大不同的步驟4:dispatchDraw()
/**
* 源碼分析:dispatchDraw()
* 作用:遍歷子View & 繪制子View
* 注:
* a. ViewGroup中:由于系統為我們實現了該方法,故不需重寫該方法
* b. View中默認為空實現(因為沒有子View可以去繪制)
*/
protected void dispatchDraw(Canvas canvas) {
......
// 1. 遍歷子View
final int childrenCount = mChildrenCount;
......
for (int i = 0; i < childrenCount; i++) {
......
if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
transientChild.getAnimation() != null) {
// 2. 繪制子View視圖 ->>分析1
more |= drawChild(canvas, transientChild, drawingTime);
}
....
}
}
/**
* 分析1:drawChild()
* 作用:繪制子View
*/
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
// 最終還是調用了子 View 的 draw ()進行子View的繪制
return child.draw(canvas, this, drawingTime);
}
至此,ViewGroup的draw過程已分析完畢。
3 其他細節問題:View.setWillNotDraw()
/**
* 源碼分析:setWillNotDraw()
* 定義:View 中的特殊方法
* 作用:設置 WILL_NOT_DRAW 標記位;
* 注:
* a. 該標記位的作用是:當一個View不需要繪制內容時,系統進行相應優化
* b. 默認情況下:View 不啟用該標記位(設置為false);ViewGroup 默認啟用(設置為true)
*/
public void setWillNotDraw(boolean willNotDraw) {
setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
}
// 應用場景
// a. setWillNotDraw參數設置為true:當自定義View繼承自 ViewGroup 、且本身并不具備任何繪制時,設置為 true 后,系統會進行相應的優化。
// b. setWillNotDraw參數設置為false:當自定義View繼承自 ViewGroup 、且需要繪制內容時,那么設置為 false,來關閉 WILL_NOT_DRAW 這個標記位。
4 總結
image