前言

之前已经和大家聊了onLayout的流程,本文将会继续聊一聊onDraw中做了什么?本文将集中关注软件渲染,关于Canvas的api源码解析暂时不会在本文聊,会专门开一个Skia源码解析进行分析。

如果遇到问题可以来https://www.jianshu.com/p/a4fb6a02ad53中讨论

正文

performTravel的方法走完onMeasure和onLayout流程后会走到下面这段代码段。

文件:/frameworks/base/core/java/android/view/ViewRootImpl.java

if (mFirst) {

if (sAlwaysAssignFocus || !isInTouchMode()) {

if (mView != null) {

if (!mView.hasFocus()) {

mView.restoreDefaultFocus();

} else {

...

}

}

} else {

View focused = mView.findFocus();

if (focused instanceof ViewGroup

&& ((ViewGroup) focused).getDescendantFocusability()

== ViewGroup.FOCUS_AFTER_DESCENDANTS) {

focused.restoreDefaultFocus();

}

}

}

final boolean changedVisibility = (viewVisibilityChanged || mFirst) && isViewVisible;

final boolean hasWindowFocus = mAttachInfo.mHasWindowFocus && isViewVisible;

final boolean regainedFocus = hasWindowFocus && mLostWindowFocus;

if (regainedFocus) {

mLostWindowFocus = false;

} else if (!hasWindowFocus && mHadWindowFocus) {

mLostWindowFocus = true;

}

if (changedVisibility || regainedFocus) {

boolean isToast = (mWindowAttributes == null) ? false

: (mWindowAttributes.type == WindowManager.LayoutParams.TYPE_TOAST);

...

}

mFirst = false;

mWillDrawSoon = false;

mNewSurfaceNeeded = false;

mActivityRelaunched = false;

mViewVisibility = viewVisibility;

mHadWindowFocus = hasWindowFocus;

if (hasWindowFocus && !isInLocalFocusMode()) {

final boolean imTarget = WindowManager.LayoutParams

.mayUseInputMethod(mWindowAttributes.flags);

if (imTarget != mLastWasImTarget) {

mLastWasImTarget = imTarget;

InputMethodManager imm = InputMethodManager.peekInstance();

if (imm != null && imTarget) {

imm.onPreWindowFocus(mView, hasWindowFocus);

imm.onPostWindowFocus(mView, mView.findFocus(),

mWindowAttributes.softInputMode,

!mHasHadWindowFocus, mWindowAttributes.flags);

}

}

}

在进入onDraw的流程之前,会先处理焦点。这个过程中可以分为2大步骤:

1.如果是第一次渲染,则说明之前的宽高都是都为0.在requestFocus方法中会有这个判断把整个焦点集中拦截下来:

private boolean canTakeFocus() {

return ((mViewFlags & VISIBILITY_MASK) == VISIBLE)

&& ((mViewFlags & FOCUSABLE) == FOCUSABLE)

&& ((mViewFlags & ENABLED_MASK) == ENABLED)

&& (sCanFocusZeroSized || !isLayoutValid() || hasSize());

}

而在每一次onMeasure之前,都会尝试集中一次焦点的遍历。其中requestFocusNoSearch方法中,如果没有测量过就会直接返回false。因为每一次更换焦点或者集中焦点都可能伴随着如背景drawable,statelistDrawable等切换。没有测量过就没有必要做这无用功(详情请看View的绘制流程(三) onMeasure

)。

因此此时为了弥补之前拒绝焦点的行为,会重新进行一次restoreDefaultFocus的行为进行requestFocus处理。

2.如果存在窗体焦点,同时不是打开了FLAG_LOCAL_FOCUS_MODE标志(这是一种特殊情况,一般打上这个标志位只有在startingWindow的快照中才会有,startingWindow具体是什么可以看看WMS在Activity启动中的职责 添加窗体(三))。

则会调用InputMethodManager的onPostWindowFocus方法启动带了android.view.InputMethod这个action的软键盘服务。详细的这里暂时不展开讨论。

onDraw流程

if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {

reportNextDraw();

}

boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;

if (!cancelDraw && !newSurface) {

if (mPendingTransitions != null && mPendingTransitions.size() > 0) {

for (int i = 0; i < mPendingTransitions.size(); ++i) {

mPendingTransitions.get(i).startChangingAnimations();

}

mPendingTransitions.clear();

}

performDraw();

} else {

if (isViewVisible) {

scheduleTraversals();

} else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {

for (int i = 0; i < mPendingTransitions.size(); ++i) {

mPendingTransitions.get(i).endChangingAnimations();

}

mPendingTransitions.clear();

}

}

mIsInTraversal = false;

1.判断到如果是第一次调用draw方法,则会调用reportNextDraw。

private void reportNextDraw() {

if (mReportNextDraw == false) {

drawPending();

}

mReportNextDraw = true;

}

void drawPending() {

mDrawsNeededToReport++;

}

能看到实际上就是设置mReportNextDraw为true。我们回顾一下前两个流程mReportNextDraw参与了标志位的判断。在执行onMeasure和onLayout有两个大前提,一个是mStop为false,一个是mReportNextDraw为true。只要满足其一就会执行。

这么做的目的只有一个,保证调用一次onDraw方法。为什么会这样呢?performDraw是整个Draw流程的入口。然而在这个入口,必须要保证cancelDraw为false以及newSurface为false。

注意,如果是第一次渲染因为会添加进新的Surface,此时newSurface为true(可以看View的绘制流程(二) 绘制的准备)。所以会走到下面的分之,如果串口可见则调用scheduleTraversals执行下一次Loop的绘制流程。否则判断是否有需要执行的LayoutTransitions layout动画就执行了。

因此第一次是不会走到onDraw,是从第二次Looper之后View的绘制流程才会执行onDraw。

我们继续关注performDraw的逻辑。

ViewRootImpl performDraw

private void performDraw() {

if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) {

return;

} else if (mView == null) {

return;

}

final boolean fullRedrawNeeded = mFullRedrawNeeded || mReportNextDraw;

mFullRedrawNeeded = false;

mIsDrawing = true;

Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");

boolean usingAsyncReport = false;

if (mReportNextDraw && mAttachInfo.mThreadedRenderer != null

&& mAttachInfo.mThreadedRenderer.isEnabled()) {

usingAsyncReport = true;

mAttachInfo.mThreadedRenderer.setFrameCompleteCallback((long frameNr) -> {

pendingDrawFinished();

});

}

try {

boolean canUseAsync = draw(fullRedrawNeeded);

if (usingAsyncReport && !canUseAsync) {

mAttachInfo.mThreadedRenderer.setFrameCompleteCallback(null);

usingAsyncReport = false;

}

} finally {

mIsDrawing = false;

Trace.traceEnd(Trace.TRACE_TAG_VIEW);

}

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 (mWindowDrawCountDown != null) {

try {

mWindowDrawCountDown.await();

} catch (InterruptedException e) {

Log.e(mTag, "Window redraw count down interrupted!");

}

mWindowDrawCountDown = null;

}

if (mAttachInfo.mThreadedRenderer != null) {

mAttachInfo.mThreadedRenderer.setStopped(mStopped);

}

if (mSurfaceHolder != null && mSurface.isValid()) {

SurfaceCallbackHelper sch = new SurfaceCallbackHelper(this::postDrawFinished);

SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();

sch.dispatchSurfaceRedrawNeededAsync(mSurfaceHolder, callbacks);

} else if (!usingAsyncReport) {

if (mAttachInfo.mThreadedRenderer != null) {

mAttachInfo.mThreadedRenderer.fence();

}

pendingDrawFinished();

}

}

}

我们把整个流程抽象出来实际上就是可以分为如下几个步骤:

对于软件渲染:

1.调用draw方法,遍历View的层级。

2.如果Surface是生效的,则在SurfaceHolder.Callback的surfaceRedrawNeededAsync回调中调用pendingDrawFinished。

3.如果是强制同步渲染,则会直接调用pendingDrawFinished。

对于硬件渲染:

1.调用draw方法,遍历View的层级。

2.通过监听mThreadedRenderer的setFrameCompleteCallback回调执行pendingDrawFinished方法。

我们先关注软件渲染的流程。也就是draw和pendingDrawFinished。

ViewRootImpl draw

private boolean draw(boolean fullRedrawNeeded) {

Surface surface = mSurface;

if (!surface.isValid()) {

return false;

}

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;

final Rect dirty = mDirty;

if (mSurfaceHolder != null) {

dirty.setEmpty();

if (animating && mScroller != null) {

mScroller.abortAnimation();

}

return false;

}

if (fullRedrawNeeded) {

mAttachInfo.mIgnoreDirtyState = true;

dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));

}

mAttachInfo.mTreeObserver.dispatchOnDraw();

int xOffset = -mCanvasOffsetX;

int yOffset = -mCanvasOffsetY + curScrollY;

final WindowManager.LayoutParams params = mWindowAttributes;

final Rect surfaceInsets = params != null ? params.surfaceInsets : null;

if (surfaceInsets != null) {

xOffset -= surfaceInsets.left;

yOffset -= surfaceInsets.top;

dirty.offset(surfaceInsets.left, surfaceInsets.right);

}

...

mAttachInfo.mDrawingTime =

mChoreographer.getFrameTimeNanos() / TimeUtils.NANOS_PER_MS;

boolean useAsyncReport = false;

if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {

if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {

boolean invalidateRoot = accessibilityFocusDirty || mInvalidateRootRequested;

mInvalidateRootRequested = false;

mIsAnimating = false;

if (mHardwareYOffset != yOffset || mHardwareXOffset != xOffset) {

mHardwareYOffset = yOffset;

mHardwareXOffset = xOffset;

invalidateRoot = true;

}

if (invalidateRoot) {

mAttachInfo.mThreadedRenderer.invalidateRoot();

}

dirty.setEmpty();

final boolean updated = updateContentDrawBounds();

if (mReportNextDraw) {

mAttachInfo.mThreadedRenderer.setStopped(false);

}

if (updated) {

requestDrawWindow();

}

useAsyncReport = true;

final FrameDrawingCallback callback = mNextRtFrameCallback;

mNextRtFrameCallback = null;

mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this, callback);

} else {

if (mAttachInfo.mThreadedRenderer != null &&

!mAttachInfo.mThreadedRenderer.isEnabled() &&

mAttachInfo.mThreadedRenderer.isRequested() &&

mSurface.isValid()) {

try {

mAttachInfo.mThreadedRenderer.initializeIfNeeded(

mWidth, mHeight, mAttachInfo, mSurface, surfaceInsets);

} catch (OutOfResourcesException e) {

handleOutOfResourcesException(e);

return false;

}

mFullRedrawNeeded = true;

scheduleTraversals();

return false;

}

if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,

scalingRequired, dirty, surfaceInsets)) {

return false;

}

}

}

if (animating) {

mFullRedrawNeeded = true;

scheduleTraversals();

}

return useAsyncReport;

}

大致上完成了如下流程:

1.如果surface无效则直接返回

sFirstDrawHandlers这个存储着runnable静态对象。实际上是在ActivityThread启动后调用attach方法通过addFirstDrawHandler添加进来的目的只是为了启动jit模式。

3.scrollToRectOrFocus 处理滑动区域或者焦点区域。如果发生了滑动则回调TreeObserver.dispatchOnScrollChanged。接下来则通过全局的mScroller通过computeScrollOffset判断是否需要滑动动画。如果需要执行动画,则调用DeocView的onRootViewScrollYChanged,进行Y轴上的动画执行。

4.通过ViewTreeObserver的dispatchOnDraw开始分发draw开始绘制的监听者。

5.判断是否存在surface面上偏移量,有就矫正一次脏区,把偏移量添加上去。

接下来则会进入到硬件渲染和软件渲染的分支。但是进一步进行调用draw的流程有几个前提条件:脏区不为空,需要执行动画,辅助服务发生了焦点变化

6.如果ThreadedRenderer不为空且可用。ThreadedRenderer通过onPreDraw回调到ViewRootImpl,更新mHardwareYOffset,mHardwareXOffset。如果这两个参数发生了变化,则说明整个发生了硬件绘制的区域变化,需要从头遍历一次所有的区域设置为无效区域,mThreadedRenderer.invalidateRoot。

最后调用ThreadedRenderer.draw方法执行硬件渲染绘制。并且设置通过registerRtFrameCallback设置进来的callback设置到ThreadedRenderer中。

7.如果此时ThreadedRenderer不可用但是不为空,说明此时需要对ThreadedRenderer进行初始化,调用scheduleTraversals在下一轮的绘制流程中才进行硬件渲染。

8.如果以上情况都不满足,说明是软件渲染,则调用drawSoftware进行软件渲染。

9.如果不许要draw方法遍历全局的View树,则判断是否需要执行滑动动画,需要则调用scheduleTraversals进入下一轮的绘制。

本文先抛开硬件渲染,来看看软件渲染drawSoftware中做了什么。还有scrollToRectOrFocus滑动中做了什么?

ViewRootImpl scrollToRectOrFocus

boolean scrollToRectOrFocus(Rect rectangle, boolean immediate) {

final Rect ci = mAttachInfo.mContentInsets;

final Rect vi = mAttachInfo.mVisibleInsets;

int scrollY = 0;

boolean handled = false;

if (vi.left > ci.left || vi.top > ci.top

|| vi.right > ci.right || vi.bottom > ci.bottom) {

final View focus = mView.findFocus();

if (focus == null) {

return false;

}

View lastScrolledFocus = (mLastScrolledFocus != null) ? mLastScrolledFocus.get() : null;

if (focus != lastScrolledFocus) {

rectangle = null;

}

if (focus == lastScrolledFocus && !mScrollMayChange && rectangle == null) {

} else {

mLastScrolledFocus = new WeakReference(focus);

mScrollMayChange = false;

if (focus.getGlobalVisibleRect(mVisRect, null)) {

if (rectangle == null) {

focus.getFocusedRect(mTempRect);

if (mView instanceof ViewGroup) {

((ViewGroup) mView).offsetDescendantRectToMyCoords(

focus, mTempRect);

}

} else {

mTempRect.set(rectangle);

}

if (mTempRect.intersect(mVisRect)) {

if (mTempRect.height() >

(mView.getHeight()-vi.top-vi.bottom)) {

}

else if (mTempRect.top < vi.top) {

scrollY = mTempRect.top - vi.top;

} else if (mTempRect.bottom > (mView.getHeight()-vi.bottom)) {

scrollY = mTempRect.bottom - (mView.getHeight()-vi.bottom);

} else {

scrollY = 0;

}

handled = true;

}

}

}

}

if (scrollY != mScrollY) {

if (!immediate) {

if (mScroller == null) {

mScroller = new Scroller(mView.getContext());

}

mScroller.startScroll(0, mScrollY, 0, scrollY-mScrollY);

} else if (mScroller != null) {

mScroller.abortAnimation();

}

mScrollY = scrollY;

}

return handled;

}

能看到在这个过程中实际上就是处理两个区域mVisibleInsets可见区域以及mContentInsets内容区域。

实际上这个过程就是从根部节点开始寻找焦点,然后整个画面定格在焦点处。因为mVisibleInsets一般是屏幕中出去过扫描区的大小,但是内容区域就不一定了,可能内容会超出屏幕大小,因此会通过mScroller滑动定位。

计算原理如下,分为2个情况:

1.可视区域的顶部比起获得了焦点的view的顶部要低,说明这个view在屏幕外了,需要向下滑动:

scrollY = mTempRect.top - vi.top;

2.如果焦点view的底部比起可视区域要比可视区域的低,说明需要向上滑动,注意滑动之后需要展示view,因此滑动的距离要减去view的高度:

scrollY = mTempRect.bottom - (mView.getHeight()-vi.bottom);

稍微变一下如下:

scrollY = mTempRect.bottom +vi.bottom - mView.getHeight();

ViewRootImpl drawSoftware

private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,

boolean scalingRequired, Rect dirty, Rect surfaceInsets) {

final Canvas canvas;

int dirtyXOffset = xoff;

int dirtyYOffset = yoff;

if (surfaceInsets != null) {

dirtyXOffset += surfaceInsets.left;

dirtyYOffset += surfaceInsets.top;

}

try {

dirty.offset(-dirtyXOffset, -dirtyYOffset);

final int left = dirty.left;

final int top = dirty.top;

final int right = dirty.right;

final int bottom = dirty.bottom;

canvas = mSurface.lockCanvas(dirty);

if (left != dirty.left || top != dirty.top || right != dirty.right

|| bottom != dirty.bottom) {

attachInfo.mIgnoreDirtyState = true;

}

canvas.setDensity(mDensity);

} catch (Surface.OutOfResourcesException e) {

handleOutOfResourcesException(e);

return false;

} catch (IllegalArgumentException e) {

mLayoutRequested = true; // ask wm for a new surface next time.

return false;

} finally {

dirty.offset(dirtyXOffset, dirtyYOffset); // Reset to the original value.

}

try {

if (!canvas.isOpaque() || yoff != 0 || xoff != 0) {

canvas.drawColor(0, PorterDuff.Mode.CLEAR);

}

dirty.setEmpty();

mIsAnimating = false;

mView.mPrivateFlags |= View.PFLAG_DRAWN;

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) {

attachInfo.mIgnoreDirtyState = false;

}

}

} finally {

try {

surface.unlockCanvasAndPost(canvas);

} catch (IllegalArgumentException e) {

Log.e(mTag, "Could not unlock surface", e);

mLayoutRequested = true; // ask wm for a new surface next time.

//noinspection ReturnInsideFinallyBlock

return false;

}

}

return true;

}

这里面的逻辑和上面硬件渲染逻辑有点相似:

1.同样还是根据全局的surface的偏移量对整个dirty区域进行偏移

2.通过Surface.lockCanvas方法映射一个Canvas对象,之后所有的绘制行为都在这个Canvas对象上。关于这个方法,详细的原理可以看看我写的SurfaceView和TextureView 源码浅析(上)。

3.获得Canvas后,由于上面是对整个surface的偏移,因此Canvas作为surface映射出来的绘制对象也需要进行一次偏移。

4.调用DecorView的draw方法,开始对整个View树遍历。

5.遍历完整个View树后,说明所有的信息已经会知道Canvas了,就可以通过surface.unlockCanvasAndPost,把记录在SkCanvas中的像素数据发送到SF中渲染到屏幕中。关于第五点详细的可以阅读SurfaceView和TextureView 源码浅析(上)。后续的步骤可以阅读我写的SF系列文章。

我们继续来要来看看draw方法做了什么。

DecorView draw

@Override

public void draw(Canvas canvas) {

super.draw(canvas);

if (mMenuBackground != null) {

mMenuBackground.draw(canvas);

}

}

能看到整个DecorView实际上就是调用了父类的draw方法后,专门给menu栏的drawable绘制到Canvas中。

View draw

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;

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) {

if (!dirtyOpaque) onDraw(canvas);

dispatchDraw(canvas);

drawAutofilledHighlight(canvas);

// Overlay is part of the content and draws beneath Foreground

if (mOverlay != null && !mOverlay.isEmpty()) {

mOverlay.getOverlayView().dispatchDraw(canvas);

}

onDrawForeground(canvas);

drawDefaultFocusHighlight(canvas);

return;

}

boolean drawTop = false;

boolean drawBottom = false;

boolean drawLeft = false;

boolean drawRight = false;

float topFadeStrength = 0.0f;

float bottomFadeStrength = 0.0f;

float leftFadeStrength = 0.0f;

float rightFadeStrength = 0.0f;

int paddingLeft = mPaddingLeft;

final boolean offsetRequired = isPaddingOffsetRequired();

if (offsetRequired) {

paddingLeft += getLeftPaddingOffset();

}

int left = mScrollX + paddingLeft;

int right = left + mRight - mLeft - mPaddingRight - paddingLeft;

int top = mScrollY + getFadeTop(offsetRequired);

int bottom = top + getFadeHeight(offsetRequired);

if (offsetRequired) {

right += getRightPaddingOffset();

bottom += getBottomPaddingOffset();

}

final ScrollabilityCache scrollabilityCache = mScrollCache;

final float fadeHeight = scrollabilityCache.fadingEdgeLength;

int length = (int) fadeHeight;

if (verticalEdges && (top + length > bottom - length)) {

length = (bottom - top) / 2;

}

if (horizontalEdges && (left + length > right - length)) {

length = (right - left) / 2;

}

if (verticalEdges) {

topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength()));

drawTop = topFadeStrength * fadeHeight > 1.0f;

bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength()));

drawBottom = bottomFadeStrength * fadeHeight > 1.0f;

}

if (horizontalEdges) {

leftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength()));

drawLeft = leftFadeStrength * fadeHeight > 1.0f;

rightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength()));

drawRight = rightFadeStrength * fadeHeight > 1.0f;

}

saveCount = canvas.getSaveCount();

int solidColor = getSolidColor();

if (solidColor == 0) {

if (drawTop) {

canvas.saveUnclippedLayer(left, top, right, top + length);

}

if (drawBottom) {

canvas.saveUnclippedLayer(left, bottom - length, right, bottom);

}

if (drawLeft) {

canvas.saveUnclippedLayer(left, top, left + length, bottom);

}

if (drawRight) {

canvas.saveUnclippedLayer(right - length, top, right, bottom);

}

} else {

scrollabilityCache.setFadeColor(solidColor);

}

// Step 3, draw the content

if (!dirtyOpaque) onDraw(canvas);

// Step 4, draw the children

dispatchDraw(canvas);

// Step 5, draw the fade effect and restore layers

final Paint p = scrollabilityCache.paint;

final Matrix matrix = scrollabilityCache.matrix;

final Shader fade = scrollabilityCache.shader;

if (drawTop) {

matrix.setScale(1, fadeHeight * topFadeStrength);

matrix.postTranslate(left, top);

fade.setLocalMatrix(matrix);

p.setShader(fade);

canvas.drawRect(left, top, right, top + length, p);

}

if (drawBottom) {

matrix.setScale(1, fadeHeight * bottomFadeStrength);

matrix.postRotate(180);

matrix.postTranslate(left, bottom);

fade.setLocalMatrix(matrix);

p.setShader(fade);

canvas.drawRect(left, bottom - length, right, bottom, p);

}

if (drawLeft) {

matrix.setScale(1, fadeHeight * leftFadeStrength);

matrix.postRotate(-90);

matrix.postTranslate(left, top);

fade.setLocalMatrix(matrix);

p.setShader(fade);

canvas.drawRect(left, top, left + length, bottom, p);

}

if (drawRight) {

matrix.setScale(1, fadeHeight * rightFadeStrength);

matrix.postRotate(90);

matrix.postTranslate(right, top);

fade.setLocalMatrix(matrix);

p.setShader(fade);

canvas.drawRect(right - length, top, right, bottom, p);

}

canvas.restoreToCount(saveCount);

drawAutofilledHighlight(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);

}

大致上draw方法分为如下几个步骤:

1.首先在draw方法中先校验mPrivateFlags中打开的标志位。还记得上一篇文章聊过的PFLAG_DIRTY_MASK的掩码实际上控制的是dirty以及透明两个标志位。

final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&

(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);

mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;

首先校验PFLAG_DIRTY_OPAQUE也就是透明标志位是否开启了,开启了dirtyOpaque则为true。同时打开PFLAG_DRAWN标志位,说明该View已经调用过了draw方法了。

2.如果dirtyOpaque为false说明不是透明则调用drawBackground,进行背景的绘制。

3.校验是否有横竖方向的边缘阴影需要绘制,如果不需要则以此执行如下三个流程:

1.执行该View重写的onDraw流程,进行绘制

2.dispatchDraw 把绘制行为分发到子View中

3.判断是否有overlay,有则绘制每一个View的浮层的dispatchDraw

4.onDrawForeground 绘制View的前景drawable

5.drawDefaultFocusHighlight 绘制默认的焦点高亮。

4.如果需要绘制上下左右四个方向的滑轮,则执行如下几个步骤:

1.计算滑轮的上下左右四个方向,根据是横向还是竖向计算其长度

2.把这一块内容作为Canvas的非裁剪区域绘制到外面区域

3.执行3.1以及3.2的步骤

4.根据绘制的上下左右四个方向,对滑轮进行旋转

5.执行3.3-3.5的步骤

我们关注核心行为3.1onDraw以及3.2dispatchDraw 以及drawBackground中完成了什么事情.

View drawBackground

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.mThreadedRenderer != 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);

}

}

能看到这里面绘制的逻辑分为硬件渲染和软件渲染:

硬件渲染首先会通过getDrawableRenderNode方法获取一个drawable渲染的renderNode,接着调用canvas的drawRenderNode。从之前我分析的TextureView一文中可以了解到硬件渲染,Canvas实质上就是DisplayListCanvas。

软件渲染则是调用draable的draw方法,把像素绘制到canvas智商。

getDrawableRenderNode

private RenderNode getDrawableRenderNode(Drawable drawable, RenderNode renderNode) {

if (renderNode == null) {

renderNode = RenderNode.create(drawable.getClass().getName(), this);

}

final Rect bounds = drawable.getBounds();

final int width = bounds.width();

final int height = bounds.height();

final DisplayListCanvas canvas = renderNode.start(width, height);

canvas.translate(-bounds.left, -bounds.top);

try {

drawable.draw(canvas);

} finally {

renderNode.end(canvas);

}

renderNode.setLeftTopRightBottom(bounds.left, bounds.top, bounds.right, bounds.bottom);

renderNode.setProjectBackwards(drawable.isProjected());

renderNode.setProjectionReceiver(true);

renderNode.setClipToBounds(false);

return renderNode;

}

能看到在绘制一个硬件渲染的drawable对象时候,会先生成一个RenderNode,调用start之后获取Drawable对象对应DisplayListCanvas,在调用drawable的draw方法,把信息绘制到DisplayListCanvas,最后返回renderNode。

再把这个drawable对应的renderNode添加到当前View的Canvas中。

ViewGroup dispatchDraw

View默认是留下一个onDraw的空方法。我们看看dispatchDraw中做了什么。

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.CLIP_SAVE_FLAG);

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;

final ArrayList 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;

}

}

final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);

final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);

if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {

more |= drawChild(canvas, child, drawingTime);

}

}

while (transientIndex >= 0) {

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 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 (clipToPadding) {

canvas.restoreToCount(clipSaveCount);

}

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) {

mGroupFlags |= FLAG_NOTIFY_ANIMATION_LISTENER;

final Runnable end = new Runnable() {

@Override

public void run() {

notifyAnimationListener();

}

};

post(end);

}

}

这个过程中做了如下几件事情:

1.判断是否打开了FLAG_RUN_ANIMATION标志位,且允许Layout动画。首先遍历该viewGroup中所有子View中所有的可见的子View,并且bindLayoutAnimation设置好每一个子View对应的Layout动画。

private void bindLayoutAnimation(View child) {

Animation a = mLayoutAnimationController.getAnimationForView(child);

child.setAnimation(a);

}

LayoutAnimationController 控制Layout动画的控制者调用start启动动画,并且回调监听。

3.判断是否被padding裁剪内容区域,默认是开启的。这个情况下,则会滑动的区域,padding区域,以及viewgroup的区域,进行裁剪,而不是统统都画到Canvas中。

canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop,

mScrollX + mRight - mLeft - mPaddingRight,

mScrollY + mBottom - mTop - mPaddingBottom);

4.判断是否打开了硬件加速。如果没有,buildOrderedChildList对当前该ViewGroup下所有子View进行z轴上的插入排序,从而得知谁将绘制在更加更加上方。这个过程中z轴上的数值越小,越先调用drawChild方法绘制到canvas中,也就是层级越低,被其他子View覆盖在其上。在处理每一个孩子对应的drawChild方法之前,会先处理通过addTransientView添加进来的临时View。这种方式十分少见,你可以看成临时动画一样的效果,不参加view的onmeasure,onLayout,但是会绘制出来。需要手动的remove掉。

5.绘制通过addDisappearingView添加的消失临时View。

6.如果FLAG_NOTIFY_ANIMATION_LISTENER,FLAG_ANIMATION_DONE标志位都关闭了,同时Layout动画也完成了,所有的drawChild都返回了false,则在下一个Looper中开始时机调用notifyAnimationListener,通知监听者本次动画已经完成。

整个核心都是drawChild方法,我们来看看drawChild做了什么?

drawChild

protected boolean drawChild(Canvas canvas, View child, long drawingTime) {

return child.draw(canvas, this, drawingTime);

}

核心调用了view的draw方法。但是注意了,这个draw和上面那个draw方法不太一样。

boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {

final boolean hardwareAcceleratedCanvas = canvas.isHardwareAccelerated();

boolean drawingWithRenderNode = mAttachInfo != null

&& mAttachInfo.mHardwareAccelerated

&& hardwareAcceleratedCanvas;

boolean more = false;

final boolean childHasIdentityMatrix = hasIdentityMatrix();

final int parentFlags = parent.mGroupFlags;

if ((parentFlags & ViewGroup.FLAG_CLEAR_TRANSFORMATION) != 0) {

parent.getChildTransformation().clear();

parent.mGroupFlags &= ~ViewGroup.FLAG_CLEAR_TRANSFORMATION;

}

Transformation transformToApply = null;

boolean concatMatrix = false;

final boolean scalingRequired = mAttachInfo != null && mAttachInfo.mScalingRequired;

final Animation a = getAnimation();

if (a != null) {

more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);

concatMatrix = a.willChangeTransformationMatrix();

if (concatMatrix) {

mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;

}

transformToApply = parent.getChildTransformation();

} else {

if ((mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_TRANSFORM) != 0) {

// No longer animating: clear out old animation matrix

mRenderNode.setAnimationMatrix(null);

mPrivateFlags3 &= ~PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;

}

if (!drawingWithRenderNode

&& (parentFlags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) {

final Transformation t = parent.getChildTransformation();

final boolean hasTransform = parent.getChildStaticTransformation(this, t);

if (hasTransform) {

final int transformType = t.getTransformationType();

transformToApply = transformType != Transformation.TYPE_IDENTITY ? t : null;

concatMatrix = (transformType & Transformation.TYPE_MATRIX) != 0;

}

}

}

concatMatrix |= !childHasIdentityMatrix;

// Sets the flag as early as possible to allow draw() implementations

// to call invalidate() successfully when doing animations

mPrivateFlags |= PFLAG_DRAWN;

if (!concatMatrix &&

(parentFlags & (ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS |

ViewGroup.FLAG_CLIP_CHILDREN)) == ViewGroup.FLAG_CLIP_CHILDREN &&

canvas.quickReject(mLeft, mTop, mRight, mBottom, Canvas.EdgeType.BW) &&

(mPrivateFlags & PFLAG_DRAW_ANIMATION) == 0) {

mPrivateFlags2 |= PFLAG2_VIEW_QUICK_REJECTED;

return more;

}

mPrivateFlags2 &= ~PFLAG2_VIEW_QUICK_REJECTED;

if (hardwareAcceleratedCanvas) {

// Clear INVALIDATED flag to allow invalidation to occur during rendering, but

// retain the flag's value temporarily in the mRecreateDisplayList flag

mRecreateDisplayList = (mPrivateFlags & PFLAG_INVALIDATED) != 0;

mPrivateFlags &= ~PFLAG_INVALIDATED;

}

RenderNode renderNode = null;

Bitmap cache = null;

int layerType = getLayerType(); // TODO: signify cache state with just 'cache' local

if (layerType == LAYER_TYPE_SOFTWARE || !drawingWithRenderNode) {

if (layerType != LAYER_TYPE_NONE) {

// If not drawing with RenderNode, treat HW layers as SW

layerType = LAYER_TYPE_SOFTWARE;

buildDrawingCache(true);

}

cache = getDrawingCache(true);

}

if (drawingWithRenderNode) {

renderNode = updateDisplayListIfDirty();

if (!renderNode.isValid()) {

renderNode = null;

drawingWithRenderNode = false;

}

}

int sx = 0;

int sy = 0;

if (!drawingWithRenderNode) {

computeScroll();

sx = mScrollX;

sy = mScrollY;

}

final boolean drawingWithDrawingCache = cache != null && !drawingWithRenderNode;

final boolean offsetForScroll = cache == null && !drawingWithRenderNode;

int restoreTo = -1;

if (!drawingWithRenderNode || transformToApply != null) {

restoreTo = canvas.save();

}

if (offsetForScroll) {

canvas.translate(mLeft - sx, mTop - sy);

} else {

if (!drawingWithRenderNode) {

canvas.translate(mLeft, mTop);

}

if (scalingRequired) {

if (drawingWithRenderNode) {

// TODO: Might not need this if we put everything inside the DL

restoreTo = canvas.save();

}

// mAttachInfo cannot be null, otherwise scalingRequired == false

final float scale = 1.0f / mAttachInfo.mApplicationScale;

canvas.scale(scale, scale);

}

}

float alpha = drawingWithRenderNode ? 1 : (getAlpha() * getTransitionAlpha());

if (transformToApply != null

|| alpha < 1

|| !hasIdentityMatrix()

|| (mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_ALPHA) != 0) {

if (transformToApply != null || !childHasIdentityMatrix) {

int transX = 0;

int transY = 0;

if (offsetForScroll) {

transX = -sx;

transY = -sy;

}

if (transformToApply != null) {

if (concatMatrix) {

if (drawingWithRenderNode) {

renderNode.setAnimationMatrix(transformToApply.getMatrix());

} else {

canvas.translate(-transX, -transY);

canvas.concat(transformToApply.getMatrix());

canvas.translate(transX, transY);

}

parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;

}

float transformAlpha = transformToApply.getAlpha();

if (transformAlpha < 1) {

alpha *= transformAlpha;

parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;

}

}

if (!childHasIdentityMatrix && !drawingWithRenderNode) {

canvas.translate(-transX, -transY);

canvas.concat(getMatrix());

canvas.translate(transX, transY);

}

}

if (alpha < 1 || (mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_ALPHA) != 0) {

if (alpha < 1) {

mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_ALPHA;

} else {

mPrivateFlags3 &= ~PFLAG3_VIEW_IS_ANIMATING_ALPHA;

}

parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;

if (!drawingWithDrawingCache) {

final int multipliedAlpha = (int) (255 * alpha);

if (!onSetAlpha(multipliedAlpha)) {

if (drawingWithRenderNode) {

renderNode.setAlpha(alpha * getAlpha() * getTransitionAlpha());

} else if (layerType == LAYER_TYPE_NONE) {

canvas.saveLayerAlpha(sx, sy, sx + getWidth(), sy + getHeight(),

multipliedAlpha);

}

} else {

mPrivateFlags |= PFLAG_ALPHA_SET;

}

}

}

} else if ((mPrivateFlags & PFLAG_ALPHA_SET) == PFLAG_ALPHA_SET) {

onSetAlpha(255);

mPrivateFlags &= ~PFLAG_ALPHA_SET;

}

if (!drawingWithRenderNode) {

if ((parentFlags & ViewGroup.FLAG_CLIP_CHILDREN) != 0 && cache == null) {

if (offsetForScroll) {

canvas.clipRect(sx, sy, sx + getWidth(), sy + getHeight());

} else {

if (!scalingRequired || cache == null) {

canvas.clipRect(0, 0, getWidth(), getHeight());

} else {

canvas.clipRect(0, 0, cache.getWidth(), cache.getHeight());

}

}

}

if (mClipBounds != null) {

// clip bounds ignore scroll

canvas.clipRect(mClipBounds);

}

}

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 {

draw(canvas);

}

}

} else if (cache != null) {

mPrivateFlags &= ~PFLAG_DIRTY_MASK;

if (layerType == LAYER_TYPE_NONE || mLayerPaint == null) {

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();

if (alpha < 1) {

mLayerPaint.setAlpha((int) (alpha * layerPaintAlpha));

}

canvas.drawBitmap(cache, 0.0f, 0.0f, mLayerPaint);

if (alpha < 1) {

mLayerPaint.setAlpha(layerPaintAlpha);

}

}

}

if (restoreTo >= 0) {

canvas.restoreToCount(restoreTo);

}

if (a != null && !more) {

if (!hardwareAcceleratedCanvas && !a.getFillAfter()) {

onSetAlpha(255);

}

parent.finishAnimatingView(this, a);

}

if (more && hardwareAcceleratedCanvas) {

if (a.hasAlpha() && (mPrivateFlags & PFLAG_ALPHA_SET) == PFLAG_ALPHA_SET) {

invalidate(true);

}

}

mRecreateDisplayList = false;

return more;

}

上面这个方法做了一个很重要的事情,那就是绘制每一个View的缓存。在这个过程中有两个比较重要的标志位:

boolean drawingWithRenderNode = mAttachInfo != null

&& mAttachInfo.mHardwareAccelerated

&& hardwareAcceleratedCanvas;

drawingWithRenderNode判断是否需要通过硬件绘制RenderNode。

final boolean drawingWithDrawingCache = cache != null && !drawingWithRenderNode;

drawingWithDrawingCache是否绘制view中的缓存由2点决定,一个是关闭硬件渲染,另一个是缓存的bitmap不为空。

做到的事情如下:

1.首先判断View中是否通过setAnimation设置了一个变换矩阵Transformation动画到View中。如果有,则调用applyLegacyAnimation方法确定整个变化动画刷新的范围在这个View范围内;transformToApply设置为父容器的Transformation。如果没有设置Animation,则判断是否关闭了硬件渲染且打开了FLAG_SUPPORT_STATIC_TRANSFORMATIONS。也就是说当前的View的父容器是否存在一个静态的变换矩阵,存在则更新到transformToApply中。

2.如果mLayerType为LAYER_TYPE_SOFTWARE或者关闭了硬件渲染,说明是一个软件渲染,则调用buildDrawingCache构建一个绘制的缓存,通过getDrawingCache获取这个缓存bitmap到cache中。

3.如果drawingWithRenderNode为true,说明在使用硬件渲染。则调用updateDisplayListIfDirty更新DisplayList中的脏区。

4.drawingWithRenderNode为false,则调用computeScroll计算滑动区域,赋值给sx和sy。

5.如果存在一个变换矩阵或者关闭硬件渲染,则调用canvas的save方法保存当前的状态。如果offsetForScroll为true(说明此时是软件渲染同时没有缓存),则调用canvas的translate方法参数第四步骤中计算出来平移距离。

横向平移:mLeft - sx

纵向平移: mTop - sy

这么做可以把绘制的原点移动到平移 平移后的位置,之后所有的绘制都是基于这个点进行的

6.offsetForScroll为false,说明此时可能需要绘制缓存或者是硬件渲染,只做了两件事情:平移当前的画布的绘制原点,如果需要则对整个画布进行伸缩。

7.如果transformToApply不为空,前提下。发现打开了硬件渲染,则调用RenderNode.setAnimationMatrix方法设置动画的矩阵。如果关闭,则先回退经过平移的Canvas原点,先对动画矩阵进行合并后在进行滑动的移动。

8.如果alpha小于1,且判断到drawingWithDrawingCache关闭的。则说明可以在当前的绘制结果中进行透明度处理。判断onSetAlpha为false,如果是硬件渲染则调用renderNode.setAlpha,如果是软件渲染则调用canvas.saveLayerAlpha。如果绘制缓存是打开。onSetAlpha如果为true说明整个透明是由子View决定的,因此先打开PFLAG_ALPHA_SET标志位,等待后续的处理。

9.如果父容器打开了FLAG_CLIP_CHILDREN标志位且当前的View没有缓存。说明当前的View绘制的结果需要被父容器裁剪了:

如果进行了滑动,则裁剪区域如下:

canvas.clipRect(sx, sy, sx + getWidth(), sy + getHeight());

如果没有缓存,则直接裁剪当前的View

canvas.clipRect(0, 0, getWidth(), getHeight());

有缓存:

canvas.clipRect(0, 0, cache.getWidth(), cache.getHeight());

如果需要根据边缘进行裁剪:

canvas.clipRect(mClipBounds);

10.drawingWithDrawingCache如果是关闭,且drawingWithRenderNode是打开的,则调用DisplayListCanvas.drawRenderNode(renderNode) 方法。参数中的renderNode是updateDisplayListIfDirty方法生成一个新的DisplayListCanvas。通过drawRenderNode把结果绘制到父容器的DisplayListCanvas。

11.drawingWithDrawingCache关闭,drawingWithRenderNode也是关闭的。说明此时是直接进行绘制。如果打开了PFLAG_SKIP_DRAW标志位说明需要直接掉过当前的View,直接调用dispatchDraw分发View的绘制命令。如果没有打开,则调用draw方法。就会继续调用onDraw后并且dispatchDraw分发View的绘制方法。

12.drawingWithDrawingCache是打开的,同时cache缓存不为空,则把cache中的结果绘制到Canvas中。

13.当所有子View都绘制结束之后,则调用canvas.restoreToCount方法一层层的恢复绘制状态。主要还是恢复绘制原点。调用父容器的finishAnimatingView清空所有的Animation以及disappearingAnimation,回调onAnimationEnd。

在这几点中,除去Canvas操作(关于Canvas操作,我会专门开一个Skia源码解析专题进行分析)有几个比较重要的方法:

1.buildDrawingCache 构建一个绘制缓存对象

2.updateDisplayListIfDirty 硬件渲染更新脏区

buildDrawingCache

public void buildDrawingCache(boolean autoScale) {

if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0 || (autoScale ?

mDrawingCache == null : mUnscaledDrawingCache == null)) {

try {

buildDrawingCacheImpl(autoScale);

} finally {

}

}

}

能看到实际上就是调用buildDrawingCacheImpl.

buildDrawingCacheImpl

private void buildDrawingCacheImpl(boolean autoScale) {

mCachingFailed = false;

int width = mRight - mLeft;

int height = mBottom - mTop;

final AttachInfo attachInfo = mAttachInfo;

final boolean scalingRequired = attachInfo != null && attachInfo.mScalingRequired;

if (autoScale && scalingRequired) {

width = (int) ((width * attachInfo.mApplicationScale) + 0.5f);

height = (int) ((height * attachInfo.mApplicationScale) + 0.5f);

}

final int drawingCacheBackgroundColor = mDrawingCacheBackgroundColor;

final boolean opaque = drawingCacheBackgroundColor != 0 || isOpaque();

final boolean use32BitCache = attachInfo != null && attachInfo.mUse32BitDrawingCache;

final long projectedBitmapSize = width * height * (opaque && !use32BitCache ? 2 : 4);

final long drawingCacheSize =

ViewConfiguration.get(mContext).getScaledMaximumDrawingCacheSize();

if (width <= 0 || height <= 0 || projectedBitmapSize > drawingCacheSize) {

if (width > 0 && height > 0) {

Log.w(VIEW_LOG_TAG, getClass().getSimpleName() + " not displayed because it is"

+ " too large to fit into a software layer (or drawing cache), needs "

+ projectedBitmapSize + " bytes, only "

+ drawingCacheSize + " available");

}

destroyDrawingCache();

mCachingFailed = true;

return;

}

boolean clear = true;

Bitmap bitmap = autoScale ? mDrawingCache : mUnscaledDrawingCache;

if (bitmap == null || bitmap.getWidth() != width || bitmap.getHeight() != height) {

Bitmap.Config quality;

if (!opaque) {

// Never pick ARGB_4444 because it looks awful

// Keep the DRAWING_CACHE_QUALITY_LOW flag just in case

switch (mViewFlags & DRAWING_CACHE_QUALITY_MASK) {

case DRAWING_CACHE_QUALITY_AUTO:

case DRAWING_CACHE_QUALITY_LOW:

case DRAWING_CACHE_QUALITY_HIGH:

default:

quality = Bitmap.Config.ARGB_8888;

break;

}

} else {

quality = use32BitCache ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565;

}

if (bitmap != null) bitmap.recycle();

try {

bitmap = Bitmap.createBitmap(mResources.getDisplayMetrics(),

width, height, quality);

bitmap.setDensity(getResources().getDisplayMetrics().densityDpi);

if (autoScale) {

mDrawingCache = bitmap;

} else {

mUnscaledDrawingCache = bitmap;

}

if (opaque && use32BitCache) bitmap.setHasAlpha(false);

} catch (OutOfMemoryError e) {

if (autoScale) {

mDrawingCache = null;

} else {

mUnscaledDrawingCache = null;

}

mCachingFailed = true;

return;

}

clear = drawingCacheBackgroundColor != 0;

}

Canvas canvas;

if (attachInfo != null) {

canvas = attachInfo.mCanvas;

if (canvas == null) {

canvas = new Canvas();

}

canvas.setBitmap(bitmap);

attachInfo.mCanvas = null;

} else {

canvas = new Canvas(bitmap);

}

if (clear) {

bitmap.eraseColor(drawingCacheBackgroundColor);

}

computeScroll();

final int restoreCount = canvas.save();

if (autoScale && scalingRequired) {

final float scale = attachInfo.mApplicationScale;

canvas.scale(scale, scale);

}

canvas.translate(-mScrollX, -mScrollY);

mPrivateFlags |= PFLAG_DRAWN;

if (mAttachInfo == null || !mAttachInfo.mHardwareAccelerated ||

mLayerType != LAYER_TYPE_NONE) {

mPrivateFlags |= PFLAG_DRAWING_CACHE_VALID;

}

if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {

mPrivateFlags &= ~PFLAG_DIRTY_MASK;

dispatchDraw(canvas);

drawAutofilledHighlight(canvas);

if (mOverlay != null && !mOverlay.isEmpty()) {

mOverlay.getOverlayView().draw(canvas);

}

} else {

draw(canvas);

}

canvas.restoreToCount(restoreCount);

canvas.setBitmap(null);

if (attachInfo != null) {

attachInfo.mCanvas = canvas;

}

}

1.在创建绘制的缓存bitmap之前,如果当前View的宽高其中之一小于等于0,或者当前View需要内存大于最大允许的缓存View大小。

这个过程中,View绘制后的缓存计算方法如下:

projectedBitmapSize = width * height * (opaque && !use32BitCache ? 2 : 4);

能看到这个过程中判断是否需要透明且关闭32的缓存,一个像素就会2位,否则则是4位。

如果计算出来的结果比MAXIMUM_DRAWING_CACHE_SIZE大则销毁绘制缓存。

private static final int MAXIMUM_DRAWING_CACHE_SIZE = 480 * 800 * 4; // ARGB8888

能看到每一个View最大只能是由宽480,高800且是ARGB8888模式内存大小。

2.如果View的大小发生了变化,则调用Bitmap.createBitmap的方法创建一个对应View大小的Bitmap。

3.如果attachInfo不为空,则判断是否存在一个全局的Canvas,如果不存在就创建一个新的Canvas,并把bitmap设置到Canvas中。

4.如果需要进行伸缩,则伸缩缓存bitmap。如果需要滑动,则移动整个Canvas的绘制原点。

5.如果PFLAG_SKIP_DRAW 打开了,则直接调用dispatchDraw,继续分发绘制流程。关闭了则调用draw方法,先调用onDraw后调用dispatchDraw。

能看到在上面draw方法中因为检测存在绘制缓存而跳过的流程,在这个方法中都进行处理了。

updateDisplayListIfDirty

public RenderNode updateDisplayListIfDirty() {

final RenderNode renderNode = mRenderNode;

if (!canHaveDisplayList()) {

return renderNode;

}

if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0

|| !renderNode.isValid()

|| (mRecreateDisplayList)) {

if (renderNode.isValid()

&& !mRecreateDisplayList) {

mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;

mPrivateFlags &= ~PFLAG_DIRTY_MASK;

dispatchGetDisplayList();

return renderNode; // no work needed

}

mRecreateDisplayList = true;

int width = mRight - mLeft;

int height = mBottom - mTop;

int layerType = getLayerType();

final DisplayListCanvas canvas = renderNode.start(width, height);

try {

if (layerType == LAYER_TYPE_SOFTWARE) {

buildDrawingCache(true);

Bitmap cache = getDrawingCache(true);

if (cache != null) {

canvas.drawBitmap(cache, 0, 0, mLayerPaint);

}

} else {

computeScroll();

canvas.translate(-mScrollX, -mScrollY);

mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;

mPrivateFlags &= ~PFLAG_DIRTY_MASK;

// Fast path for layouts with no backgrounds

if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {

dispatchDraw(canvas);

drawAutofilledHighlight(canvas);

if (mOverlay != null && !mOverlay.isEmpty()) {

mOverlay.getOverlayView().draw(canvas);

}

if (debugDraw()) {

debugDrawFocus(canvas);

}

} else {

draw(canvas);

}

}

} finally {

renderNode.end(canvas);

setDisplayListProperties(renderNode);

}

} else {

mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;

mPrivateFlags &= ~PFLAG_DIRTY_MASK;

}

return renderNode;

}

1.如果canHaveDisplayList为 false也就是mThreadedRenderer为null,则直接返回。

如果PFLAG_DRAWING_CACHE_VALID关闭,或者renderNode是无效的,或者mRecreateDisplayList是false。则进入到RenderNode的Canvas绘制中。

2.如果renderNode是有效的,且不需要进行重新构建整个硬件渲染的DisplayList(mRecreateDisplayList为false),说明不是第一次绘制了已经有绘制结果了,则调用dispatchGetDisplayList。

3.接下来的情景说明是第一次绘制,renderNode还是属于无效状态。其初始化流程如下:

1.renderNode.start(width, height) 调用start方法创建一个全新的DisplayListCanvas

2.如果LayerType是LAYER_TYPE_SOFTWARE,就算是Layer的模式是软件渲染模式,如果打开了硬件渲染模式,还是会把当前View对应绘制缓存bitmap通过setBitmap的方式设置到DisplayListCanvas中。

3.如果不是LAYER_TYPE_SOFTWARE,调用computeScroll进行滑动计算后。把整个RenderNode的绘制原点退回到滑动之前的状态,并且打上PFLAG_DRAWN和PFLAG_DRAWING_CACHE_VALID两个标志位。

4.如果打开PFLAG_SKIP_DRAW,则直接调用dispatchDraw,分发绘制流程。

5.没有打开PFLAG_SKIP_DRAW,则直接调用draw方法,先回调onDraw再调用dispatchDraw进行分发。

4.最后调用renderNode.end(canvas) 结束整个RenderNode的绘制。

我们来看看非第一次绘制时候dispatchGetDisplayList做了什么?

ViewGroup dispatchGetDisplayList

protected void dispatchGetDisplayList() {

final int count = mChildrenCount;

final View[] children = mChildren;

for (int i = 0; i < count; i++) {

final View child = children[i];

if (((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null)) {

recreateChildDisplayList(child);

}

}

final int transientCount = mTransientViews == null ? 0 : mTransientIndices.size();

for (int i = 0; i < transientCount; ++i) {

View child = mTransientViews.get(i);

if (((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null)) {

recreateChildDisplayList(child);

}

}

if (mOverlay != null) {

View overlayView = mOverlay.getOverlayView();

recreateChildDisplayList(overlayView);

}

if (mDisappearingChildren != null) {

final ArrayList disappearingChildren = mDisappearingChildren;

final int disappearingCount = disappearingChildren.size();

for (int i = 0; i < disappearingCount; ++i) {

final View child = disappearingChildren.get(i);

recreateChildDisplayList(child);

}

}

}

能看到实际上很简单,对4种View进行recreateChildDisplayList处理。

1.所有的可见子View或者带着动画的子View

2.mTransientViews 通过addTransientView添加进来的临时View

3.overlayView 每一个View的浮层

4.mDisappearingChildren 通过addDisappearingView 添加进来的当View移除时候需要的动画View。

ViewGroup recreateChildDisplayList

private void recreateChildDisplayList(View child) {

child.mRecreateDisplayList = (child.mPrivateFlags & PFLAG_INVALIDATED) != 0;

child.mPrivateFlags &= ~PFLAG_INVALIDATED;

child.updateDisplayListIfDirty();

child.mRecreateDisplayList = false;

}

能看到这个过程实际上就是调用了子View的updateDisplayListIfDirty方法。

pendingDrawFinished

当一切都处理完毕之后,就会调用pendingDrawFinished。如果mDrawsNeededToReport计数为0,则说明所有需要绘制的命令全部完成了。最后调用reportDrawFinished。

void pendingDrawFinished() {

if (mDrawsNeededToReport == 0) {

throw new RuntimeException("Unbalanced drawPending/pendingDrawFinished calls");

}

mDrawsNeededToReport--;

if (mDrawsNeededToReport == 0) {

reportDrawFinished();

}

}

reportDrawFinished

private void reportDrawFinished() {

try {

mDrawsNeededToReport = 0;

mWindowSession.finishDrawing(mWindow);

} catch (RemoteException e) {

// Have fun!

}

}

能看到最后会调用reportDrawFinished,通知WindowSession已经finishDrawing。

public void finishDrawing(IWindow window) {

if (WindowManagerService.localLOGV) Slog.v(

TAG_WM, "IWindow finishDrawing called for " + window);

mService.finishDrawingWindow(this, window);

}

WMS finishDrawingWindow

void finishDrawingWindow(Session session, IWindow client) {

final long origId = Binder.clearCallingIdentity();

try {

synchronized (mWindowMap) {

WindowState win = windowForClientLocked(session, client, false);

if (win != null && win.mWinAnimator.finishDrawingLocked()) {

if ((win.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0) {

win.getDisplayContent().pendingLayoutChanges |=

WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;

}

win.setDisplayLayoutNeeded();

mWindowPlacerLocked.requestTraversal();

}

}

} finally {

Binder.restoreCallingIdentity(origId);

}

}

会调用WindowState的setDisplayLayoutNeeded。设置DisplayContent的mLayoutNeeded为true。

调用WindowSurfacePlacer的requestTraversal。而这个方法会在AnimationHandler 窗体动画的handler中调用performSurfacePlacement。而这里的逻辑可以阅读WMS在Activity启动中的职责 计算窗体的大小

总结

到这列就完成了onDraw的解析。从onMeasure,onLayout,onDraw四个流程已经过了一遍。但是还没有仔细聊聊硬件渲染,但是没关系,从软件渲染也能一窥整个核心流程了。

老规矩,先来一副时序图:

View的绘制流程.jpg

整个onDraw的入口依次执行了如下的方法:

1.执行该View的background 背景的绘制

2.执行该View重写的onDraw流程,进行绘制

3.dispatchDraw 把绘制行为分发到子View中

4.判断是否有overlay,有则绘制每一个View的浮层的dispatchDraw

5.onDrawForeground 绘制View的前景drawable

6.drawDefaultFocusHighlight 绘制默认的焦点高亮。

每一次进行一次onDraw之前对dirtyOpaque标志位进行判断,实际上就是判断是否是透明的,是透明的就不会调用该View的onDraw方法。

其中dispatchDraw进行绘制行为分发后,就会调用drawChild的方法会每一个子View的draw方法。同时会通过buildOrderedChildList插入排序计算z轴上的绘制的顺序,保证z轴上会根据大小顺序调用drawChild进行覆盖绘制。

这个过程中,软件渲染过程中会伴随着绘制一个Bitmap的缓存。每一个View都能够申请到的缓存最大数值就是:

(宽/高)480 * (宽/高)800 * 4(ARGB8888)

如果超出这个数值就不会出现缓存,或者直到OOM了也会销毁缓存。

当执行完毕之后,必定会调用WMS的finishDrawingWindow,告诉WMS ViewRootImpl已经完成了绘制工作。这个方法会设置DisplayContent的mLayoutNeeded为true。这样就能告诉WMS,当下一轮WMS的relayoutWindow对窗体进行重新测量的时候,允许遍历DisplayContent所有的内容窗体(详细的可以看看我写的WMS在Activity启动中的职责 计算窗体的大小)。

本文已经涉及到不少关于硬件渲染的逻辑,下一篇就来聊聊硬件绘制的原理。


有没有必要换新机?小米4 & 小米3对比评测
哪些动物会远游