這是Android視圖繪製系列文章的第三篇,系列文章目錄以下:canvas
View繪製就比如畫畫,先拋開Android概念,若是要畫一張圖,首先會想到哪幾個基本問題:bash
Android繪製系統也是按照這個思路對View進行繪製,上面這些問題的答案分別藏在:app
這一篇將從源碼的角度分析「繪製(draw)」。View
繪製系統中的draw
實際上是講的是繪製的順序,至於具體畫什麼東西是各個子View
本身決定的。ide
在分析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個步驟,他們分別是:post
爲啥提煉了主線後仍是以爲好複雜。。。還好註釋又幫咱們省去了一些步驟,註釋說「一般狀況下第二步和第五步會跳過。」在剩下的步驟中有三個步驟最最重要:ui
讀到這裏能夠得出結論:View
繪製順序是先畫背景(drawBackground()
),再畫本身(onDraw()
),接着畫孩子(dispatchDraw()
)。晚畫的東西會蓋在上面。this
先看下drawBackground()
:spa
/**
* 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()
:debug
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()
觸發其繪製本身,「歸」表示全部子控件完成繪製後父控件繼續後序繪製步驟`
通過三篇文章的分析,對View
繪製流程有了一個大概的瞭解:
View
繪製流程就比如畫畫,它按前後順序解決了三個問題 :
View
樹的根結點開始自頂向下進行地,即都是由父控件驅動子控件進行地。父控件的測量在子控件件測量以後,但父控件的定位和繪製都在子控件以前。ViewGroup.onMeasure()
,會遍歷全部子控件並驅動它們測量本身View.measure()
。父控件還會將父控件的佈局要求與子控件的佈局訴求相結合造成一個MeasureSpec
對象傳遞給子控件以指導其測量本身。View.setMeasuredDimension()
是測量過程的終點,它表示View
大小有了肯定值。ViewGroup.onLayout()
遍歷全部子控件並驅動它們定位本身View.layout()
。子控件老是相對於父控件左上角定位。View.setFrame()
是定位過程的終點,它表示視圖矩形區域以及相對於父控件的位置已經肯定。ViewGroup.dispatchDraw()
遍歷全部子控件並驅動他們繪製本身View.draw()