一、調(diào)用流程
1. ViewRootImpl # performTraversals()
performTraversals()調(diào)用了performDraw(),三大流程的起點都是performTraversals()。
// 沒有取消draw也沒有創(chuàng)建新的平面 第一次traversals時newSurface為true
if (!cancelDraw && !newSurface) {
// ...
// 開始draw流程
performDraw();
} else {
if (isViewVisible) {
// Try again
// 如果是可見的, 就再調(diào)用一次traversals
scheduleTraversals();
// ...
}
// ...
}
2. ViewRootImpl # performDraw()
調(diào)用到ViewRootImpl的draw()。
private void performDraw() {
// ...
// mFullRedrawNeeded表示是否需要完全重繪
final boolean fullRedrawNeeded = mFullRedrawNeeded;
mFullRedrawNeeded = false;
mIsDrawing = true;
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");
try {
// 調(diào)用draw()
draw(fullRedrawNeeded);
} finally {
mIsDrawing = false;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
// ...
}
3. ViewRootImpl # draw()
這個方法非常長,主要是去處理繪制區(qū)域、坐標等準備工作,之后調(diào)用drawSoftware()。
private void draw(boolean fullRedrawNeeded) {
// ...
// 獲取需要繪制的區(qū)域
final Rect dirty = mDirty;
// ...
// 判斷是否需要完全繪制
if (fullRedrawNeeded) {
mAttachInfo.mIgnoreDirtyState = true;
// 如果需要就將區(qū)域設(shè)置為屏幕的所有區(qū)域
dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
}
// ...
if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
// ...
} else {
// ...
// 調(diào)用drawSoftware()繪制
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
return;
}
}
}
// ...
}
4. ViewRootImpl # drawSoftware()
- 初始canvas對象
- 調(diào)用DecorView的draw()
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty) {
final Canvas canvas;
try {
final int left = dirty.left;
final int top = dirty.top;
final int right = dirty.right;
final int bottom = dirty.bottom;
// 創(chuàng)建一個繪制區(qū)域的canvas對象
canvas = mSurface.lockCanvas(dirty);
// 判斷l(xiāng)ockCanvas有沒有改變dirty的頂點值
if (left != dirty.left || top != dirty.top || right != dirty.right
|| bottom != dirty.bottom) {
attachInfo.mIgnoreDirtyState = true;
}
// 設(shè)置畫布密度
canvas.setDensity(mDensity);
} catch (Surface.OutOfResourcesException e) {
// ...
}
try {
// ...
// 先清空畫布
if (!canvas.isOpaque() || yoff != 0 || xoff != 0) {
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
}
dirty.setEmpty();
mIsAnimating = false;
mView.mPrivateFlags |= View.PFLAG_DRAWN;
try {
// 設(shè)置畫布偏移值
canvas.translate(-xoff, -yoff);
if (mTranslator != null) {
mTranslator.translateCanvas(canvas);
}
canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);
attachInfo.mSetIgnoreDirtyState = false;
// 調(diào)用draw() 從DecorView開始繪制流程
mView.draw(canvas);
// ...
}
// ...
}
// ...
return true;
}
5. DecorView # draw()
- 先調(diào)用父類的draw()去繪制。
- 如果有菜單背景的drawable的畫就畫上。
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
if (mMenuBackground != null) {
mMenuBackground.draw(canvas);
}
}
6. View # draw()
FrameLayout和ViewGroup都沒有重寫draw(),所以就調(diào)用到了View的draw()。View的draw()也很長,但是注釋寫的分步很清楚。
/*
* 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)
*/
繪制背景、繪制自身、繪制子View、繪制陰影、繪制其它的裝飾。到這里就調(diào)用到Draw向子View分發(fā)的過程了。下面就仔細的看看這個方法。
二、View的draw()的實現(xiàn)
根據(jù)注釋可以將draw()分成六個階段。
- 繪制背景
- 存儲圖層
- 繪制自身
- 繪制子View
- 繪制陰影并恢復(fù)圖層
- 繪制滾動條等效果
1. 繪制背景
View # draw()
如果不為透明,就調(diào)用drawBackground()去繪制背景。
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
// 判斷是否為透明
if (!dirtyOpaque) {
// 調(diào)用drawBackgroud()去繪制背景
drawBackground(canvas);
}
View # drawBackground()
- 設(shè)置背景圖片邊界
- 判斷View是否有移動
- N:直接繪制背景圖片
- Y:移動到View的偏移位置,繪制背景,再移動回來。
private void drawBackground(Canvas canvas) {
final Drawable background = mBackground;
if (background == null) {
return;
}
// 設(shè)置背景圖片的邊界位置
setBackgroundBounds();
// 硬件加速相關(guān)......
final int scrollX = mScrollX;
final int scrollY = mScrollY;
// 判斷View是否滑動
if ((scrollX | scrollY) == 0) {
// 沒有滑動就繪制背景圖片
background.draw(canvas);
} else {
// 如果移動了,就先移動canvas,繪制背景,canvas再移回
canvas.translate(scrollX, scrollY);
background.draw(canvas);
canvas.translate(-scrollX, -scrollY);
}
}
View # setBackgroundBounds()
調(diào)用drawable的setBounds()設(shè)置邊界,傳參是由layout過程中生成的頂點值組合的,就相當于是View的頂點。
void setBackgroundBounds() {
if (mBackgroundSizeChanged && mBackground != null) {
// 設(shè)置背景圖的四個坐標,這四個值就是View的四個頂點值
mBackground.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
mBackgroundSizeChanged = false;
rebuildOutline();
}
}
2. 繪制自身
View # draw()
- 如果不需要繪制陰影,就直接進入繪制自身的步驟。
- 如果View不是透明的,就調(diào)用onDraw()去繪制自身。
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);
// ...
}
View # onDraw()
空實現(xiàn),沒有做統(tǒng)一實現(xiàn),自定義View時需要自己去實現(xiàn)。
protected void onDraw(Canvas canvas) {
}
3. 繪制子View分發(fā)過程
View # draw()
在繪制自己完成后就去分發(fā)調(diào)用子View的繪制過程。
if (!verticalEdges && !horizontalEdges) {
// ...
dispatchDraw(canvas);
// ...
}
ViewGroup # dispatchDraw()
在View中是空實現(xiàn),所以來看ViewGroup中的實現(xiàn)。會遍歷所有的子View,如果子View可見或是存在動畫,就調(diào)用drawChld()。
@Override
protected void dispatchDraw(Canvas canvas) {
boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);
final int childrenCount = mChildrenCount;
final View[] children = mChildren;
int flags = mGroupFlags;
// ...
// 遍歷子View
for (int i = 0; i < childrenCount; i++) {
// ...
final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
// 調(diào)用drawChild去傳遞
more |= drawChild(canvas, child, drawingTime);
}
}
// ...
}
ViewGroup # drawChild()
直接調(diào)用了子View的draw(),實現(xiàn)了傳遞。
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
View # draw()
這個draw()有三個參數(shù),和之前分析的一個參數(shù)的draw()不同,注釋上說得很清楚,這個draw()是ViewGroup.drawChild()調(diào)用去繪制子View的。和一個參數(shù)最大不同是,這個draw()判斷了是繪制緩存內(nèi)容還是去調(diào)用一個參數(shù)的draw()繪制。
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
// ...
if (!drawingWithDrawingCache) {
// 如果不使用緩存
if (drawingWithRenderNode) {
// 硬件緩存
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
((DisplayListCanvas) canvas).drawRenderNode(renderNode);
} else {
// Fast path for layouts with no backgrounds
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
// 如果需要跳過,就直接去調(diào)用分發(fā)給這個View的子View的方法
dispatchDraw(canvas);
} else {
// 調(diào)用普通的draw()
draw(canvas);
}
}
} else if (cache != null) {
// 如果使用緩存并且緩存不為空
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
if (layerType == LAYER_TYPE_NONE || mLayerPaint == null) {
// no layer paint, use temporary paint to draw bitmap
Paint cachePaint = parent.mCachePaint;
if (cachePaint == null) {
cachePaint = new Paint();
cachePaint.setDither(false);
parent.mCachePaint = cachePaint;
}
cachePaint.setAlpha((int) (alpha * 255));
// 繪制緩存的內(nèi)容
canvas.drawBitmap(cache, 0.0f, 0.0f, cachePaint);
} else {
// use layer paint to draw the bitmap, merging the two alphas, but also restore
int layerPaintAlpha = mLayerPaint.getAlpha();
if (alpha < 1) {
mLayerPaint.setAlpha((int) (alpha * layerPaintAlpha));
}
// 使用圖層的Paint去繪制緩存內(nèi)容
canvas.drawBitmap(cache, 0.0f, 0.0f, mLayerPaint);
if (alpha < 1) {
mLayerPaint.setAlpha(layerPaintAlpha);
}
}
}
// ...
}
4. 繪制裝飾物
View # draw()
if (!verticalEdges && !horizontalEdges) {
// ...
onDrawForeground(canvas);
// ...
}
View # onDrawForeground()
public void onDrawForeground(Canvas canvas) {
// 繪制滾動指示器
onDrawScrollIndicators(canvas);
// 繪制滾動條
onDrawScrollBars(canvas);
final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null;
if (foreground != null) {
// ...
// 如果有前景圖片,就繪制它
foreground.draw(canvas);
}
}
5. 如果需要繪制邊框陰影
前面那些步驟走完后就能直接return了。如果需要繪制陰影的話,則不會進入前面分析的方法塊。而是執(zhí)行下面的步驟。注釋上說這種情況不常見。
View # draw()
- 同樣地繪制背景
- 去計算陰影帶來的變量影響,對陰影分類去保存圖層
- 同樣地繪制自身
- 同樣地分發(fā)繪制流程
- 分類去繪制陰影
- 加載保存的圖層
- 同樣地繪制裝飾
// 共同的步驟,繪制背景圖片
if (!dirtyOpaque) {
drawBackground(canvas);
}
// 不需要繪制陰影的情況
if (!verticalEdges && !horizontalEdges) {
// ...
return;
}
// 如果需要繪制陰影
// 根據(jù)陰影情況去改變參數(shù)......
if (solidColor == 0) {
final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;
// 保存圖層
if (drawTop) {
canvas.saveLayer(left, top, right, top + length, null, flags);
}
// ...
} else {
scrollabilityCache.setFadeColor(solidColor);
}
// 繪制自身
if (!dirtyOpaque) onDraw(canvas);
// 繪制子View
dispatchDraw(canvas);
// ...
// 繪制陰影
if (drawTop) {
matrix.setScale(1, fadeHeight * topFadeStrength);
matrix.postTranslate(left, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(left, top, right, top + length, p);
}
// ...
// 取出保存的圖層
canvas.restoreToCount(saveCount);
// ...
// 繪制裝飾
onDrawForeground(canvas);