- 文章獨(dú)家授權(quán)公眾號(hào):碼個(gè)蛋
- 更多分享:http://www.cherylgood.cn
前言
- 大家好!本次我們將繼續(xù)學(xué)習(xí)Android之自定義View的死亡三部曲中的最后一部(Draw):畫出最真實(shí)的自己
- 在此之前,我們?cè)?a target="_blank" rel="nofollow">Android之自定義View的死亡三部曲之(Measure) 中分析了View測(cè)測(cè)量過程境析,獲得了View的三圍數(shù)據(jù)-測(cè)量后獲得高和寬拣播,在Android之自定義View的死亡三部曲之(Layout) 中分析了View的測(cè)量過程荡短,經(jīng)過測(cè)量后桌吃,我們就能拿到View的left、top榄攀、right嗜傅、bottom四個(gè)點(diǎn)的值。那么我們剩下最后一步檩赢,將我的的View繪制出來吕嘀。
- Ok,這次我們依然是以ViewRootImpl的performTraversals方法起點(diǎn)贞瞒。
private void performTraversals() {
...
if (!mStopped) {
//1偶房、獲取頂層布局的childWidthMeasureSpec
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
//2、獲取頂層布局的childHeightMeasureSpec
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
//3军浆、測(cè)量開始測(cè)量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
if (didLayout) {
//4棕洋、執(zhí)行布局方法
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
...
}
if (!cancelDraw && !newSurface) {
...
//5、開始繪制了哦
performDraw();
}
}
...
}
- 這次我們分析到performDraw方法了乒融。好的掰盘,我們一起來看下performDraw里面的代碼吧,我只保留來于本次分析相關(guān)的關(guān)鍵代碼。
private void performDraw() {
......
//1簇抵、fullRedrawNeeded這個(gè)變量標(biāo)識(shí)了本次繪制是否需要完全重新繪制
final boolean fullRedrawNeeded = mFullRedrawNeeded;
try {
//2庆杜、此處調(diào)用了ViewRootImpl的draw方法
draw(fullRedrawNeeded);
} finally {
mIsDrawing = false;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
......
}
- 看1處,既然有完全繪制碟摆,當(dāng)然也會(huì)有局部繪制了,這樣做是為了提高性能
- OK叨橱,我們看下draw這個(gè)方法里面的代碼
private void draw(boolean fullRedrawNeeded) {
......
//1典蜕、獲得dirty,也就是我們要繪制的區(qū)域
final Rect dirty = mDirty;
if (mSurfaceHolder != null) {
// The app owns the surface, we won't draw.
dirty.setEmpty();
if (animating) {
if (mScroller != null) {
mScroller.abortAnimation();
}
disposeResizeBuffer();
}
return;
}
//2罗洗、判斷是否需要完全繪制
if (fullRedrawNeeded) {
mAttachInfo.mIgnoreDirtyState = true;
//3愉舔、需要完全繪制時(shí),將dirty的值設(shè)置為神歌屏幕的大小
dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
}
......
//3伙菜、調(diào)用drawSoftware進(jìn)行繪制
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
return;
}
}
-
從上面的代碼分析中轩缤,我們看到,最后時(shí)通過調(diào)用drawSoftware進(jìn)行繪制贩绕,那么我們看下drawSoftware方法的代碼
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, boolean scalingRequired, Rect dirty) { //1火的、哈哈,看到了canvas淑倾,是不是感覺里繪制越來越近了 final Canvas canvas; try { //2馏鹤、取出繪制區(qū)域的四個(gè)位置的值 final int left = dirty.left; final int top = dirty.top; final int right = dirty.right; final int bottom = dirty.bottom; //3、傳入我們的繪制區(qū)域娇哆,創(chuàng)建一個(gè)被鎖定了繪制區(qū)域的canvas canvas = mSurface.lockCanvas(dirty); // The dirty rectangle can be modified by Surface.lockCanvas() //noinspection ConstantConditions if (left != dirty.left || top != dirty.top || right != dirty.right || bottom != dirty.bottom) { attachInfo.mIgnoreDirtyState = true; } //4湃累、設(shè)置畫布的密度 canvas.setDensity(mDensity); } try { if (!canvas.isOpaque() || yoff != 0 || xoff != 0) { //5勃救、清除畫布的顏色 canvas.drawColor(0, PorterDuff.Mode.CLEAR); } dirty.setEmpty(); mIsAnimating = false; attachInfo.mDrawingTime = SystemClock.uptimeMillis(); mView.mPrivateFlags |= View.PFLAG_DRAWN; try { //7、設(shè)置畫布的偏離值 canvas.translate(-xoff, -yoff); if (mTranslator != null) { mTranslator.translateCanvas(canvas); } canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0); attachInfo.mSetIgnoreDirtyState = false; //8治力、調(diào)用mView大的draw方法開始繪制 mView.draw(canvas); } } return true; }
-
Ok蒙秒,我們分析到第8步知道,最終調(diào)用了mView的draw開始繪制了宵统,而mView也就是DecorView晕讲,我們前面分析過DecorView是一個(gè)FrameLayout,而FrameLayout并沒有重現(xiàn)draw方法榜田,ViewGroup也沒有重寫益兄,所以,我們直接看View的draw方法箭券,代碼有點(diǎn)長(zhǎng)净捅,但是思路分清晰,官方給出的解釋也是非常清晰的
@CallSuper public void draw(Canvas canvas) { final int privateFlags = mPrivateFlags; //(1)辩块、dirtyOpaque標(biāo)識(shí)了當(dāng)前View是否時(shí)透明的 final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE && (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState); 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 * 3. Draw view's content * 4. Draw children * 5. If necessary, draw the fading edges and restore layers * 6. Draw decorations (scrollbars for instance) */ //上面的解釋大知識(shí)蛔六,繪制過程中有一系列的步驟,但是有幾個(gè)是必須要執(zhí)行的 //1废亭、繪制背景2国章、如果有需要,在可以先保存當(dāng)前canvas的層級(jí)數(shù)據(jù)豆村,3液兽、繪制View的內(nèi)容4、繪制View的子類5掌动、如果又需要四啰,在退出此次繪制時(shí)恢復(fù)之前的canvas的層級(jí)數(shù)據(jù) //6、繪制一些裝飾的效果 // Step 1, draw the background, if needed int saveCount; //(2)粗恢、透明時(shí)不需要繪制背景 if (!dirtyOpaque) { //(3)柑晒、不透明時(shí),繪制背景 drawBackground(canvas); } //他說可以跳過第2步和第5步眷射,說明第2和第5步時(shí)很重要 // 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 //(4)匙赞、如果不透明,繪制View的內(nèi)容 if (!dirtyOpaque) onDraw(canvas); // Step 4, draw the children //(5)將canvas傳遞給childView妖碉,將繪制事件傳遞下去 dispatchDraw(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); // we're done... return; } ...... }
OK,第2步和第5步是保存canves狀態(tài)和恢復(fù)的操作涌庭,我們這次就分析其他步驟就好
-
首先,我們看第1步嗅绸,在View非透明情況下脾猛,執(zhí)行背景的繪制操作
private void drawBackground(Canvas canvas) { final Drawable background = mBackground; //1、背景為null當(dāng)然是直接返回了 if (background == null) { return; } //2鱼鸠、確認(rèn)背景的邊界值 setBackgroundBounds(); // Attempt to use a display list if requested. if (canvas.isHardwareAccelerated() && mAttachInfo != null && mAttachInfo.mHardwareRenderer != null) { mBackgroundRenderNode = getDrawableRenderNode(background, mBackgroundRenderNode); final RenderNode renderNode = mBackgroundRenderNode; if (renderNode != null && renderNode.isValid()) { setBackgroundRenderNodeProperties(renderNode); ((DisplayListCanvas) canvas).drawRenderNode(renderNode); return; } } //3猛拴、獲取當(dāng)前的scrollX和scrollY的值 final int scrollX = mScrollX; final int scrollY = mScrollY; if ((scrollX | scrollY) == 0) { //此時(shí)沒有滾動(dòng)羹铅,開始繪制背景 background.draw(canvas); } else { //正在滾動(dòng),移動(dòng)canvas后繪制 canvas.translate(scrollX, scrollY); background.draw(canvas); canvas.translate(-scrollX, -scrollY); } }
從上面的分析愉昆,我有又個(gè)意外的發(fā)現(xiàn)职员,當(dāng)scrllX或者scrollY的值不為0時(shí),先使canvas偏移后在繪制跛溉,這就是為什么如果我們是使用Scoller來實(shí)現(xiàn)View的滑動(dòng)時(shí)焊切,實(shí)際上移動(dòng)的是View的可視區(qū)域,而不是View本身
-
我們看下setBackgroundBounds里面是如何確認(rèn)背景邊界的
void setBackgroundBounds() { if (mBackgroundSizeChanged && mBackground != null) { //1芳室、直接根據(jù)layout中獲得的四個(gè)位置的值直接確定 mBackground.setBounds(0, 0, mRight - mLeft, mBottom - mTop); mBackgroundSizeChanged = false; rebuildOutline(); } }
-
介紹完繪制背景专肪,我們接下來分析繪制內(nèi)容部分,我們看onDraw方法堪侯,沒錯(cuò)嚎尤,又是空的,因?yàn)檫@是我們?cè)谧远xView的時(shí)候需要自己去實(shí)現(xiàn)的
protected void onDraw(Canvas canvas) { }
OK伍宦,那我們看下一步芽死,傳遞繪制事件給child們
-
我們先看View的dispatchDraw,沒錯(cuò)次洼,還是空的关贵,View就是最原始的了,哪里有child嘛卖毁。
protected void dispatchDraw(Canvas canvas) { }
-
那么我們來看ViewGroup中的吧,源碼優(yōu)點(diǎn)長(zhǎng)揖曾,我保留于本次分析相關(guān)就好
@Override protected void dispatchDraw(Canvas canvas) { boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode); //1、獲取child的數(shù)據(jù) final int childrenCount = mChildrenCount; final View[] children = mChildren; int flags = mGroupFlags; ...... 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) { //2亥啦、調(diào)用drawChild傳遞canvas翩肌、child進(jìn)去繪制child more |= drawChild(canvas, child, drawingTime); } } ...... }
-
ok,重點(diǎn)是drawChild這個(gè)方法禁悠,我們看下drawChild里面做什么操作
protected boolean drawChild(Canvas canvas, View child, long drawingTime) { return child.draw(canvas, this, drawingTime); }
-
里面直接調(diào)用了child的draw方法。不過這個(gè)方法跟我們前面的分析的draw有點(diǎn)區(qū)別哦兑宇,沒錯(cuò)碍侦,參數(shù)個(gè)數(shù)不同,那么我們看下到底卻別在哪呢,這個(gè)方法的代碼很長(zhǎng)隶糕,我截取關(guān)鍵代碼
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) { ...... if (!drawingWithDrawingCache) { if (drawingWithRenderNode) { mPrivateFlags &= ~PFLAG_DIRTY_MASK; ((DisplayListCanvas) canvas).drawRenderNode(renderNode); } else { if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) { mPrivateFlags &= ~PFLAG_DIRTY_MASK; dispatchDraw(canvas); } else { // 1瓷产、這里調(diào)用子View的draw方法,并將調(diào)整好的canvas傳進(jìn)去 draw(canvas); } } } else if (cache != null) // 2枚驻、如果是cache模式濒旦,則利用cache mPrivateFlags &= ~PFLAG_DIRTY_MASK; if (layerType == LAYER_TYPE_NONE) { Paint cachePaint = parent.mCachePaint; if (cachePaint == null) { cachePaint = new Paint(); cachePaint.setDither(false); parent.mCachePaint = cachePaint; } cachePaint.setAlpha((int) (alpha * 255)); canvas.drawBitmap(cache, 0.0f, 0.0f, cachePaint); } else { int layerPaintAlpha = mLayerPaint.getAlpha(); mLayerPaint.setAlpha((int) (alpha * layerPaintAlpha)); canvas.drawBitmap(cache, 0.0f, 0.0f, mLayerPaint); mLayerPaint.setAlpha(layerPaintAlpha); } } ...... }
上面主要做的事情就是,如果有cache再登,就利用cache進(jìn)行繪制尔邓,沒有則直接調(diào)用View的draw方法晾剖。然后根據(jù)前面的分析,最終會(huì)調(diào)用個(gè)個(gè)View的onDraw進(jìn)行繪制操作
-
接下來到了第六部梯嗽,繪制裝飾物(例如recyclerView的滾動(dòng)條)齿尽,OK,我們來看下onDrawForeground方法
public void onDrawForeground(Canvas canvas) { //1灯节、繪制滾動(dòng)指示器 onDrawScrollIndicators(canvas); //2循头、繪制滾動(dòng)條 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 foreground.draw(canvas); } }
通過以上的分析,我們就把View的Draw分析完了炎疆,