Android 重學系列 View的繪制流程(五) onDraw

前言

之前已經(jīng)和大家聊了onLayout的流程梦碗,本文將會繼續(xù)聊一聊onDraw中做了什么?本文將集中關(guān)注軟件渲染蓖救,關(guān)于Canvas的api源碼解析暫時不會在本文聊洪规,會專門開一個Skia源碼解析進行分析。

如果遇到問題可以來http://www.reibang.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啟動中的職責 添加窗體(三))锭碳。

則會調(diào)用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.判斷到如果是第一次調(diào)用draw方法擒抛,則會調(diào)用reportNextDraw推汽。
    private void reportNextDraw() {
        if (mReportNextDraw == false) {
            drawPending();
        }
        mReportNextDraw = true;
    }

    void drawPending() {
        mDrawsNeededToReport++;
    }

能看到實際上就是設置mReportNextDraw為true。我們回顧一下前兩個流程mReportNextDraw參與了標志位的判斷歧沪。在執(zhí)行onMeasure和onLayout有兩個大前提民泵,一個是mStop為false,一個是mReportNextDraw為true槽畔。只要滿足其一就會執(zhí)行。

這么做的目的只有一個胁编,保證調(diào)用一次onDraw方法厢钧。為什么會這樣呢?performDraw是整個Draw流程的入口嬉橙。然而在這個入口早直,必須要保證cancelDraw為false以及newSurface為false。

注意市框,如果是第一次渲染因為會添加進新的Surface霞扬,此時newSurface為true(可以看View的繪制流程(二) 繪制的準備)。所以會走到下面的分之枫振,如果串口可見則調(diào)用scheduleTraversals執(zhí)行下一次Loop的繪制流程喻圃。否則判斷是否有需要執(zhí)行的LayoutTransitions layout動畫就執(zhí)行了。

因此第一次是不會走到onDraw粪滤,是從第二次Looper之后View的繪制流程才會執(zhí)行onDraw斧拍。

我們繼續(xù)關(guān)注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.調(diào)用draw方法杖小,遍歷View的層級肆汹。
  • 2.如果Surface是生效的,則在SurfaceHolder.Callback的surfaceRedrawNeededAsync回調(diào)中調(diào)用pendingDrawFinished予权。
  • 3.如果是強制同步渲染昂勉,則會直接調(diào)用pendingDrawFinished。

對于硬件渲染:

  • 1.調(diào)用draw方法扫腺,遍歷View的層級岗照。
  • 2.通過監(jiān)聽mThreadedRenderer的setFrameCompleteCallback回調(diào)執(zhí)行pendingDrawFinished方法。

我們先關(guān)注軟件渲染的流程斧账。也就是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無效則直接返回

    1. sFirstDrawHandlers這個存儲著runnable靜態(tài)對象。實際上是在ActivityThread啟動后調(diào)用attach方法通過addFirstDrawHandler添加進來的目的只是為了啟動jit模式咧织。
  • 3.scrollToRectOrFocus 處理滑動區(qū)域或者焦點區(qū)域嗓袱。如果發(fā)生了滑動則回調(diào)TreeObserver.dispatchOnScrollChanged。接下來則通過全局的mScroller通過computeScrollOffset判斷是否需要滑動動畫习绢。如果需要執(zhí)行動畫渠抹,則調(diào)用DeocView的onRootViewScrollYChanged蝙昙,進行Y軸上的動畫執(zhí)行。

  • 4.通過ViewTreeObserver的dispatchOnDraw開始分發(fā)draw開始繪制的監(jiān)聽者梧却。

  • 5.判斷是否存在surface面上偏移量奇颠,有就矯正一次臟區(qū),把偏移量添加上去放航。

接下來則會進入到硬件渲染和軟件渲染的分支烈拒。但是進一步進行調(diào)用draw的流程有幾個前提條件:臟區(qū)不為空,需要執(zhí)行動畫广鳍,輔助服務發(fā)生了焦點變化

  • 6.如果ThreadedRenderer不為空且可用荆几。ThreadedRenderer通過onPreDraw回調(diào)到ViewRootImpl,更新mHardwareYOffset赊时,mHardwareXOffset吨铸。如果這兩個參數(shù)發(fā)生了變化,則說明整個發(fā)生了硬件繪制的區(qū)域變化祖秒,需要從頭遍歷一次所有的區(qū)域設置為無效區(qū)域诞吱,mThreadedRenderer.invalidateRoot。

最后調(diào)用ThreadedRenderer.draw方法執(zhí)行硬件渲染繪制竭缝。并且設置通過registerRtFrameCallback設置進來的callback設置到ThreadedRenderer中房维。

  • 7.如果此時ThreadedRenderer不可用但是不為空,說明此時需要對ThreadedRenderer進行初始化抬纸,調(diào)用scheduleTraversals在下一輪的繪制流程中才進行硬件渲染握巢。
  • 8.如果以上情況都不滿足,說明是軟件渲染松却,則調(diào)用drawSoftware進行軟件渲染暴浦。

  • 9.如果不許要draw方法遍歷全局的View樹,則判斷是否需要執(zhí)行滑動動畫晓锻,需要則調(diào)用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<View>(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;
    }

能看到在這個過程中實際上就是處理兩個區(qū)域mVisibleInsets可見區(qū)域以及mContentInsets內(nèi)容區(qū)域。

實際上這個過程就是從根部節(jié)點開始尋找焦點躁锁,然后整個畫面定格在焦點處纷铣。因為mVisibleInsets一般是屏幕中出去過掃描區(qū)的大小,但是內(nèi)容區(qū)域就不一定了战转,可能內(nèi)容會超出屏幕大小搜立,因此會通過mScroller滑動定位。

計算原理如下槐秧,分為2個情況:

  • 1.可視區(qū)域的頂部比起獲得了焦點的view的頂部要低啄踊,說明這個view在屏幕外了忧设,需要向下滑動:

scrollY = mTempRect.top - vi.top;

  • 2.如果焦點view的底部比起可視區(qū)域要比可視區(qū)域的低,說明需要向上滑動,注意滑動之后需要展示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.同樣還是根據(jù)全局的surface的偏移量對整個dirty區(qū)域進行偏移
  • 2.通過Surface.lockCanvas方法映射一個Canvas對象址晕,之后所有的繪制行為都在這個Canvas對象上。關(guān)于這個方法顿锰,詳細的原理可以看看我寫的SurfaceView和TextureView 源碼淺析(上)谨垃。
  • 3.獲得Canvas后,由于上面是對整個surface的偏移硼控,因此Canvas作為surface映射出來的繪制對象也需要進行一次偏移乘客。
  • 4.調(diào)用DecorView的draw方法,開始對整個View樹遍歷淀歇。
  • 5.遍歷完整個View樹后,說明所有的信息已經(jīng)會知道Canvas了匈织,就可以通過surface.unlockCanvasAndPost浪默,把記錄在SkCanvas中的像素數(shù)據(jù)發(fā)送到SF中渲染到屏幕中。關(guān)于第五點詳細的可以閱讀SurfaceView和TextureView 源碼淺析(上)缀匕。后續(xù)的步驟可以閱讀我寫的SF系列文章纳决。

我們繼續(xù)來要來看看draw方法做了什么。

DecorView draw
    @Override
    public void draw(Canvas canvas) {
        super.draw(canvas);

        if (mMenuBackground != null) {
            mMenuBackground.draw(canvas);
        }
    }

能看到整個DecorView實際上就是調(diào)用了父類的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已經(jīng)調(diào)用過了draw方法了夭织。

  • 2.如果dirtyOpaque為false說明不是透明則調(diào)用drawBackground,進行背景的繪制吠撮。

  • 3.校驗是否有橫豎方向的邊緣陰影需要繪制尊惰,如果不需要則以此執(zhí)行如下三個流程:

    • 1.執(zhí)行該View重寫的onDraw流程,進行繪制
    • 2.dispatchDraw 把繪制行為分發(fā)到子View中
    • 3.判斷是否有overlay泥兰,有則繪制每一個View的浮層的dispatchDraw
    • 4.onDrawForeground 繪制View的前景drawable
    • 5.drawDefaultFocusHighlight 繪制默認的焦點高亮弄屡。
  • 4.如果需要繪制上下左右四個方向的滑輪,則執(zhí)行如下幾個步驟:

    • 1.計算滑輪的上下左右四個方向鞋诗,根據(jù)是橫向還是豎向計算其長度
    • 2.把這一塊內(nèi)容作為Canvas的非裁剪區(qū)域繪制到外面區(qū)域
    • 3.執(zhí)行3.1以及3.2的步驟
    • 4.根據(jù)繪制的上下左右四個方向膀捷,對滑輪進行旋轉(zhuǎn)
    • 5.執(zhí)行3.3-3.5的步驟

我們關(guān)注核心行為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,接著調(diào)用canvas的drawRenderNode削彬。從之前我分析的TextureView一文中可以了解到硬件渲染担孔,Canvas實質(zhì)上就是DisplayListCanvas江锨。

  • 軟件渲染則是調(diào)用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,調(diào)用start之后獲取Drawable對象對應DisplayListCanvas拌消,在調(diào)用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<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;
                }
            }

            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<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 (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);
    }
    1. LayoutAnimationController 控制Layout動畫的控制者調(diào)用start啟動動畫,并且回調(diào)監(jiān)聽遍蟋。
  • 3.判斷是否被padding裁剪內(nèi)容區(qū)域吹害,默認是開啟的。這個情況下虚青,則會滑動的區(qū)域它呀,padding區(qū)域,以及viewgroup的區(qū)域棒厘,進行裁剪纵穿,而不是統(tǒng)統(tǒng)都畫到Canvas中。

            canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop,
                    mScrollX + mRight - mLeft - mPaddingRight,
                    mScrollY + mBottom - mTop - mPaddingBottom);
  • 4.判斷是否打開了硬件加速奢人。如果沒有政恍,buildOrderedChildList對當前該ViewGroup下所有子View進行z軸上的插入排序,從而得知誰將繪制在更加更加上方达传。這個過程中z軸上的數(shù)值越小篙耗,越先調(diào)用drawChild方法繪制到canvas中,也就是層級越低宪赶,被其他子View覆蓋在其上宗弯。在處理每一個孩子對應的drawChild方法之前,會先處理通過addTransientView添加進來的臨時View搂妻。這種方式十分少見蒙保,你可以看成臨時動畫一樣的效果,不參加view的onmeasure欲主,onLayout邓厕,但是會繪制出來逝嚎。需要手動的remove掉。

  • 5.繪制通過addDisappearingView添加的消失臨時View详恼。

  • 6.如果FLAG_NOTIFY_ANIMATION_LISTENER补君,F(xiàn)LAG_ANIMATION_DONE標志位都關(guān)閉了,同時Layout動畫也完成了昧互,所有的drawChild都返回了false挽铁,則在下一個Looper中開始時機調(diào)用notifyAnimationListener,通知監(jiān)聽者本次動畫已經(jīng)完成敞掘。

整個核心都是drawChild方法叽掘,我們來看看drawChild做了什么?

drawChild

    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
        return child.draw(canvas, this, drawingTime);
    }

核心調(diào)用了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點決定面殖,一個是關(guān)閉硬件渲染,另一個是緩存的bitmap不為空哭廉。

做到的事情如下:

  • 1.首先判斷View中是否通過setAnimation設置了一個變換矩陣Transformation動畫到View中脊僚。如果有,則調(diào)用applyLegacyAnimation方法確定整個變化動畫刷新的范圍在這個View范圍內(nèi)遵绰;transformToApply設置為父容器的Transformation泵三。如果沒有設置Animation珊蟀,則判斷是否關(guān)閉了硬件渲染且打開了FLAG_SUPPORT_STATIC_TRANSFORMATIONS。也就是說當前的View的父容器是否存在一個靜態(tài)的變換矩陣,存在則更新到transformToApply中查乒。

  • 2.如果mLayerType為LAYER_TYPE_SOFTWARE或者關(guān)閉了硬件渲染,說明是一個軟件渲染绿渣,則調(diào)用buildDrawingCache構(gòu)建一個繪制的緩存速种,通過getDrawingCache獲取這個緩存bitmap到cache中。

  • 3.如果drawingWithRenderNode為true哭当,說明在使用硬件渲染猪腕。則調(diào)用updateDisplayListIfDirty更新DisplayList中的臟區(qū)。

  • 4.drawingWithRenderNode為false钦勘,則調(diào)用computeScroll計算滑動區(qū)域陋葡,賦值給sx和sy。

  • 5.如果存在一個變換矩陣或者關(guān)閉硬件渲染彻采,則調(diào)用canvas的save方法保存當前的狀態(tài)腐缤。如果offsetForScroll為true(說明此時是軟件渲染同時沒有緩存)捌归,則調(diào)用canvas的translate方法參數(shù)第四步驟中計算出來平移距離。

橫向平移:mLeft - sx
縱向平移: mTop - sy

這么做可以把繪制的原點移動到平移 平移后的位置岭粤,之后所有的繪制都是基于這個點進行的

  • 6.offsetForScroll為false惜索,說明此時可能需要繪制緩存或者是硬件渲染,只做了兩件事情:平移當前的畫布的繪制原點绍在,如果需要則對整個畫布進行伸縮门扇。

  • 7.如果transformToApply不為空,前提下偿渡。發(fā)現(xiàn)打開了硬件渲染臼寄,則調(diào)用RenderNode.setAnimationMatrix方法設置動畫的矩陣。如果關(guān)閉溜宽,則先回退經(jīng)過平移的Canvas原點吉拳,先對動畫矩陣進行合并后在進行滑動的移動。

  • 8.如果alpha小于1适揉,且判斷到drawingWithDrawingCache關(guān)閉的留攒。則說明可以在當前的繪制結(jié)果中進行透明度處理。判斷onSetAlpha為false嫉嘀,如果是硬件渲染則調(diào)用renderNode.setAlpha炼邀,如果是軟件渲染則調(diào)用canvas.saveLayerAlpha。如果繪制緩存是打開剪侮。onSetAlpha如果為true說明整個透明是由子View決定的拭宁,因此先打開PFLAG_ALPHA_SET標志位,等待后續(xù)的處理瓣俯。

  • 9.如果父容器打開了FLAG_CLIP_CHILDREN標志位且當前的View沒有緩存杰标。說明當前的View繪制的結(jié)果需要被父容器裁剪了:
    如果進行了滑動,則裁剪區(qū)域如下:

canvas.clipRect(sx, sy, sx + getWidth(), sy + getHeight());
如果沒有緩存彩匕,則直接裁剪當前的View
canvas.clipRect(0, 0, getWidth(), getHeight());
有緩存:
canvas.clipRect(0, 0, cache.getWidth(), cache.getHeight());
如果需要根據(jù)邊緣進行裁剪:
canvas.clipRect(mClipBounds);

  • 10.drawingWithDrawingCache如果是關(guān)閉腔剂,且drawingWithRenderNode是打開的,則調(diào)用DisplayListCanvas.drawRenderNode(renderNode) 方法驼仪。參數(shù)中的renderNode是updateDisplayListIfDirty方法生成一個新的DisplayListCanvas掸犬。通過drawRenderNode把結(jié)果繪制到父容器的DisplayListCanvas。

  • 11.drawingWithDrawingCache關(guān)閉绪爸,drawingWithRenderNode也是關(guān)閉的登渣。說明此時是直接進行繪制。如果打開了PFLAG_SKIP_DRAW標志位說明需要直接掉過當前的View毡泻,直接調(diào)用dispatchDraw分發(fā)View的繪制命令胜茧。如果沒有打開,則調(diào)用draw方法。就會繼續(xù)調(diào)用onDraw后并且dispatchDraw分發(fā)View的繪制方法呻顽。

  • 12.drawingWithDrawingCache是打開的雹顺,同時cache緩存不為空,則把cache中的結(jié)果繪制到Canvas中廊遍。

  • 13.當所有子View都繪制結(jié)束之后嬉愧,則調(diào)用canvas.restoreToCount方法一層層的恢復繪制狀態(tài)。主要還是恢復繪制原點喉前。調(diào)用父容器的finishAnimatingView清空所有的Animation以及disappearingAnimation没酣,回調(diào)onAnimationEnd。

在這幾點中卵迂,除去Canvas操作(關(guān)于Canvas操作裕便,我會專門開一個Skia源碼解析專題進行分析)有幾個比較重要的方法:

  • 1.buildDrawingCache 構(gòu)建一個繪制緩存對象
  • 2.updateDisplayListIfDirty 硬件渲染更新臟區(qū)
buildDrawingCache
    public void buildDrawingCache(boolean autoScale) {
        if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0 || (autoScale ?
                mDrawingCache == null : mUnscaledDrawingCache == null)) {
            try {
                buildDrawingCacheImpl(autoScale);
            } finally {
            }
        }
    }

能看到實際上就是調(diào)用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.在創(chuàng)建繪制的緩存bitmap之前,如果當前View的寬高其中之一小于等于0见咒,或者當前View需要內(nèi)存大于最大允許的緩存View大小偿衰。
    這個過程中,View繪制后的緩存計算方法如下:

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

能看到這個過程中判斷是否需要透明且關(guān)閉32的緩存改览,一個像素就會2位下翎,否則則是4位。

如果計算出來的結(jié)果比MAXIMUM_DRAWING_CACHE_SIZE大則銷毀繪制緩存宝当。

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

能看到每一個View最大只能是由寬480视事,高800且是ARGB8888模式內(nèi)存大小。

  • 2.如果View的大小發(fā)生了變化庆揩,則調(diào)用Bitmap.createBitmap的方法創(chuàng)建一個對應View大小的Bitmap俐东。

  • 3.如果attachInfo不為空,則判斷是否存在一個全局的Canvas盾鳞,如果不存在就創(chuàng)建一個新的Canvas犬性,并把bitmap設置到Canvas中瞻离。

  • 4.如果需要進行伸縮腾仅,則伸縮緩存bitmap。如果需要滑動套利,則移動整個Canvas的繪制原點推励。

  • 5.如果PFLAG_SKIP_DRAW 打開了,則直接調(diào)用dispatchDraw肉迫,繼續(xù)分發(fā)繪制流程验辞。關(guān)閉了則調(diào)用draw方法,先調(diào)用onDraw后調(diào)用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關(guān)閉陵珍,或者renderNode是無效的,或者mRecreateDisplayList是false违施。則進入到RenderNode的Canvas繪制中互纯。

  • 2.如果renderNode是有效的,且不需要進行重新構(gòu)建整個硬件渲染的DisplayList(mRecreateDisplayList為false)磕蒲,說明不是第一次繪制了已經(jīng)有繪制結(jié)果了留潦,則調(diào)用dispatchGetDisplayList。

  • 3.接下來的情景說明是第一次繪制辣往,renderNode還是屬于無效狀態(tài)兔院。其初始化流程如下:

    • 1.renderNode.start(width, height) 調(diào)用start方法創(chuàng)建一個全新的DisplayListCanvas
    • 2.如果LayerType是LAYER_TYPE_SOFTWARE,就算是Layer的模式是軟件渲染模式排吴,如果打開了硬件渲染模式秆乳,還是會把當前View對應繪制緩存bitmap通過setBitmap的方式設置到DisplayListCanvas中。
    • 3.如果不是LAYER_TYPE_SOFTWARE钻哩,調(diào)用computeScroll進行滑動計算后屹堰。把整個RenderNode的繪制原點退回到滑動之前的狀態(tài),并且打上PFLAG_DRAWN和PFLAG_DRAWING_CACHE_VALID兩個標志位街氢。
    • 4.如果打開PFLAG_SKIP_DRAW扯键,則直接調(diào)用dispatchDraw,分發(fā)繪制流程珊肃。
    • 5.沒有打開PFLAG_SKIP_DRAW荣刑,則直接調(diào)用draw方法,先回調(diào)onDraw再調(diào)用dispatchDraw進行分發(fā)伦乔。
  • 4.最后調(diào)用renderNode.end(canvas) 結(jié)束整個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<View> 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;
    }

能看到這個過程實際上就是調(diào)用了子View的updateDisplayListIfDirty方法招刹。

pendingDrawFinished

當一切都處理完畢之后恬试,就會調(diào)用pendingDrawFinished。如果mDrawsNeededToReport計數(shù)為0疯暑,則說明所有需要繪制的命令全部完成了训柴。最后調(diào)用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!
        }
    }

能看到最后會調(diào)用reportDrawFinished妇拯,通知WindowSession已經(jīng)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);
        }
    }

會調(diào)用WindowState的setDisplayLayoutNeeded。設置DisplayContent的mLayoutNeeded為true。

調(diào)用WindowSurfacePlacer的requestTraversal仗嗦。而這個方法會在AnimationHandler 窗體動畫的handler中調(diào)用performSurfacePlacement预麸。而這里的邏輯可以閱讀WMS在Activity啟動中的職責 計算窗體的大小

總結(jié)

到這列就完成了onDraw的解析。從onMeasure儒将,onLayout吏祸,onDraw四個流程已經(jīng)過了一遍。但是還沒有仔細聊聊硬件渲染钩蚊,但是沒關(guān)系贡翘,從軟件渲染也能一窺整個核心流程了。

老規(guī)矩砰逻,先來一副時序圖:


View的繪制流程.jpg

整個onDraw的入口依次執(zhí)行了如下的方法:

  • 1.執(zhí)行該View的background 背景的繪制
  • 2.執(zhí)行該View重寫的onDraw流程鸣驱,進行繪制
  • 3.dispatchDraw 把繪制行為分發(fā)到子View中
  • 4.判斷是否有overlay,有則繪制每一個View的浮層的dispatchDraw
  • 5.onDrawForeground 繪制View的前景drawable
  • 6.drawDefaultFocusHighlight 繪制默認的焦點高亮蝠咆。

每一次進行一次onDraw之前對dirtyOpaque標志位進行判斷踊东,實際上就是判斷是否是透明的,是透明的就不會調(diào)用該View的onDraw方法刚操。

其中dispatchDraw進行繪制行為分發(fā)后闸翅,就會調(diào)用drawChild的方法會每一個子View的draw方法。同時會通過buildOrderedChildList插入排序計算z軸上的繪制的順序菊霜,保證z軸上會根據(jù)大小順序調(diào)用drawChild進行覆蓋繪制坚冀。

這個過程中,軟件渲染過程中會伴隨著繪制一個Bitmap的緩存鉴逞。每一個View都能夠申請到的緩存最大數(shù)值就是:

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

如果超出這個數(shù)值就不會出現(xiàn)緩存记某,或者直到OOM了也會銷毀緩存。

當執(zhí)行完畢之后构捡,必定會調(diào)用WMS的finishDrawingWindow液南,告訴WMS ViewRootImpl已經(jīng)完成了繪制工作。這個方法會設置DisplayContent的mLayoutNeeded為true勾徽。這樣就能告訴WMS滑凉,當下一輪WMS的relayoutWindow對窗體進行重新測量的時候,允許遍歷DisplayContent所有的內(nèi)容窗體(詳細的可以看看我寫的WMS在Activity啟動中的職責 計算窗體的大小)捂蕴。

本文已經(jīng)涉及到不少關(guān)于硬件渲染的邏輯譬涡,下一篇就來聊聊硬件繪制的原理闪幽。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末啥辨,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子盯腌,更是在濱河造成了極大的恐慌溉知,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異级乍,居然都是意外死亡舌劳,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進店門玫荣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來甚淡,“玉大人,你說我怎么就攤上這事捅厂」嶝裕” “怎么了?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵焙贷,是天一觀的道長撵割。 經(jīng)常有香客問我,道長辙芍,這世上最難降的妖魔是什么啡彬? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮故硅,結(jié)果婚禮上庶灿,老公的妹妹穿的比我還像新娘。我一直安慰自己吃衅,他們只是感情好跳仿,可當我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著捐晶,像睡著了一般菲语。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上惑灵,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天山上,我揣著相機與錄音,去河邊找鬼英支。 笑死佩憾,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的干花。 我是一名探鬼主播妄帘,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼池凄!你這毒婦竟也來了抡驼?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤肿仑,失蹤者是張志新(化名)和其女友劉穎致盟,沒想到半個月后碎税,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡馏锡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年雷蹂,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片杯道。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡匪煌,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出党巾,到底是詐尸還是另有隱情虐杯,我是刑警寧澤,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布昧港,位于F島的核電站擎椰,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏创肥。R本人自食惡果不足惜达舒,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望叹侄。 院中可真熱鬧巩搏,春花似錦、人聲如沸趾代。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽撒强。三九已至禽捆,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間飘哨,已是汗流浹背胚想。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留芽隆,地道東北人浊服。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像胚吁,于是被迫代替她去往敵國和親牙躺。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,611評論 2 353