前言
在上一篇我们了解了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既然是透明的,那就没必要绘制它了。接着是绘制流程的六个步骤,接下来讲。
绘制流程的六个步骤:上面红色注释已经标记过
/** * 将背景画在指定的画布上 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); } } |
// 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;2.绘制内容:
/** * 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的其余部分,绘制例如滚动条,滚动指示器等 |