編輯:關於Android編程
前面已經了解了 View 三大流程的 measure 和 layout 過程,這一篇繼續學習最後的 draw 過程。draw 的過程依舊是在 ViewRootImpl#performTraversals 方法中調用的,其調用順序是在最後, 相較與 measure 和 layout 過程要簡單的多,它的作用就是將 View 繪制到屏幕上面。
下面直接通過查看 View#draw 源碼,來分析下其 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
......
int solidColor = getSolidColor();
if (solidColor == 0) {
final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;
if (drawTop) {
canvas.saveLayer(left, top, right, top + length, null, flags);
}
if (drawBottom) {
canvas.saveLayer(left, bottom - length, right, bottom, null, flags);
}
if (drawLeft) {
canvas.saveLayer(left, top, left + length, bottom, null, flags);
}
if (drawRight) {
canvas.saveLayer(right - length, top, right, bottom, null, flags);
}
} 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
......
if (drawTop) {
......
canvas.drawRect(left, top, right, top + length, p);
}
if (drawBottom) {
......
canvas.drawRect(left, bottom - length, right, bottom, p);
}
if (drawLeft) {
......
canvas.drawRect(left, top, left + length, bottom, p);
}
if (drawRight) {
......
canvas.drawRect(right - length, top, right, bottom, p);
}
......
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
}
上面的源碼注釋寫的很清晰,通過查看後我們了解到 View 的繪制共分為如下六步:
1:繪制背景。 2:如果需要,保存圖層信息。 3:繪制 View 的內容。 4:如果 View 有子 View,繪制 View 的子 View。 5:如果需要,繪制 View 的邊緣(如陰影等)。 6:繪制 View 的裝飾(如滾動條等)。其中以上六步,第二步和第五步並不是必須的,所以我們只需重點分析其他四步即可。
繪制背景調用了 View#drawBackground 方法:
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);
}
}
繪制內容調用了 View#onDraw 方法,由於 View 的內容各不相同,所以該方法是一個空實現,需要由子類去實現:
protected void onDraw(Canvas canvas) {
}
繪制子 View 調用了 View#dispatchDraw 方法,該方法同樣是一個空實現:
protected void dispatchDraw(Canvas canvas) {
}
當只有包含子類的時候,才會去重寫它,ViewGroup 不正好是嗎? 來看下 ViewGroup 對該方法的實現吧:
protected void dispatchDraw(Canvas canvas) {
......
final int childrenCount = mChildrenCount;
......
for (int i = 0; i < childrenCount; i++) {
......
if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
transientChild.getAnimation() != null) {
more |= drawChild(canvas, transientChild, drawingTime);
}
......
}
}
ViewGroup#dispatchDraw 方法的代碼比較多,只分析重點,遍歷了所有的子 View 並調用了 ViewGroup#drawChild 方法:
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
該方法最終還是調用了子 View 的 draw 方法,似曾相識啊,和上篇中的 layout 過程是一樣呢。
由於 ViewGroup 已經為我們實現了該方法,所以我們一般都不需要重寫該方法。
繪制裝飾調用了 View#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);
}
}
該方法默認實現是繪制了滾動指示器、滾動條、和前景。
View 中有一個特殊的方法,setWillNotDraw(boolean willNotDraw):
/**
* If this view doesn't do any drawing on its own, set this flag to
* allow further optimizations. By default, this flag is not set on
* View, but could be set on some View subclasses such as ViewGroup.
*
* Typically, if you override {@link #onDraw(android.graphics.Canvas)}
* you should clear this flag.
*
* @param willNotDraw whether or not this View draw on its own
*/
public void setWillNotDraw(boolean willNotDraw) {
setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
}
該方法是用於設置 WILL_NOT_DRAW 標記位的。默認情況下, View 沒有啟用這個優化標記位的,但是 ViewGroup 會默認啟用。
如果當我們自定義的控件繼承自 ViewGroup 並且本身並不具備任何繪制時,那麼可以設置 setWillNotDraw 方法為 true,設置為 true 後,系統會進行相應的優化。
如果當我們知道我們自定義繼承自 ViewGroup 的控件需要繪制內容時,那麼需要設置 setWillNotDraw 方法為 false,來關閉 WILL_NOT_DRAW 這個標記位。
安卓手機充不進電怎麼辦?
在插上電源充電之後,充上幾個小時發現手機的電量沒有任何增進,反而掉的見底了!小編在充電的時候,充了一整夜都只到75%。這是怎麼回事,是電池問題嗎?還是其他什
微信發不出去怎麼辦 微信不能發信息怎麼辦
微信可以說是我們當下非常常用的手機軟件,很多人都會選擇它來進行交流。有的時候,我們在使用微信的過程中也會發現很多問題,今天,小編就來講講微信發不出信息怎麼辦
Android lollipop 更新問題
很多朋友都說lollipop出來想試用一下,結果在網官下載的android studio 都是20版本,也沒有看見更新到android 5.0。 我也在網上狂了一下,
Android自定義TextView實現文字傾斜效果
前言由於Android自帶的TextView控件沒有提供傾斜的(我暫時沒有找到),我們可以自定義控件來實現,下面首先來看我們實現的效果圖。TextView文字傾斜其實實現