View的繪製-draw流程詳解

目錄

做用

根據 measure 測量出的寬高,layout 佈局的位置,渲染整個 View 樹,將界面呈現出來。java

具體分析

如下源碼基於版本27android

DecorView 的draw 流程

《View的繪製-measure流程詳解》中說過,View 的繪製流程是從 ViewRootViewImpl 中的 performMeasure()performLayoutperformDraw 開始的。在執行完 performMeasure()performLayout 後,開始執行 performDraw 方法:(如下源碼有所刪減)canvas

//ViewRootViewImpl 類
 private void performDraw() {
    ....
    draw(fullRedrawNeeded);
    ....
 }
-------------------------------------------------------------------------
//ViewRootViewImpl 類
private void draw(boolean fullRedrawNeeded) {
    ....
    mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
    ....
}

-------------------------------------------------------------------------
//ThreadedRenderer 類
void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) {
    ....
    updateRootDisplayList(view, callbacks);
    ....
}
-------------------------------------------------------------------------
//ThreadedRenderer 類
private void updateRootDisplayList(View view, DrawCallbacks callbacks) {
    ....
    updateViewTreeDisplayList(view);
    ....
}
-------------------------------------------------------------------------
//ThreadedRenderer 類
private void updateViewTreeDisplayList(View view) {
    view.mPrivateFlags |= View.PFLAG_DRAWN;
    view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED)
            == View.PFLAG_INVALIDATED;
    view.mPrivateFlags &= ~View.PFLAG_INVALIDATED;
    //這裏調用了 View 的 updateDisplayListIfDirty 方法 
    //這個 View 其實就是 DecorView
    view.updateDisplayListIfDirty();
    view.mRecreateDisplayList = false;
}
複製代碼

接下來查看 View 的 updateDisplayListIfDirty 方法:app

//View 類
 public RenderNode updateDisplayListIfDirty() {
    ....
    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 {
        /*最終調用了 DecorView 的 draw 方法,爲何沒有走上面的 dispatchDraw(canvas) 我也母雞啊,我是Debug 斷點調試曉得走這裏的,哈哈*/
        draw(canvas);
    }
    ....
}
-------------------------------------------------------------------------
//DecorView 重寫了 draw 方法。因此走到了 DecorView 的 draw 方法
@Override
public void draw(Canvas canvas) {
    //調用父類 (View)的 draw 方法
    super.draw(canvas);

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

以上流程,推薦兩篇文章:ViewRootImpl的performDraw過程 ~~~~~~~~~~~~~~~~~淺談ondraw的前世今身ide

View 的 draw 流程

就這樣, View 的繪製就開始啦。主要有四個步驟:函數

  • drawBackground 繪製背景色
  • onDraw 繪製內容
  • dispatchDraw 繪製 children
  • onDrawForeground 繪製裝飾(前景,滾動條)
//View 類
/** *手動將此視圖(及其全部子項)渲染到給定的Canvas。在調用此函數前,視圖必須已經完成了完整佈局(layout)。 *通常咱們在自定義控件繼承 View 的時候,不要重寫 draw 方法,只需重寫 onDraw 方法 */
public void draw(Canvas canvas) {
    ....
    
    // 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
        //繪製 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;
    }
    ....
}
複製代碼

咱們對四個步驟進行分析:佈局

//View 類
//繪製背景
private void drawBackground(Canvas canvas) {
    final Drawable background = mBackground;
    //若是沒有設置背景,就不進行繪製
    if (background == null) {
        return;
    }
    //若是設置了背景嗎,且背景的大小發生了改變,
    //就用 layout 計算出的四個邊界值來肯定背景的邊界
    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) {
        //調用 Drawable 的 draw 方法來進行背景的繪製
        background.draw(canvas);
    } else {
        //平移畫布
        canvas.translate(scrollX, scrollY);
        //調用 Drawable 的 draw 方法來進行背景的繪製
        background.draw(canvas);
        canvas.translate(-scrollX, -scrollY);
    }
}
-------------------------------------------------------------------------
//View 類
//繪製內容
protected void onDraw(Canvas canvas) {
    /*View 中的 onDraw 是一個空實現。也不難理解,當咱們自定義控件繼承 View 的時候,須要重寫 onDraw 方法,經過 Canvas 和 Paint 來進行內容的繪製*/
}
-------------------------------------------------------------------------
//View 類
//繪製 children
protected void dispatchDraw(Canvas canvas) {
    /*View 中的 dispatchDraw 也是一個空實現。由於單獨一個 View 自己是沒有子元素的,不須要繪製 children */
}

-------------------------------------------------------------------------
//View 類
//繪製裝飾
public void onDrawForeground(Canvas canvas) {
    //繪製指示器
    onDrawScrollIndicators(canvas);
    //繪製滾動條
    onDrawScrollBars(canvas);

    final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null;
    if (foreground != null) {
        if (mForegroundInfo.mBoundsChanged) {
            mForegroundInfo.mBoundsChanged = false;
            final Rect selfBounds = mForegroundInfo.mSelfBounds;
            final Rect overlayBounds = mForegroundInfo.mOverlayBounds;

            if (mForegroundInfo.mInsidePadding) {
                selfBounds.set(0, 0, getWidth(), getHeight());
            } else {
                selfBounds.set(getPaddingLeft(), getPaddingTop(),
                        getWidth() - getPaddingRight(), getHeight() - getPaddingBottom());
            }

            final int ld = getLayoutDirection();
            Gravity.apply(mForegroundInfo.mGravity, foreground.getIntrinsicWidth(),
                    foreground.getIntrinsicHeight(), selfBounds, overlayBounds, ld);
            foreground.setBounds(overlayBounds);
        }
        //調用 Drawable 的 draw 方法,繪製前景色
        foreground.draw(canvas);
    }
}
複製代碼

以上就是 View 的繪製流程了。ViewGroup 自己是繼承 View 的,它的基本繪製流程也是經過父類 View 進行的,只不過它重寫了 dispatchDraw 方法,來進行子元素的繪製。下面咱們來進行具體分析:post

ViewGroup 的繪製 dispatchDraw 流程

//ViewGroup 類
@Override
protected void dispatchDraw(Canvas canvas) {
    ....
    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) {
            //調用 drawChild 方法,進行繪製子元素
            more |= drawChild(canvas, child, drawingTime);
        }
    }
    ....
}
-------------------------------------------------------------------------
//ViewGroup 類
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
    //調用 View 的 draw 方法,這裏要注意,調用的是 View 的三個參數的 draw 方法
    return child.draw(canvas, this, drawingTime);
}

複製代碼

在 View 中還有一個 draw(Canvas canvas) 的重載方法,就是 draw(Canvas canvas, ViewGroup parent, long drawingTime):優化

//View 類
/** * ViewGroup.drawChild()調用此方法以使每一個子視圖本身繪製。 * 這是View專門根據圖層類型和硬件加速來渲染行爲的地方。 */
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
    ....
    //是否支持硬件加速
    boolean drawingWithRenderNode = mAttachInfo != null
            && mAttachInfo.mHardwareAccelerated
            && hardwareAcceleratedCanvas;

    if (layerType == LAYER_TYPE_SOFTWARE || !drawingWithRenderNode) {
         if (layerType != LAYER_TYPE_NONE) {
             //未開啓
             //調用 View 的 buildDrawingCache 方法
             buildDrawingCache(true);
        }
        cache = getDrawingCache(true);
    }
    //開啓了硬件加速
    if (drawingWithRenderNode) {
        //調用 View 的 updateDisplayListIfDirty 方法
        renderNode = updateDisplayListIfDirty();
        if (!renderNode.isValid()) {
            // Uncommon, but possible. If a view is removed from the hierarchy during the call
            // to getDisplayList(), the display list will be marked invalid and we should not
            // try to use it again.
            renderNode = null;
            drawingWithRenderNode = false;
        }
    }
    ....
}
複製代碼

分別查看 buildDrawingCacheupdateDisplayListIfDirty 方法:ui

//View 類
public void buildDrawingCache(boolean autoScale) {
    ....
    buildDrawingCacheImpl(autoScale);
    ....
}
-------------------------------------------------------------------------
//View 類
private void buildDrawingCacheImpl(boolean autoScale) {
    ....
    // 若是不須要進行自身繪製,就直接調用 dispatchDraw 繪製子 Children
    //不然就直接調用 View 的 draw 方法
    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);
    }
    ....
}
-------------------------------------------------------------------------

//View 類
public RenderNode updateDisplayListIfDirty() {
    ....
    // 若是不須要進行自身繪製,就直接調用 dispatchDraw 繪製子 Children
    //不然就直接調用 View 的 draw 方法
    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);
    }
    ....
}
複製代碼

如此,從頂層 DecorView 的 draw 方法開始,而後調用 dispatchDraw 方法循環遍歷繪製子元素,若是子元素是繼承了 ViewGroup ,就再次循環調用 dispatchDraw 方法,一層層往下遞歸調用,直到每個子元素都被繪製完成,整個 draw 流程也就結束了。

tips: 在咱們使用真機進行源碼斷點調試的時候,可能會出現源碼不能打斷點的狀況,或者斷點沒有走在該走的地方。這是由於國內手機廠商基本都是定製系統,可能修改了源碼。這個時候可使用模擬器進行斷點調試。注意:模擬器版本號要和項目編譯版本號一致!

setWillNotDraw 解析

在 View 中有一個方法是 setWillNotDraw:

//View 類
/** * If this view doesn't do any drawing on its own, set this flag to * allow further optimizations. By default, this flag is not set on * View, but could be set on some View subclasses such as ViewGroup. * * Typically, if you override {@link #onDraw(android.graphics.Canvas)} * you should clear this flag. * * @param willNotDraw whether or not this View draw on its own */
public void setWillNotDraw(boolean willNotDraw) {
    setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
}
複製代碼

從註釋上看,若是此視圖自己不執行任何繪製,就設置爲 true,系統會進行一些繪製優化。View 自己是默認設置爲 false 的,沒有啓動這個優化標記(這也不難理解,由於通常咱們自定義控件繼承 View 的時候,是要重寫 onDraw 方法進行繪製的)。ViewGroup 默認是開啓這個優化標記的。固然若是明確 ViewGroup 是要經過 onDraw 方法進行繪製的時候,要手動關閉這個標記( setWillNotDraw(false) )。

示例:

咱們自定義一個控件,繼承 ViewGroup,重寫 onDraw 方法。

public class MyViewGroup extends ViewGroup {
    public MyViewGroup(Context context) {
        super(context);
        setWillNotDraw(false);
    }
    public MyViewGroup(Context context, AttributeSet attrs) {
        super(context, attrs);
        //這裏若是不調用這句話,咱們在使用的時候,onDraw 方法不會被調用
        setWillNotDraw(false);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        //onLayout 在這裏必須重寫,由於在 ViewGroup 中 onLayout是一個抽象方法
    }
    //重寫 onDraw 方法
    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawColor(Color.BLACK);
    }
}
複製代碼

xml 中使用

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    
    <com.ownchan.miclock.study.MyViewGroup
        android:layout_width="match_parent"
        android:background="@color/black"
        android:layout_height="match_parent"/>
</FrameLayout>
複製代碼

當咱們的自定義控件在繼承 ViewGroup 的時候,若是須要重寫 onDraw 方法進行繪製,須要執行 setWillNotDraw(false)

推薦一個詳解 draw 和 onDraw 調用時機好文: 你真的瞭解Android ViewGroup的draw和onDraw的調用時機嗎

總結

參考文檔

《Android開發藝術探索》第四章-View的工做原理

自定義View Draw過程- 最易懂的自定義View原理系列(4)

ViewRootImpl的performDraw過程

你真的瞭解Android ViewGroup的draw和onDraw的調用時機嗎

淺談ondraw的前世今身

相關文章
相關標籤/搜索