Android View 的繪製流程分析及其源碼調用追蹤

1、概述

接着前面的一篇 Android Application 啓動流程分析及其源碼調用探究,從 Activity 啓動,到正式開始繪製流程,整個過程以下圖所示:java

View 的繪製流程

  • 上圖中我列出了重要的幾個方法步驟,能夠看到,裏面包含了 PhoneWindow 的建立時機和 DecorView 的初始化時機。由於 Android 視圖層次結構中,PhoneWindow 和 DecorView 扮演着很重要的角色,請看下圖,瞭解這二者的建立初始化時機,對整個 View 的繪製流程會有更好的理解。
  • PhoneWindow 是 Android 系統中最基本的窗口系統,每一個 Activity 會建立一個,是視圖真正的控制者。DecorView 本質上是一個 FrameLayout,是 Activity 中全部 View 的祖先,即當前 Activity 視圖樹根節點。

Android 視圖層次結構

2、View 繪製起源源碼追蹤

從上一篇 Android Application 啓動流程分析及其源碼調用探究 的最後一步 STEP 14 中,咱們看到主線程收到 H.LAUNCH_ACTIVITY 消息,交由 ActivityThread#handleLaunchActivity() 方法處理。該方法接着調用了 performLaunchActivity()handleResumeActivity() 這兩個方法,下面分別講一下。canvas

ActivityThread.javabash

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
        ···
        Activity a = performLaunchActivity(r, customIntent);

        if (a != null) {
            ···
            handleResumeActivity(r.token, false, r.isForward,
                    !r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);
            ···
            }
        } else {
            ···
        }
    }

複製代碼

2.1 performLaunchActivity()

performLaunchActivity() 方法裏面執行了幾個操做,建立 Activity 對象,調用 Activity#attach(), 建立 PhoneWindow 對象,調用 Activity#onCreate(),初始化 DecorView ,添加布局到 DecorView 的 content ,調用 Activity#onStart()markdown

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ···
        Activity activity = null;
        try {
            ···
            //建立Activity對象
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            ···
        } catch (Exception e) {
            ···
        }

        try {
            ···
            if (activity != null) {
                ···
                //調用 Activity#attach(),建立 PhoneWindow 對象
                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window, r.configCallback);

                ···
                //調用Activity#onCreate(),初始化DecorView,添加布局到DecorView的content
                if (r.isPersistable()) {
                    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
                } else {
                    mInstrumentation.callActivityOnCreate(activity, r.state);
                }
                ···
                if (!r.activity.mFinished) {
                    //調用 Activity#onStart()
                    activity.performStart();
                    ···
                }
                ···
            }
            ···
        } catch (SuperNotCalledException e) {
           ···
        } catch (Exception e) {
            ···
        }
        return activity;
    }
複製代碼

PhoneWindow 對象的建立時機在 activity 執行 attach() 方法裏面,下面看看源碼:app

Activity.java框架

final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback) {
        ···
        //建立 PhoneWindow 對象
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        ···
        //爲 PhoneWindow 設置 WindowManager 
        mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        ···
        //將 PhoneWindow 關聯到 Activity
        mWindowManager = mWindow.getWindowManager();
        ···
    }
複製代碼

接下來,ActivityThread 調用 Activity#onCreate() 方法,咱們知道 Activity 執行 onCreate() 方法會調用 setContentView(),而 Activity 的 setContentView() 實際的實現來自 PhoneWindow,能夠看下 getWindow() 方法返回的其實就是上面 attach() 方法中賦值的 mWindow 對象。ide

Activity.java函數

public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }
複製代碼

咱們繼續看看 PhoneWindow 裏面:oop

PhoneWindow.java佈局

@Override
    public void setContentView(int layoutResID) {
        
        if (mContentParent == null) {
            //初始化 DecorView
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            ···
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            ···
        } else {
            //添加布局到 DecorView 的 content 
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        ···
    }

    private void installDecor() {
        ···
        if (mDecor == null) {
            //建立 DecorView 對象
            mDecor = generateDecor(-1);
            ···
        } else {
            ···
        }
        if (mContentParent == null) {
            //爲 DecorView 的 ContentView 設置佈局
            mContentParent = generateLayout(mDecor);
            ···
        }
    }
複製代碼

上面的 mContentParent 就是 Activity 中 setContentView 中設置的 layout.xml 佈局文件中的最外層父佈局, DecroView 裏面 ContentView 對應的佈局部分。

2.2 handleResumeActivity()

handleResumeActivity() 方法裏面執行了2個操做,調用 Activity#onResume(),將 DecorView 添加到 WindowManager。

ActivityThread.java

final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
        ···
        //調用 Activity#onResume()
        r = performResumeActivity(token, clearHide, reason);

        if (r != null) {
            ···
            if (r.window == null && !a.mFinished && willBeVisible) {
                ···
                if (a.mVisibleFromClient) {
                    if (!a.mWindowAdded) {
                        a.mWindowAdded = true;
                        //將 DecorView 添加到 WindowManager,
                        //WindowManager 的實現類是 WindowManagerImpl,
                        //因此實際調用的是 WindowManagerImpl 的 addView 方法
                        wm.addView(decor, l);
                    } else {
                        ···
                    }
                }
            } else if (!willBeVisible) {
                ···
            }
            ···
        } else {
            ···
        }
    }
複製代碼

WindowManagerImpl.java

@Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }
複製代碼
  • 咱們先來了解下,ViewParent 和 DecorView 的概念。ViewParent 對應於 ViewRootImpl 類,它是鏈接 WindowManager 和 DecorView 的紐帶,View 繪製的三大流程均是經過 ViewParent 來完成的。在 ActivityThread 中,當 Activity 對象被建立完畢後,會將 DecorView 添加到 Window 中,同時會建立 ViewRootImpl 對象,並將 ViewRootImpl 對象和 DecorView 創建關聯,將 DecorView 實例對象交給 ViewRootImpl 用以繪製 View 。最後調用 ViewRootImpl 類中的 performTraversals(),從而實現視圖的繪製。咱們看看 WindowManagerGlobal 的 addView() 方法。

WindowManagerGlobal.java

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        ···
        synchronized (mLock) {
            ···
            //建立 ViewRootImpl 對象
            root = new ViewRootImpl(view.getContext(), display);
            ···
            try {
                //把 DecorView 加載到 Window 中
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                ···
            }
        }
    }
複製代碼

接着,咱們進入到 ViewRootImpl 的源碼中,繼續追蹤:

ViewRootImpl.java

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                mView = view;
                ···
                // Schedule the first layout -before- adding to the window
                // manager, to make sure we do the relayout before receiving
                // any other events from the system.
                requestLayout();
                ···
                //將 ViewRootImpl 對象和 DecorView 創建關聯
                view.assignParent(this);
                ···
            }
        }
    }

    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            ···
            scheduleTraversals();
        }
    }

    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            ···
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            ···
        }
    }

    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
    final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

    void doTraversal() {
        if (mTraversalScheduled) {
            ···
            //
            performTraversals();
            ···
        }
    }
複製代碼

整個 View 樹的繪圖流程是在 ViewRootImpl 類的 performTraversals() 方法開始的,它把控着整個繪製的流程。該函數作的執行過程主要是根據以前設置的狀態,判斷是否從新計算視圖大小、是否從新放置視圖的位置、以及是否重繪,從上到下遍歷整個視圖樹,每一個 View 控件負責繪製本身,而 ViewGroup 還須要負責通知本身的子 View 進行繪製操做。

下面,咱們以 performTraversals() 爲起點,來分析 View 的整個繪製流程。

3、View 的繪製流程

ViewRootImpl.java

private void performTraversals() {
        final View host = mView;
        ···
        int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
        int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
         // Ask host how big it wants to be
        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
        ···
        performLayout(lp, mWidth, mHeight);
        ···
        performDraw();
        ···
    }
複製代碼
  • performMeasure():此階段的目的是計算出 View 樹中的各個控件要顯示其內容的話,須要多大尺寸。
  • performLayout():此階段的基本思想也是由根 View 開始,遞歸地完成整個 View 樹的佈局工做。
  • performDraw():此階段也是從根節點向下遍歷 View 樹,完成全部 ViewGroup 和 View 的繪製工做,根據佈局過程計算出的顯示區域,將全部 View 的當前需顯示的內容畫到屏幕上。

咱們都知道,每個視圖的繪製過程都必須經歷三個最主要的階段,即onMeasure()onLayout()onDraw(),特別是咱們進行自定義 View 的時候,能夠明顯地看出來,這三個方法分別對應到上面 ViewRootImpl 類中源碼的三個 performXXX() 方法。下面咱們逐一來看看。

3.1 performMeasure

ViewRootImpl.java

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        ···
        try {
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        } finally {
            ···
        }
    }
複製代碼

上面的 mView 正是以前分析過的 DecorView ,另外,咱們能夠看獲得 measure() 這個方法是 final 的,所以咱們沒法在子類中去重寫這個方法,說明 Android 是不容許咱們改變 View 的 measure 框架。

咱們來看 View 的 measure() 方法:

View.java

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        ···
        if (forceLayout || needsLayout) {
            ···
            int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
            if (cacheIndex < 0 || sIgnoreMeasureCache) {
                // measure ourselves, this should set the measured dimension flag back
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                ···
            } else {
                ···
            }
            ···
        }
        ···
    }
複製代碼

measure 階段是最複雜的,在這裏咱們刪繁就簡,省去 MeasureSpec 相關的分析。 前面說了,measure() 這個方法是 final 的,因此 View 子類只能經過重載 onMeasure() 來實現本身的測量邏輯。並且這裏還會先判斷是否知足從新繪製的條件纔會進行實際的測量工做,即 forceLayout (表示強制從新佈局,能夠經過 View.requestLayout() 來實現)或者 needsLayout (表示本次傳入的 MeasureSpec 與上次傳入的不一樣)爲 true。

View.java

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }
複製代碼

這裏簡單總結一下:

  • 測量該 View 以及它的內容來決定測量的寬度和高度。該方法被 measure(int,int) 調用,而且應該被子類重寫來提供準確並且有效的對它們的內容的測量。
  • 當重寫該方法時,您必須調用 setMeasuredDimension(int,int) 來存儲該 view 測量出的寬和高。若是不這樣作將會觸發 IllegalStateException,由 measure(int,int) 拋出。
  • 測量的基類實現默認爲背景的尺寸,除非 MeasureSpec 容許使用更大的尺寸。子類應該重寫 onMeasure(int,int) 方法來提供對內容更好的測量。
  • 若是該方法被重寫,子類負責確保測量的高和寬至少是該 View 的 最小高度和最小寬度值。
  • 對於非 ViewGroup 的 View 而言,經過調用上面默認的 onMeasure() 便可完成 View 的測量(固然你也能夠重載 onMeasure() 並調用 setMeasuredDimension() 來設置任意大小的佈局,這裏就能夠根據實際需求來決定,也就是說,若是你不想使用系統默認的測量方式,能夠按照本身的意願進行定製)。
  • 當經過 setMeasuredDimension() 方法最終設置完成 View 的 measure 以後 View 的 mMeasuredWidth 和 mMeasuredHeight 成員變量纔會有具體的數值,在 setMeasuredDimension() 方法調用以後,咱們才能使用 getMeasuredWidth()getMeasuredHeight() 來獲取視圖測量出的寬高,以此以前調用這兩個方法獲得的值都會是 0
  • 一個佈局中通常都會包含多個子視圖,每一個視圖都須要經歷一次 measure 過程。ViewGroup 中定義了一個 measureChildren()measureChild()measureChildWithMargins() 方法來去測量子視圖的大小,三個方法最終都是調用子視圖的 measure() 方法。measureChildren() 內部實質上是循環調用measureChild() ,而 measureChild()measureChildWithMargins() 的區別是在因而否把 margin 和 padding 也做爲子視圖的大小。(見下面源碼)

ViewGroup.java

protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
        final int size = mChildrenCount;
        final View[] children = mChildren;
        for (int i = 0; i < size; ++i) {
            final View child = children[i];
            if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
                measureChild(child, widthMeasureSpec, heightMeasureSpec);
            }
        }
    }

    protected void measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {
        final LayoutParams lp = child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

    protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                        + widthUsed, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                        + heightUsed, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }
複製代碼

3.2 performLayout

當 measure 過程完成後,接下來就會進行 layout 階段,即佈局階段。layout 的做用是根據前面測量的尺寸以及設置的其它屬性值,共同來肯定 View 的位置。

ViewRootImpl.java

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
        ···
        final View host = mView;
        ···
        try {
            host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
            ···
            if (numViewsRequestingLayout > 0) {
                ···
                if (validLayoutRequesters != null) {
                    ···
                    host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
                    ···
                }
            }
        } finally {
            ···
        }
        ···
    }

複製代碼

上面的 host 就是 DecorView ,DecorView 繼承了 FrameLayout ,咱們來看看 ViewGroup 的 layout() 方法:

ViewGroup.java

@Override
    public final void layout(int l, int t, int r, int b) {
        if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
            ···
            super.layout(l, t, r, b);
        } else {
            ···
        }
    }
複製代碼

咱們能夠看獲得,跟 measure() 方法相似,ViewGroup 的 layout() 方法是被 final 修飾的,可見 Android 是不容許自定義的 ViewGroup 子類改變 ViewGroup 的 layout 框架的。這裏面直接調用了 View 的 layout() 方法,咱們來看看 View 的 layout() 方法。

View.java

public void layout(int l, int t, int r, int b) {
        ···
        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            onLayout(changed, l, t, r, b);
            ···
        }
        ···
    }
複製代碼
  • layout() 方法會調用 setFrame() 方法,setFrame() 方法是真正執行佈局任務的步驟,至於 setOpticalFrame() 方法,其中也是調用 setFrame() 方法,經過設置 View 的 mLeft、mTop、mRight 和 mBottom 四個參數來執行佈局,對應描述了 View 相對其父 View 的位置。
  • setFrame() 方法中會判斷 View 的位置是否發生了改變,以肯定有沒有必要對當前的視圖進行重繪。
  • 而對子 View 的局部是經過 onLayout() 方法實現的,因爲非 ViewGroup 視圖不含子 View,因此 View 類的 onLayout() 方法爲空,正由於 layout 過程是父佈局容器佈局子 View 的過程,onLayout() 方法對葉子 View 沒有意義,只有 ViewGroup 纔有用。

接下來咱們看看 ViewGroup 的 onLayout() 方法:

ViewGroup.java

@Override
    protected abstract void onLayout(boolean changed,
            int l, int t, int r, int b);
複製代碼

能夠看到,ViewGroup 中的 onLayout() 方法是一個抽象方法,這就意味着全部 ViewGroup 的子類(FrameLayout 、LinearLayout、RelativeLayout)都必須重寫這個方法,而後在內部按照各自的規則對子視圖進行佈局。這裏咱們以 DecorView 來分析一下,DecerView 繼承自 FrameLayout ,咱們直接來看 FrameLayout 的 onLayout() 方法。

FrameLayout.java

@Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        layoutChildren(left, top, right, bottom, false /* no force left gravity */);
    }

    void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
        final int count = getChildCount();
        ···
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                ···
                final int width = child.getMeasuredWidth();
                final int height = child.getMeasuredHeight();
                ···
                child.layout(childLeft, childTop, childLeft + width, childTop + height);
            }
        }
    }
複製代碼
  • 能夠看獲得,這裏面也是對每個子視圖調用 layout() 方法的。若是該子視圖仍然是父佈局,會繼續遞歸下去;若是是葉子 view,則會走到 view 的 onLayout() 空方法,該葉子view佈局流程走完。
  • width 和 height 分別來源於measure階段存儲的測量值,若是這裏經過其它渠道賦給 width 和 height 值,那麼 measure 階段的測量就顯得沒有意義了。
  • onLayout() 過程結束後,咱們就能夠調用 getWidth() 方法和 getHeight() 方法來獲取視圖的寬高值。
  • getWidth() 方法和 getMeasureWidth() 方法的區別:getMeasureWidth() 方法在 measure() 階段結束後就能夠獲取到值,而 getWidth() 方法要在 layout() 階段結束後才能獲取到。另外,getMeasureWidth() 方法中的值是經過 setMeasuredDimension() 方法來進行設置的,而 getWidth() 方法中的值則是經過視圖右邊的座標減去左邊的座標計算出來的。

在自定義 View 裏面,若是在 onLayout() 方法中給子視圖的 layout() 方法傳入的四個參數是 0, 0, childView.getMeasuredWidth(), childView.getMeasuredHeight(),那麼 getWidth() 方法和 getMeasuredWidth() 獲得的值就是相同的;若是傳入的四個參數是別的自定義的值,那麼 getWidth() 方法和 getMeasuredWidth() 獲得的值就不會再相同(這裏不建議這麼操做)

到這裏,layout 階段的大體流程咱們就分析完了,這個階段主要就是根據上一階段獲得的 View 的測量寬高來肯定 View 的最終顯示位置。

3.3 performDraw

ViewRootImpl.java

private void performDraw() {
        ···
        try {
            draw(fullRedrawNeeded);
        } finally {
            ···
        }
        ···
    }

    private void draw(boolean fullRedrawNeeded) {
        ···
        if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
            if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
                ···
            } else {
                ···
                if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
                    return;
                }
            }
        }
        ···
    }

    private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
            boolean scalingRequired, Rect dirty) {
        ···
        ···
        try {
            ···
            try {
                ···
                mView.draw(canvas);
                ···
            } finally {
                ···
            }
        } finally {
            ···
        }
        ···
    }
複製代碼

咱們跟蹤代碼,會發現,從 performDraw() 方法開始,最後會調用 mView.draw(canvas); ,而這個 mView 就是咱們以前分析過的 DecorView:

DecorView.java

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

        if (mMenuBackground != null) {
            mMenuBackground.draw(canvas);
        }
    }
複製代碼

這裏,DecorView 直接調用了父類的 draw() 方法,後面繼續繪製了菜單背景,咱們重點來看父類的方法,跟蹤進去發現,直接就到了 View 類裏面,說明 FrameLayout 和 ViewGroup 都沒有重寫 draw() 方法。

View.java

@CallSuper
    public void draw(Canvas canvas) {
        ···
        /*
         * Draw traversal performs several drawing steps which must be executed
         * in the appropriate order:
         *
         *      1. Draw the background
         *      2. If necessary, save the canvas' layers to prepare for fading * 3. Draw view's content
         *      4. Draw children
         *      5. If necessary, draw the fading edges and restore layers
         *      6. Draw decorations (scrollbars for instance)
         */

        // Step 1, draw the background, if needed
        int saveCount;

        if (!dirtyOpaque) {
            drawBackground(canvas);
        }

        // skip step 2 & 5 if possible (common case)
        final int viewFlags = mViewFlags;
        boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
        boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
        if (!verticalEdges && !horizontalEdges) {
            // Step 3, draw the content
            if (!dirtyOpaque) onDraw(canvas);

            // Step 4, draw the children
            dispatchDraw(canvas);

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

            // Step 7, draw the default focus highlight
            drawDefaultFocusHighlight(canvas);

            if (debugDraw()) {
                debugDrawFocus(canvas);
            }

            // we're done... return; } ··· } 複製代碼

到了 Draw 階段,源碼裏面註釋也比較清晰明瞭,這裏總共執行了 7 步,其中第 2 步和第 5 步 一般來講都是忽略不執行的,這裏一樣化繁就簡,只分析其餘 5 步。

Step 1. 繪製背景; Step 2. 忽略跳過; Step 3. 繪製內容; Step 4. 繪製子視圖; Step 5. 忽略跳過; Step 6. 繪製裝飾(前景,滾動條); Step 7. 繪製默認焦點高光。

這裏咱們重點分析 Step 3 和 Step 4 。 Step 3 這裏調用了咱們熟悉的 onDraw() 方法:

View.java

/**
     * Implement this to do your drawing.
     *
     * @param canvas the canvas on which the background will be drawn
     */
    protected void onDraw(Canvas canvas) {
    }
複製代碼

這個方法裏面什麼都沒作,可是註釋講得很清楚,重寫該方法以完成你想要的繪製。由於每一個 View 的內容部分是各不相同的,因此須要由子類去實現具體邏輯。以 DecorView 爲例,這裏 ViewGroup 和 FrameLayout 都沒有重寫 onDraw() 方法,只有 DecorView 重寫了該方法。DecorView 重寫 onDraw() 在裏面實現本身須要的繪製。

DecorView.java

@Override
    public void onDraw(Canvas c) {
        super.onDraw(c);

        // When we are resizing, we need the fallback background to cover the area where we have our
        // system bar background views as the navigation bar will be hidden during resizing.
        mBackgroundFallback.draw(isResizing() ? this : mContentRoot, mContentRoot, c,
                mWindow.mContentParent);
    }

複製代碼

繪製完本身的需求,接着來到 Step 4 的 dispatchDraw()

View.java

/**
     * Called by draw to draw the child views. This may be overridden
     * by derived classes to gain control just before its children are drawn
     * (but after its own view has been drawn).
     * @param canvas the canvas on which to draw the view
     */
    protected void dispatchDraw(Canvas canvas) {

    }
複製代碼

View 的 dispatchDraw() 方法是一個空方法,可是註釋說明了若是 View 包含子類須要重寫該方法,實際上對於葉子 View 來講,該方法沒有什麼意義,由於它沒有子 View 須要畫了,而對於 ViewGroup 來講,就須要重寫該方法來畫它的子 View。可是咱們能夠發現,咱們熟悉的 RelativeLayout、LinearLayout、DecorView 之類的佈局並無重寫 dispatchDraw() 方法,那咱們就直接來看 ViewGroup 裏面:

ViewGroup.java

@Override
    protected void dispatchDraw(Canvas canvas) {
        ···
        for (int i = 0; i < childrenCount; i++) {
            ···
                if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
                        transientChild.getAnimation() != null) {
                    more |= drawChild(canvas, transientChild, drawingTime);
                }
            ···
            if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
                more |= drawChild(canvas, child, drawingTime);
            }
        }
        while (transientIndex >= 0) {
            // there may be additional transient views after the normal views
            ···
                more |= drawChild(canvas, transientChild, drawingTime);
            ···
        }
        ···
        // Draw any disappearing views that have animations
        if (mDisappearingChildren != null) {
            ···
            for (int i = disappearingCount; i >= 0; i--) {
                ···
                more |= drawChild(canvas, child, drawingTime);
            }
        }
        ···
    }
複製代碼

大致能夠看得出來,這裏面就是遍歷子 View ,調用 drawChild(),以繪製每一個子視圖:

/**
     * Draw one child of this View Group. This method is responsible for getting
     * the canvas in the right state. This includes clipping, translating so
     * that the child's scrolled origin is at 0, 0, and applying any animation * transformations. * * @param canvas The canvas on which to draw the child * @param child Who to draw * @param drawingTime The time at which draw is occurring * @return True if an invalidate() was issued */ protected boolean drawChild(Canvas canvas, View child, long drawingTime) { return child.draw(canvas, this, drawingTime); } 複製代碼

drawChild() 方法裏面直接就只有調用子 View 的 draw() 方法,很是明瞭。一樣的,若是該子 View 還有子視圖,也會繼續遍歷下去調用 drawChild() 方法,繼續繪製子 View,直到葉子 View 爲止,這樣不斷遞歸下去,直到畫完整棵 DecorView 樹。

  • 經過上面的分析,咱們知道,View 是不會幫咱們繪製內容部分的,所以須要每一個視圖根據想要展現的內容來自行繪製。就是咱們自定義 View 過程當中重寫 onDraw() 方法。
  • 若是該 View 是一個 ViewGroup,則須要遞歸繪製其所包含的全部子 View。
  • 繪製的方式主要是藉助 Canvas 這個類,它會做爲參數傳入到 onDraw() 方法中,供給每一個視圖使用。Canvas 這個類的用法很是豐富,基本能夠把它當成一塊畫布,在上面繪製任意的東西。
  • 在獲取畫布剪切區(每一個 View 的 draw() 方法中傳入的 Canvas)時會自動處理掉padding,子 View 獲取 Canvas 不用關注這些邏輯,只用關心如何繪製便可。
  • 默認狀況下子 View 的 ViewGroup.drawChild() 繪製順序和子 View 被添加的順序一致,可是你也能夠重載 ViewGroup.getChildDrawingOrder() 方法提供不一樣順序。
  • 至此,從 Activity 實例建立開始,咱們分析完了整個源碼調用流程,到 View 的實際繪製流程。
  • 以 Activity 的 DecorView 爲例,整個 View 體系就是一棵以 DecorView 爲根的 View 樹,依次經過遍從來完成 measure、layout 和 draw 過程。
  • 在自定義 View 中,每個視圖的繪製過程都必須經歷三個最主要的階段,即經過重寫 onMeasure()onLayout()onDraw() 來完成要自定義的部分。
相關文章
相關標籤/搜索