開篇:
前兩篇已經(jīng)詳細(xì)的介紹了 Measure 以及 Layout 過程,就剩下一個(gè) Draw 繪制過程了试读,Draw 其實(shí)也不是很復(fù)雜,但是想要徹底掌握繪制的技巧就需要了解 Canvas 的使用了辽狈,后續(xù)會再開幾篇詳細(xì)介紹 Canvas 的具體使用
老規(guī)矩置逻,還是先給出 ViewRootImpl#performTraversals 方法
ViewRootImpl#performTraversals 方法
private void performTraversals() {
...
if (!mStopped || mReportNextDraw) {
boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
(relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
updatedConfiguration) {
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
if (didLayout) {
performLayout(lp, mWidth, mHeight);
...
if (!cancelDraw && !newSurface) {
if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).startChangingAnimations();
}
mPendingTransitions.clear();
}
performDraw();
...
不廢話,直接看 performDraw 方法
ViewRootImpl #performDraw 方法
private void performDraw() {
...省略部分代碼
try {
draw(fullRedrawNeeded);
} finally {
mIsDrawing = false;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
...省略部分代碼
ViewRootImpl#draw() 方法
private void draw(boolean fullRedrawNeeded) {
...省略部分代碼
scrollToRectOrFocus(null, false);
if (mAttachInfo.mViewScrollChanged) {
mAttachInfo.mViewScrollChanged = false;
mAttachInfo.mTreeObserver.dispatchOnScrollChanged();
}
...省略部分代碼
mAttachInfo.mTreeObserver.dispatchOnDraw();
...省略部分代碼
//根據(jù)是否開啟了硬件加速输枯,是否開啟硬件加速议泵,View 的繪制流程都是一樣的,區(qū)別就是 Canvas 不同
if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
//開啟了硬件加速桃熄,則執(zhí)行該方法先口,內(nèi)部最終還是會執(zhí)行到view 的 draw 方法
mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
} else {
//未開啟硬件加速,則執(zhí)行該方法,直接調(diào)用 view 的 draw 方法
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
return;
}
}
}
measure 和 layout 過程直接調(diào)用的就是 ViewRootImpl的performMeasure和performLayout方法碉京,而draw調(diào)用的是ViewRootImpl的performDraw()方法厢汹,再由performDraw中的draw(boolean fullRedrawNeeded)方法來調(diào)用ViewTreeObserver中的dispatchOnDraw()方法,進(jìn)行通知所有掛在view樹上的view開始draw谐宙,隨后通過 drawSoftware 方法調(diào)用 view 的 draw 方法開始繪制
ViewTreeObserver.dispatchOnDraw()
public final void dispatchOnDraw() {
if (mOnDrawListeners != null) {
mInDispatchOnDraw = true;
final ArrayList<OnDrawListener> listeners = mOnDrawListeners;
int numListeners = listeners.size();
for (int i = 0; i < numListeners; ++i) {
listeners.get(i).onDraw();
}
mInDispatchOnDraw = false;
}
}
ViewRootImpl#drawSoftware 方法
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty) {
final Canvas canvas;
canvas = mSurface.lockCanvas(dirty);
//...省略部分代碼
mView.draw(canvas);
//...省略部分代碼
}
最終終于執(zhí)行到了 View 的 draw() 方法了烫葬,這個(gè)方法很重要,我們要顯示的內(nèi)容都是在這個(gè)方法中實(shí)現(xiàn)的卧惜,沒有實(shí)現(xiàn)這個(gè)方法的邏輯厘灼,就是前面的 Measure 和 Layout 邏輯處理的在漂亮,也不能呈現(xiàn)咽瓷。
View #draw(Canvas canvas)
public void draw(Canvas canvas) {
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN
/*
* 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
* //繪制 View 的內(nèi)容
* 3. Draw view's content
* //繪制子 View,子 View 的繪制也是按照這個(gè)流程進(jìn)行
* 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
// Step 1, 繪制背景
int saveCount;
if (!dirtyOpaque) {
drawBackground(canvas);
}
// 通常情況下,會跳過第 2 步和第 5 步
// 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) {
//繪制 View 的內(nèi)容钻洒,需要子類具體實(shí)現(xiàn)奋姿,View 的 onDraw 是一個(gè)空實(shí)現(xiàn),因?yàn)?View 并不是一個(gè)具體的 View 素标,不知道要繪制的內(nèi)容称诗,所以要繪制的內(nèi)容留給具體的子類去具體實(shí)現(xiàn)
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
//繪制內(nèi)部包含的子 View,這個(gè)方法 View 也沒有實(shí)現(xiàn)头遭,具體的實(shí)現(xiàn)是在 ViewGroup 中寓免,后面會具體分析該方法
// 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;
}
...省略部分代碼
View #drawBackground(Canvas)
該方法用于繪制 View 的背景计维,這個(gè)背景是我們在創(chuàng)建 View 時(shí)設(shè)定的
private void drawBackground(Canvas canvas) {
final Drawable background = mBackground;
if (background == null) {
return;
}
mBackground.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
...省略硬件加速相關(guān)代碼
//判斷 View 是否設(shè)置了 scrollX 和 mScrollY袜香,并平移滑動,繪制完背景后鲫惶,在平移到原來的位置
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);
}
}
View 的 onDraw(Canvas) 方法
protected void onDraw(Canvas canvas) {
}
onDraw 是用來繪制 View 的顯示內(nèi)容的蜈首,但是 View 并沒有實(shí)現(xiàn) onDraw ,具體的實(shí)現(xiàn)邏輯需要派生類去給根據(jù)自身情況去繪制具體的內(nèi)容欠母,可以參看 TextView 的 onDraw 方法
View #dispatchDraw
protected void dispatchDraw(Canvas canvas) {
}
dispatchDraw( )方法用于通知子View自己繪制欢策,View未實(shí)現(xiàn)該方法,由ViewGroup來實(shí)現(xiàn)該方法赏淌。我們來看一下吧踩寇;
ViewGroup#dispatchDraw(Canvas) 方法
protected void dispatchDraw(Canvas canvas) {
。猜敢。姑荷。(省略)
int clipSaveCount = 0;
final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
if (clipToPadding) {
// 這里會對畫布進(jìn)行剪切盒延,切掉Padding值
clipSaveCount = canvas.save();
canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop,
mScrollX + mRight - mLeft - mPaddingRight,
mScrollY + mBottom - mTop - mPaddingBottom);
}
。鼠冕。添寺。(省略)
// 遍歷子View
for (int i = 0; i < childrenCount; i++) {
。懈费。计露。(動畫相關(guān)操作 省略)
int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
final View child = (preorderedList == null)
? children[childIndex] : preorderedList.get(childIndex);
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
// 繪制子View
drawChild(canvas, child, drawingTime);
}
}
。憎乙。票罐。(省略)
if (clipToPadding) {
canvas.restoreToCount(clipSaveCount);
}
。泞边。该押。(省略)
}
ViewGroup#drawChild 方法
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
這里調(diào)用的是 View 這個(gè)類的重載方法,來看一下
View draw(Canvas canvas,ViewGroup parent,long drawingTime) 方法
// 畫布canvas的大小是
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
// 是否開啟硬件加速標(biāo)識
boolean drawingWithRenderNode = mAttachInfo != null
&& mAttachInfo.mHardwareAccelerated
&& hardwareAcceleratedCanvas;
阵谚。蚕礼。。省略
int sx = 0;
int sy = 0;
if (!drawingWithRenderNode) {
// 我們在自定義滑動控件時(shí)梢什,一般會重寫該方法奠蹬,并設(shè)置mScrollX和mScrollY
computeScroll();
sx = mScrollX;
sy = mScrollY;
}
final boolean drawingWithDrawingCache = cache != null && !drawingWithRenderNode;
final boolean offsetForScroll = cache == null && !drawingWithRenderNode;
int restoreTo = -1;
restoreTo = canvas.save();
// 根據(jù)mScrollX和mScrollY移動畫布的坐標(biāo)系
canvas.translate(mLeft - sx, mTop - sy);
。嗡午。囤躁。(設(shè)置畫布透明度的操作 省略)
if (!drawingWithRenderNode) {
// apply clips directly, since RenderNode won't do it for this draw
if ((parentFlags & ViewGroup.FLAG_CLIP_CHILDREN) != 0 && cache == null) {
canvas.clipRect(sx, sy, sx + getWidth(), sy + getHeight());
}
}
if (!drawingWithDrawingCache) {
if (drawingWithRenderNode) {
// 開啟硬件加速時(shí)走該分支
((DisplayListCanvas) canvas).drawRenderNode(renderNode);
} else {
// 調(diào)用View.draw()進(jìn)行繪制
draw(canvas);
}
}
if (restoreTo >= 0) {
canvas.restoreToCount(restoreTo);
}
。荔睹。狸演。省略
return more;
}
到這里關(guān)于 View 的三個(gè)工作流程就介紹完了,后面會詳細(xì)的介紹一下 View 的繪制技巧 —— Canvas 的使用应媚。