[Android FrameWork 6.0源碼學習] View的重繪過程之Draw
View繪制的三部曲,測量,布局,繪畫
現在我們分析繪畫部分
測量和布局 在前兩篇文章中已經分析過了。不了解的可以去我的博客裏找一下
下面進入正題,開始分析調用以及函數原理
private void performDraw() { if (mAttachInfo.mDisplayState == Display.STATE_OFF && !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); } //... }
這個函數調用內部draw方法去處理繪畫前的工作,來繼續完成繪制工作
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)); } } } //當界面需要滾動的時候,這個方法會觸發Scroller類下的startScroll函數 scrollToRectOrFocus(null, false); //如果界面發生了滾動,就分發滾動監聽 if (mAttachInfo.mViewScrollChanged) { mAttachInfo.mViewScrollChanged = false; mAttachInfo.mTreeObserver.dispatchOnScrollChanged(); } boolean animating = mScroller != null && mScroller.computeScrollOffset(); final int curScrollY; if (animating) { curScrollY = mScroller.getCurrY(); } else { curScrollY = mScrollY; } if (mCurScrollY != curScrollY) { mCurScrollY = curScrollY; fullRedrawNeeded = true; if (mView instanceof RootViewSurfaceTaker) { ((RootViewSurfaceTaker) mView).onRootViewScrollYChanged(mCurScrollY); } } final float appScale = mAttachInfo.mApplicationScale; final boolean scalingRequired = mAttachInfo.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) { mAttachInfo.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); } //如果有註冊TreeObserver下的監聽,在調用onDraw之前會觸發 mAttachInfo.mTreeObserver.dispatchOnDraw(); int xOffset = 0; int yOffset = curScrollY; final WindowManager.LayoutParams params = mWindowAttributes; final Rect surfaceInsets = params != null ? params.surfaceInsets : null; if (surfaceInsets != null) { xOffset -= surfaceInsets.left; yOffset -= surfaceInsets.top; // Offset dirty rect for surface insets. dirty.offset(surfaceInsets.left, surfaceInsets.right); } boolean accessibilityFocusDirty = false; final Drawable drawable = mAttachInfo.mAccessibilityFocusDrawable; if (drawable != null) { final Rect bounds = mAttachInfo.mTmpInvalRect; final boolean hasFocus = getAccessibilityFocusedRect(bounds); if (!hasFocus) { bounds.setEmpty(); } if (!bounds.equals(drawable.getBounds())) { accessibilityFocusDirty = true; } } mAttachInfo.mDrawingTime = mChoreographer.getFrameTimeNanos() / TimeUtils.NANOS_PER_MS; if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) { if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) { // If accessibility focus moved, always invalidate the root. boolean invalidateRoot = accessibilityFocusDirty; // Draw with hardware renderer. mIsAnimating = false; if (mHardwareYOffset != yOffset || mHardwareXOffset != xOffset) { mHardwareYOffset = yOffset; mHardwareXOffset = xOffset; invalidateRoot = true; } mResizeAlpha = resizeAlpha; if (invalidateRoot) { mAttachInfo.mHardwareRenderer.invalidateRoot(); } dirty.setEmpty(); mBlockResizeBuffer = false; mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, this); } 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 (mAttachInfo.mHardwareRenderer != null && !mAttachInfo.mHardwareRenderer.isEnabled() && mAttachInfo.mHardwareRenderer.isRequested()) { try { mAttachInfo.mHardwareRenderer.initializeIfNeeded( mWidth, mHeight, mAttachInfo, mSurface, surfaceInsets); } catch (OutOfResourcesException e) { handleOutOfResourcesException(e); return; } mFullRedrawNeeded = true; scheduleTraversals(); return; } //在這個drawSOftWare方法中會調用view的draw方法,之後整個繪畫流程就跑起來了 if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) { return; } } } if (animating) { mFullRedrawNeeded = true; scheduleTraversals(); } }
這個函數在調用view的draw之前做了很多處理,大概總結一下就是view的滾動設置和硬件加速功能繪畫還有一些比較深入的屬性比如插圖之類,這些我們不怎麽關心
我們最關心的是 drawSoftWare 方法
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, boolean scalingRequired, Rect dirty) { // Draw with software renderer. final Canvas canvas; try { final int left = dirty.left; final int top = dirty.top; final int right = dirty.right; final int bottom = dirty.bottom; //獲取一塊畫布,這塊畫布會傳遞到各個onDraw方法中 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 || xoff != 0) { canvas.drawColor(0, PorterDuff.Mode.CLEAR); } dirty.setEmpty(); mIsAnimating = false; 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(-xoff, -yoff); if (mTranslator != null) { mTranslator.translateCanvas(canvas); } canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0); attachInfo.mSetIgnoreDirtyState = false; //從這開始觸發整個view樹的繪制 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; }
這個方法的作用就是先從系統的Surface中獲取一塊畫布,設置好畫布的屬性後傳遞到DecorView的draw方法中,從而激活整個view的繪畫流程
@CallSuper public void draw(Canvas canvas) { final int privateFlags = mPrivateFlags; 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) */ //第一步,繪制背景 //在第一步中,在drawBackground中會根據不同類型的背景,去調用不同類型下的draw方法 //比如,背景是一個BitmapDrawable,那麽就會調用BitmapDrawable的draw方法,這些都是用Drawable這個抽象類編寫的 //每種不同的背景都是集成自Drawable,這也就是面向抽象/面向接口編程的好處,可以處理很多種情況 int saveCount; if (!dirtyOpaque) { drawBackground(canvas); } // Fading Edge是View很神奇的一個效果,大家可以自己嘗試一下 final int viewFlags = mViewFlags; boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0; boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0; if (!verticalEdges && !horizontalEdges) { // 繪制自身,調用自身的onDraw方法 if (!dirtyOpaque) onDraw(canvas); // 調用ViewGroup的dispatchDraw方法,讓ViewGroup遍歷並調用所有的onDraw方法,整個view繪畫流程被激活 dispatchDraw(canvas); // 如果設置了 Overlay ,就調用並繪制 Overlay if (mOverlay != null && !mOverlay.isEmpty()) { mOverlay.getOverlayView().dispatchDraw(canvas); } // 繪制前景圖 onDrawForeground(canvas); // we‘re done... return; } /* * 後邊是對於Fading Edge效果的設置,這次就不再分析了,有興趣的朋友可以自己看一下這個效果 * */ }
剩下的流程就是view樹中各個子view自己的繪制流程了,然後ViewRootImpl在拿著繪制好的畫布
View的三部曲到此就分析完了,分析的不是那麽細致,但是最少大體流程還是拋出來了。
在那遙遠的ViewRootImpl中定義了一個叫做performTraversals函數,這個函數負責屏幕的顯示工作
首先是由PhoneWindow來制作一個DecorView出來,在由WindowManager創建所對應的ViewRootImpl出來,然後調用ViewRootImpl下的setView函數
setView函數會觸發requestLayout函數,這個函數會觸發performTraversals函數,最終我們view的onMeasure onLayout onDraw都會被調用,從而完成了整個view的重繪過程
當屏幕上的任何一個地方發生變化。都是調用performTraversals來完成了。比如:鐘表的分鐘發生改變等等....有興趣的朋友最好是自己在研讀一遍源碼,能學到很多有意思的功能
[Android FrameWork 6.0源碼學習] View的重繪過程之Draw