View繪製——怎麼畫?
這是Android檢視繪製系列文章的第三篇,系列文章目錄如下:
View繪製就好比畫畫,先拋開Android概念,如果要畫一張圖,首先會想到哪幾個基本問題:
- 畫多大?
- 畫在哪?
- 怎麼畫?
Android繪製系統也是按照這個思路對View進行繪製,上面這些問題的答案分別藏在:
- 測量(measure)
- 定位(layout)
- 繪製(draw)
這一篇將從原始碼的角度分析“繪製(draw)”。View
繪製系統中的draw
其實是講的是繪製的順序,至於具體畫什麼東西是各個子View
自己決定的。
在分析View
的測量
和定位
時,發現它們都是自頂向下進行地,即總是由父控制元件來觸發子控制元件的測量或定位。不知道“繪製”是不是也是這樣?,以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; } }
這個方法實在太長了。。。還好有註釋幫我們提煉了一條主線。註釋說繪製一共有6個步驟,他們分別是:
- 繪製控制元件背景
- 儲存畫布層
- 繪製控制元件自身內容
- 繪製子控制元件
- 繪製褪色效果並恢復畫布層(感覺這一步和第二步是對稱的)
- 繪製裝飾物
為啥提煉了主線後還是覺得好複雜。。。還好註釋又幫我們省去了一些步驟,註釋說“通常情況下第二步和第五步會跳過。”在剩下的步驟中有三個步驟最最重要:
- 繪製控制元件背景
- 繪製控制元件自身內容
- 繪製子控制元件
讀到這裡可以得出結論:
View
繪製順序是先畫背景(drawBackground()
),再畫自己(onDraw()
),接著畫孩子(dispatchDraw()
)。晚畫的東西會蓋在上面。
先看下drawBackground()
:
/** * Draws the background onto the specified canvas. * * @param canvas Canvas on which to draw the background */ private void drawBackground(Canvas canvas) { //Drawable型別的背景圖 final Drawable background = mBackground; if (background == null) { return; } setBackgroundBounds(); ... //繪製Drawable final int scrollX = mScrollX; final int scrollY = mScrollY; if ((scrollX | scrollY) == 0) { background.draw(canvas); } else { canvas.translate(scrollX, scrollY); background.draw(canvas); canvas.translate(-scrollX, -scrollY); } }
背景是一張Drawable
型別的圖片,直接呼叫Drawable.draw()
將其繪製在畫布上。接著看下onDraw()
:
public class View implements Drawable.Callback, KeyEvent.Callback,AccessibilityEventSource { /** * Implement this to do your drawing. * * @param canvas the canvas on which the background will be drawn */ protected void onDraw(Canvas canvas) { } }
View.onDraw()
是一個空實現。想想也對,View
是一個基類,它只負責抽象出繪製的順序,具體繪製什麼由子類來決定,看一下ImageView.onDraw()
:
public class ImageView extends View { @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); ... //繪製drawable if (mDrawMatrix == null && mPaddingTop == 0 && mPaddingLeft == 0) { mDrawable.draw(canvas); } else { final int saveCount = canvas.getSaveCount(); canvas.save(); if (mCropToPadding) { final int scrollX = mScrollX; final int scrollY = mScrollY; canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop, scrollX + mRight - mLeft - mPaddingRight, scrollY + mBottom - mTop - mPaddingBottom); } canvas.translate(mPaddingLeft, mPaddingTop); if (mDrawMatrix != null) { canvas.concat(mDrawMatrix); } mDrawable.draw(canvas); canvas.restoreToCount(saveCount); } } }
ImageView
的繪製方法和View
繪製背景一樣,都是直接繪製Drawable
。
View.dispatchDraw()
也是一個空實現,想想也對,View
是葉子結點,它沒有孩子:
public class View implements Drawable.Callback, KeyEvent.Callback,AccessibilityEventSource { /** * Called by draw to draw the child views. This may be overridden * by derived classes to gain control just before its children are drawn * (but after its own view has been drawn). * @param canvas the canvas on which to draw the view */ protected void dispatchDraw(Canvas canvas) { } }
所以ViewGroup
實現了dispatchDraw()
:
public abstract class ViewGroup extends View implements ViewParent, ViewManager { @Override protected void dispatchDraw(Canvas canvas) { ... // Only use the preordered list if not HW accelerated, since the HW pipeline will do the // draw reordering internally //當沒有硬體加速時,使用預定義的繪製列表(根據z-order值升序排列所有子控制元件) final ArrayList<View> preorderedList = usingRenderNodeProperties ? null : buildOrderedChildList(); //自定義繪製順序 final boolean customOrder = preorderedList == null && isChildrenDrawingOrderEnabled(); //遍歷所有子控制元件 for (int i = 0; i < childrenCount; i++) { ... //如果沒有自定義繪製順序和預定義繪製列表,則按照索引i遞增順序遍歷子控制元件 final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder); final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex); if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) { //觸發子控制元件自己繪製自己 more |= drawChild(canvas, child, drawingTime); } } ... } private int getAndVerifyPreorderedIndex(int childrenCount, int i, boolean customOrder) { final int childIndex; if (customOrder) { final int childIndex1 = getChildDrawingOrder(childrenCount, i); if (childIndex1 >= childrenCount) { throw new IndexOutOfBoundsException("getChildDrawingOrder() " + "returned invalid index " + childIndex1 + " (child count is " + childrenCount + ")"); } childIndex = childIndex1; } else { //1.如果沒有自定義繪製順序,遍歷順序和i遞增順序一樣 childIndex = i; } return childIndex; } private static View getAndVerifyPreorderedView(ArrayList<View> preorderedList, View[] children, int childIndex) { final View child; if (preorderedList != null) { child = preorderedList.get(childIndex); if (child == null) { throw new RuntimeException("Invalid preorderedList contained null child at index " + childIndex); } } else { //2.如果沒有預定義繪製列表,則按i遞增順序遍歷子控制元件 child = children[childIndex]; } return child; } }
結合註釋相信你一定看懂了:
父控制元件會在dispatchDraw()
中遍歷所有子控制元件並觸發其繪製自己。
而且還可以通過某種手段來自定義子控制元件的繪製順序(對於本篇主題來說,這不重要)。
沿著呼叫鏈繼續往下:
public abstract class ViewGroup extends View implements ViewParent, ViewManager { /** * Draw one child of this View Group. This method is responsible for getting * the canvas in the right state. This includes clipping, translating so * that the child’s scrolled origin is at 0, 0, and applying any animation * transformations. * 繪製ViewGroup的一個孩子 * * @param canvas The canvas on which to draw the child * @param child Who to draw * @param drawingTime The time at which draw is occurring * @return True if an invalidate() was issued */ protected boolean drawChild(Canvas canvas, View child, long drawingTime) { return child.draw(canvas, this, drawingTime); } } public class View implements Drawable.Callback, KeyEvent.Callback,AccessibilityEventSource { /** * This method is called by ViewGroup.drawChild() to have each child view draw itself. * * This is where the View specializes rendering behavior based on layer type, * and hardware acceleration. */ boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) { ... if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) { mPrivateFlags &= ~PFLAG_DIRTY_MASK; dispatchDraw(canvas); } else { //繪製 draw(canvas); } ... }
ViewGroup.drawChild()
最終會呼叫View.draw()
。所以,
View
的繪製是自頂向下遞迴的過程,“遞”表示父控制元件在ViewGroup.dispatchDraw()
中遍歷子控制元件並呼叫View.draw()
觸發其繪製自己,“歸”表示所有子控制元件完成繪製後父控制元件繼續後序繪製步驟`