編輯:關於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%。這是怎麼回事,是電池問題嗎?還是其他什
微信可以說是我們當下非常常用的手機軟件,很多人都會選擇它來進行交流。有的時候,我們在使用微信的過程中也會發現很多問題,今天,小編就來講講微信發不出信息怎麼辦
很多朋友都說lollipop出來想試用一下,結果在網官下載的android studio 都是20版本,也沒有看見更新到android 5.0。 我也在網上狂了一下,
前言由於Android自帶的TextView控件沒有提供傾斜的(我暫時沒有找到),我們可以自定義控件來實現,下面首先來看我們實現的效果圖。TextView文字傾斜其實實現