Android UI绘制流程源码详细讲解Draw(Canvas canvas)
前言
在上一篇我們了解了Activity的構成后,接下來我們開始了解一下View的工作流程,就是measure、layout和draw。measure用來測量View的寬高,layout用來確定View的位置,draw則用來繪制View。接下來我們來看看具體繪制的流程以及,paint和Canvas在這中間所扮演的角色。
1.繪制流程
上一篇我們提到了在performTraversals當中一次調用了performMeasure,performLayout,performDraw方法。 接下來我們了解一下draw具體干嘛,那么我們看到ViewRootImpl. performDraw方法看下他是如何完成具體繪制的。
performTraversals
| // Remember if we must report the next draw. if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) { mReportNextDraw = true; } boolean cancelDraw = mAttachInfo.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 onMeasure會調用兩次的原因 scheduleTraversals();} else if (mPendingTransitions != null && mPendingTransitions.size() > 0) { for (int i = 0; i < mPendingTransitions.size(); ++i) { mPendingTransitions.get(i).endChangingAnimations();} mPendingTransitions.clear();} } mIsInTraversal = false; |
| 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);} // For whatever reason we didn't create a HardwareRenderer, end any // hardware animations that are now dangling if (mAttachInfo.mPendingAnimatingRenderNodes != null) { final int count = mAttachInfo.mPendingAnimatingRenderNodes.size();for (int i = 0; i < count; i++) { mAttachInfo.mPendingAnimatingRenderNodes.get(i).endAllAnimators();} mAttachInfo.mPendingAnimatingRenderNodes.clear();} if (mReportNextDraw) { mReportNextDraw = false;if (mAttachInfo.mHardwareRenderer != null) { mAttachInfo.mHardwareRenderer.fence();} 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) { } } } |
| 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);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();} } //獲取mDirty,該值表示需要重繪的區域 ?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;}//如果fullRedrawNeeded為真,則把dirty區域置為整個屏幕,表示整個視圖都需要繪制 //第一次繪制流程,需要繪制所有視圖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); } 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;} if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) { return;} } } if (animating) { mFullRedrawNeeded = true;scheduleTraversals(); } |
| /** * @return true if drawing was successful, false if an error occurred * 如果繪圖成功則為true,如果出現錯誤,則為false */ 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; //鎖定canvas區域,由dirty區域決定 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; //正式開始繪制 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; } |
View的繪制
由于ViewGroup沒有重寫draw方法,因此所有的View都是調用View----->draw方法,因此,我們直接看它的源碼:
| /** * Manually render this view (and all of its children) to the given Canvas. * The view must have already done a full layout before this function is * called. When implementing a view, implement * {@link #onDraw(android.graphics.Canvas)} instead of overriding this method. * If you do need to override this method, call the superclass version. *@param 畫布是視圖呈現的畫布。 * @param canvas The Canvas to which the View is rendered. * Opaque 不透明物 */ @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 對View的背景進行繪制 * 2. If necessary, save the canvas' layers to prepare for fading 保存當前的圖層信息 * 3. Draw view's content 繪制View的內容 * 4. Draw children 對View的子View進行繪制(如果有子View) * 5. If necessary, draw the fading edges and restore layers 繪制View的褪色的邊緣,類似于陰影效果 * 6. Draw decorations (scrollbars for instance) 繪制View的裝飾(例如:滾動條) */ // Step 1, draw the background, if needed int saveCount;if (!dirtyOpaque) { drawBackground(canvas);} // 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 if (!dirtyOpaque) onDraw(canvas);// Step 4, draw the children 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;} ................. ................. |
可以看到,draw過程比較復雜,但是邏輯十分清晰,而官方注釋也清楚地說明了每一步的做法。我們首先來看一開始的標記位dirtyOpaque,該標記位的作用是判斷當前View是否是透明的,如果View是透明的,那么根據下面的邏輯可以看出,將不會執行一些步驟,比如繪制背景、繪制內容等。這樣很容易理解,因為一個View既然是透明的,那就沒必要繪制它了。接著是繪制流程的六個步驟,接下來講。
繪制流程的六個步驟:上面紅色注釋已經標記過
? 1.繪制背景:
| /** * 將背景畫在指定的畫布上 onto:在什么......之上 * Draws the background onto the specified canvas. * * @param canvas Canvas on which to draw the background */ private void drawBackground(Canvas canvas) { final Drawable background = mBackground;if (background == null) { return;} 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;} } 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);} } |
| /** * Implement this to do your drawing. * * @param canvas the canvas on which the background will be drawn */ protected void onDraw(Canvas canvas) { } |
如果當前的View是一個ViewGroup類型,那么就需要繪制它的子View,這里調用了dispatchDraw,而View中該方法是空實現,實際是ViewGroup重寫了這個方法,那么我們來看看,ViewGroup------->dispatchDraw源碼:
| /**
* {@inheritDoc}
*/
@Override
protected void dispatchDraw(Canvas canvas) {
boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);final int childrenCount = mChildrenCount;final View[] children = mChildren;int flags = mGroupFlags;if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) {
final boolean buildCache = !isHardwareAccelerated();for (int i = 0; i < childrenCount; i++) {
final View child = children[i];if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
final LayoutParams params = child.getLayoutParams();attachLayoutAnimationParameters(child, params, i, childrenCount);bindLayoutAnimation(child);}
}
final LayoutAnimationController controller = mLayoutAnimationController;if (controller.willOverlap()) {
mGroupFlags |= FLAG_OPTIMIZE_INVALIDATE;}
controller.start();mGroupFlags &= ~FLAG_RUN_ANIMATION;mGroupFlags &= ~FLAG_ANIMATION_DONE;if (mAnimationListener != null) {
mAnimationListener.onAnimationStart(controller.getAnimation());}
}
int clipSaveCount = 0;final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;if (clipToPadding) {
clipSaveCount = canvas.save();canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop,mScrollX + mRight - mLeft - mPaddingRight,mScrollY + mBottom - mTop - mPaddingBottom);}
// We will draw our child's animation, let's reset the flag
mPrivateFlags &= ~PFLAG_DRAW_ANIMATION;mGroupFlags &= ~FLAG_INVALIDATE_REQUIRED;boolean more = false;final long drawingTime = getDrawingTime();if (usingRenderNodeProperties) canvas.insertReorderBarrier();final int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size();int transientIndex = transientCount != 0 ? 0 : -1;// Only use the preordered list if not HW accelerated, since the HW pipeline will do the
// draw reordering internally
final ArrayList<View> preorderedList = usingRenderNodeProperties? null : buildOrderedChildList();final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();for (int i = 0; i < childrenCount; i++) {
while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
final View transientChild = mTransientViews.get(transientIndex);if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||transientChild.getAnimation() != null) {
more |= drawChild(canvas, transientChild, drawingTime);}
transientIndex++;if (transientIndex >= transientCount) {
transientIndex = -1;}
}
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) {
more |= drawChild(canvas, child, drawingTime);}
}
while (transientIndex >= 0) {
// there may be additional transient views after the normal views
final View transientChild = mTransientViews.get(transientIndex);if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||transientChild.getAnimation() != null) {
more |= drawChild(canvas, transientChild, drawingTime);}
transientIndex++;if (transientIndex >= transientCount) {
break;}
}
if (preorderedList != null) preorderedList.clear();// Draw any disappearing views that have animations
if (mDisappearingChildren != null) {
final ArrayList<View> disappearingChildren = mDisappearingChildren;final int disappearingCount = disappearingChildren.size() - 1;// Go backwards -- we may delete as animations finish
for (int i = disappearingCount; i >= 0; i--) {
final View child = disappearingChildren.get(i);more |= drawChild(canvas, child, drawingTime);}
}
if (usingRenderNodeProperties) canvas.insertInorderBarrier();if (debugDraw()) {
onDebugDraw(canvas);}
if (clipToPadding) {
canvas.restoreToCount(clipSaveCount);}
// mGroupFlags might have been updated by drawChild()
flags = mGroupFlags;if ((flags & FLAG_INVALIDATE_REQUIRED) == FLAG_INVALIDATE_REQUIRED) {
invalidate(true);}
if ((flags & FLAG_ANIMATION_DONE) == 0 && (flags & FLAG_NOTIFY_ANIMATION_LISTENER) == 0 &&mLayoutAnimationController.isDone() && !more) {
// We want to erase the drawing cache and notify the listener after the
// next frame is drawn because one extra invalidate() is caused by
// drawChild() after the animation is over
mGroupFlags |= FLAG_NOTIFY_ANIMATION_LISTENER;final Runnable end = new Runnable() {
public void run() {
notifyAnimationListener();}
};post(end);}
}
|
里面的代碼主要遍歷了所有子View,每個子View都調用了drawChild這個方法,我們找到這個方法:
| /** * Draw one child of this View Group. This method is responsible for getting * the canvas in the right state. This includes clipping, translating so * that the child's scrolled origin is at 0, 0, and applying any animation * transformations. *畫一個這個視圖組的孩子。這個方法負責 獲得畫布在正確的狀態。這包括剪切,轉化 孩子的滾動原點在0 0,應用任何動畫 *轉換。 * @param canvas The canvas on which to draw the child * @param child Who to draw * @param drawingTime The time at which draw is occurring * @return True if an invalidate() was issued */ protected boolean drawChild(Canvas canvas, View child, long drawingTime) { return child.draw(canvas, this, drawingTime); } |
同樣開始向下遍歷
那么此時,其實同理于我門之前的測量和布局,父親取得所有子控件開始遍歷,調用子控件讓子控件自己調用自己的draw開始繪制自己
邏輯很清晰,都是先設定繪制區域,然后利用canvas進行繪制。
4.繪制裝飾:
| /** * Draw any foreground content for this view. * * <p>Foreground content may consist of scroll bars, a {@link #setForeground foreground} * drawable or other view-specific decorations. The foreground is drawn on top of the * primary view content.</p> * * @param canvas canvas to draw into */ public void onDrawForeground(Canvas canvas) { onDrawScrollIndicators(canvas);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.draw(canvas);} }注:所謂的繪制裝飾,就是指View除了背景、內容、子View的其余部分,繪制例如滾動條,滾動指示器等 |
總結
以上是生活随笔為你收集整理的Android UI绘制流程源码详细讲解Draw(Canvas canvas)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: kuma相关istio
- 下一篇: 信息系统项目管理师考试重点和难点分析