View的Draw過程也是和measure、layout過程一樣,都是父分發到子的一個過程。而View的Draw的繪製,我們在ViewRootImpl的performTraversals方法中可以看到如下代碼:
boolean cancelDraw = attachInfo.mTreeObserver.dispatchOnPreDraw() ||
viewVisibility != View.VISIBLE;
if (!cancelDraw && !newSurface) {
if (!skipDraw || mReportNextDraw) {
if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).startChangingAnimations();
}
mPendingTransitions.clear();
}
performDraw();
}
} else {
if (viewVisibility == View.VISIBLE) {
// Try again
scheduleTraversals();
} else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).endChangingAnimations();
}
mPendingTransitions.clear();
}
}
其中performDraw就是View的繪製入口,我們繼續看其中的代碼:
private void performDraw() {
if (!mAttachInfo.mScreenOn && !mReportNextDraw) {
return;
}
final boolean fullRedrawNeeded = mFullRedrawNeeded;
mFullRedrawNeeded = false;
mIsDrawing = true;
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");
try {
draw(fullRedrawNeeded);
} finally {
mIsDrawing = false;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
if (mReportNextDraw) {
mReportNextDraw = false;
if (LOCAL_LOGV) {
Log.v(TAG, "FINISHED DRAWING: " + mWindowAttributes.getTitle());
}
if (mSurfaceHolder != null && mSurface.isValid()) {
mSurfaceHolderCallback.surfaceRedrawNeeded(mSurfaceHolder);
SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
if (callbacks != null) {
for (SurfaceHolder.Callback c : callbacks) {
if (c instanceof SurfaceHolder.Callback2) {
((SurfaceHolder.Callback2)c).surfaceRedrawNeeded(
mSurfaceHolder);
}
}
}
}
try {
mWindowSession.finishDrawing(mWindow);
} catch (RemoteException e) {
}
}
}
重點是draw( fullRedrawNeeded ),我們可以繼續看這個函數:
private void draw(boolean fullRedrawNeeded) {
Surface surface = mSurface;
if (!surface.isValid()) {
return;
}
if (DEBUG_FPS) {
trackFPS();
}
if (!sFirstDrawComplete) {
synchronized (sFirstDrawHandlers) {
sFirstDrawComplete = true;
final int count = sFirstDrawHandlers.size();
for (int i = 0; i< count; i++) {
mHandler.post(sFirstDrawHandlers.get(i));
}
}
}
scrollToRectOrFocus(null, false);
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo.mViewScrollChanged) {
attachInfo.mViewScrollChanged = false;
attachInfo.mTreeObserver.dispatchOnScrollChanged();
}
int yoff;
boolean animating = mScroller != null && mScroller.computeScrollOffset();
if (animating) {
yoff = mScroller.getCurrY();
} else {
yoff = mScrollY;
}
if (mCurScrollY != yoff) {
mCurScrollY = yoff;
fullRedrawNeeded = true;
}
final float appScale = attachInfo.mApplicationScale;
final boolean scalingRequired = attachInfo.mScalingRequired;
int resizeAlpha = 0;
if (mResizeBuffer != null) {
long deltaTime = SystemClock.uptimeMillis() - mResizeBufferStartTime;
if (deltaTime < mResizeBufferDuration) {
float amt = deltaTime/(float) mResizeBufferDuration;
amt = mResizeInterpolator.getInterpolation(amt);
animating = true;
resizeAlpha = 255 - (int)(amt*255);
} else {
disposeResizeBuffer();
}
}
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;
}
if (fullRedrawNeeded) {
attachInfo.mIgnoreDirtyState = true;
dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
}
if (DEBUG_ORIENTATION || DEBUG_DRAW) {
Log.v(TAG, "Draw " + mView + "/"
+ mWindowAttributes.getTitle()
+ ": dirty={" + dirty.left + "," + dirty.top
+ "," + dirty.right + "," + dirty.bottom + "} surface="
+ surface + " surface.isValid()=" + surface.isValid() + ", appScale:" +
appScale + ", width=" + mWidth + ", height=" + mHeight);
}
invalidateDisplayLists();
attachInfo.mTreeObserver.dispatchOnDraw();
if (!dirty.isEmpty() || mIsAnimating) {
if (attachInfo.mHardwareRenderer != null && attachInfo.mHardwareRenderer.isEnabled()) {
// Draw with hardware renderer.
mIsAnimating = false;
mHardwareYOffset = yoff;
mResizeAlpha = resizeAlpha;
mCurrentDirty.set(dirty);
dirty.setEmpty();
attachInfo.mHardwareRenderer.draw(mView, attachInfo, this,
animating ? null : mCurrentDirty);
} else {
// If we get here with a disabled & requested hardware renderer, something went
// wrong (an invalidate posted right before we destroyed the hardware surface
// for instance) so we should just bail out. Locking the surface with software
// rendering at this point would lock it forever and prevent hardware renderer
// from doing its job when it comes back.
// Before we request a new frame we must however attempt to reinitiliaze the
// hardware renderer if it's in requested state. This would happen after an
// eglTerminate() for instance.
if (attachInfo.mHardwareRenderer != null &&
!attachInfo.mHardwareRenderer.isEnabled() &&
attachInfo.mHardwareRenderer.isRequested()) {
try {
attachInfo.mHardwareRenderer.initializeIfNeeded(mWidth, mHeight,
mHolder.getSurface());
} catch (OutOfResourcesException e) {
handleOutOfResourcesException(e);
return;
}
mFullRedrawNeeded = true;
scheduleTraversals();
return;
}
if (!drawSoftware(surface, attachInfo, yoff, scalingRequired, dirty)) {
return;
}
}
}
if (animating) {
mFullRedrawNeeded = true;
scheduleTraversals();
}
}
繪製過程分爲硬件繪製和軟件繪製過程,硬件繪製通過:
attachInfo.mHardwareRenderer.draw(mView, attachInfo, this,
animating ? null : mCurrentDirty);
軟件繪製通過調用:
drawSoftware(surface, attachInfo, yoff, scalingRequired, dirty)
drawSoftware方法代碼如下:
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int yoff,
boolean scalingRequired, Rect dirty) {
// Draw with software renderer.
Canvas canvas;
try {
int left = dirty.left;
int top = dirty.top;
int right = dirty.right;
int bottom = dirty.bottom;
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;
}
// TODO: Do this in native
canvas.setDensity(mDensity);
} catch (Surface.OutOfResourcesException e) {
handleOutOfResourcesException(e);
return false;
} catch (IllegalArgumentException e) {
Log.e(TAG, "Could not lock surface", e);
// Don't assume this is due to out of memory, it could be
// something else, and if it is something else then we could
// kill stuff (or ourself) for no reason.
mLayoutRequested = true; // ask wm for a new surface next time.
return false;
}
try {
if (DEBUG_ORIENTATION || DEBUG_DRAW) {
Log.v(TAG, "Surface " + surface + " drawing to bitmap w="
+ canvas.getWidth() + ", h=" + canvas.getHeight());
//canvas.drawARGB(255, 255, 0, 0);
}
// If this bitmap's format includes an alpha channel, we
// need to clear it before drawing so that the child will
// properly re-composite its drawing on a transparent
// background. This automatically respects the clip/dirty region
// or
// If we are applying an offset, we need to clear the area
// where the offset doesn't appear to avoid having garbage
// left in the blank areas.
if (!canvas.isOpaque() || yoff != 0) {
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
}
dirty.setEmpty();
mIsAnimating = false;
attachInfo.mDrawingTime = SystemClock.uptimeMillis();
mView.mPrivateFlags |= View.PFLAG_DRAWN;
if (DEBUG_DRAW) {
Context cxt = mView.getContext();
Log.i(TAG, "Drawing: package:" + cxt.getPackageName() +
", metrics=" + cxt.getResources().getDisplayMetrics() +
", compatibilityInfo=" + cxt.getResources().getCompatibilityInfo());
}
try {
canvas.translate(0, -yoff);
if (mTranslator != null) {
mTranslator.translateCanvas(canvas);
}
canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);
attachInfo.mSetIgnoreDirtyState = false;
mView.draw(canvas);
drawAccessibilityFocusedDrawableIfNeeded(canvas);
} finally {
if (!attachInfo.mSetIgnoreDirtyState) {
// Only clear the flag if it was not set during the mView.draw() call
attachInfo.mIgnoreDirtyState = false;
}
}
} finally {
try {
surface.unlockCanvasAndPost(canvas);
} catch (IllegalArgumentException e) {
Log.e(TAG, "Could not unlock surface", e);
mLayoutRequested = true; // ask wm for a new surface next time.
//noinspection ReturnInsideFinallyBlock
return false;
}
if (LOCAL_LOGV) {
Log.v(TAG, "Surface " + surface + " unlockCanvasAndPost");
}
}
return true;
}
drawSoftware這個方法的代碼中的dirty表示的是一個矩形區域,也就是我們需要重新繪製的區域。 canvas = mSurface.lockCanvas(dirty); 這一個會判斷我們的髒區域是不是需要的,如果這個區域所在的位置不可用了,或者是surface的大小或內容已經被改變了,那麼就會修改dirty的值,這時候,我們就可以忽略髒區域了。之後就是一些對畫布的初始化,然後就調用View的Draw方法了。
View的Draw方法有如下流程:
- backgroud.draw( canvas ) 也就是繪製背景
- onDraw( canvas ) 也就是繪製內容
- disptachDraw( canvas ) 繪製子元素
- onDrawScrollBars( canvas ) 繪製滾動條
重點要關注的是View本身內容的繪製和子視圖的繪製,即onDraw()和dispatchDraw()函數。 對於View.java和ViewGroup.java,onDraw()默認都是空實現,因爲具體View本身長什麼樣子是由View的設計者來決定的,默認不顯示任何東西。
View.java中dispatchDraw()默認爲空實現,因爲其不包含子視圖,而ViewGroup重載了dispatchDraw()來對其子視圖進行繪製,通常應用程序不應該對dispatchDraw()進行重載,其默認實現體現了View系統繪製的流程。那麼,接下來我們繼續分析下ViewGroup中dispatchDraw()的具體流程
dispatchDraw()的核心代碼就是通過for循環調用drawChild()對ViewGroup的每個子視圖進行繪製,上述代碼中如果FLAG_USE_CHILD_DRAWING_ORDER爲true,則子視圖的繪製順序通過getChildDrawingOrder來決定,默認的繪製順序即是子視圖加入ViewGroup的順序,而我們可以重載getChildDrawingOrder函數來更改默認的繪製順序,這會影響到子視圖之間的重疊關係。
drawChild()的核心過程就是爲子視圖分配合適的cavas剪切區,剪切區的大小正是由layout過程決定的,而剪切區的位置取決於滾動值以及子視圖當前的動畫。設置完剪切區後就會調用子視圖的draw()函數進行具體的繪製,如果子視圖的包含SKIP_DRAW標識,那麼僅調用dispatchDraw(),即跳過子視圖本身的繪製,但要繪製視圖可能包含的字視圖。 完成了dispatchDraw()過程後,View系統會調用onDrawScrollBars()來繪製滾動條。