Android視圖繪制流程之onDraw()

measurelayout的過程都結(jié)束后,接下來就進入到draw的過程了。

同樣,根據(jù)名字你就能夠判斷出,在這里才真正地開始對視圖進行繪制

ViewRoot中的代碼會繼續(xù)執(zhí)行并創(chuàng)建出一個Canvas對象,然后調(diào)用View的draw()方法來執(zhí)行具體的繪制工作。

draw()方法內(nèi)部的繪制過程總共可以分為六步,其中第二步和第五步在一般情況下很少用到,因此這里我們只分析簡化后的繪制過程。代碼如下所示:

public void draw(Canvas canvas) {
    if (ViewDebug.TRACE_HIERARCHY) {
        ViewDebug.trace(this, ViewDebug.HierarchyTraceType.DRAW);
    }
    final int privateFlags = mPrivateFlags;
    final boolean dirtyOpaque = (privateFlags & DIRTY_MASK) == DIRTY_OPAQUE &&
            (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
    mPrivateFlags = (privateFlags & ~DIRTY_MASK) | DRAWN;
    // Step 1, draw the background, if needed
    int saveCount;
    if (!dirtyOpaque) {
        final Drawable background = mBGDrawable;
        if (background != null) {
            final int scrollX = mScrollX;
            final int scrollY = mScrollY;
            if (mBackgroundSizeChanged) {
                background.setBounds(0, 0,  mRight - mLeft, mBottom - mTop);
                mBackgroundSizeChanged = false;
            }
            if ((scrollX | scrollY) == 0) {
                background.draw(canvas);
            } else {
                canvas.translate(scrollX, scrollY);
                background.draw(canvas);
                canvas.translate(-scrollX, -scrollY);
            }
        }
    }
    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);
        // Step 6, draw decorations (scrollbars)
        onDrawScrollBars(canvas);
        // we're done...
        return;
    }
}

可以看到,第一步的作用是對視圖的背景進行繪制

  • 這里會先得到一個mBGDrawable對象
  • 然后根據(jù)layout過程確定的視圖位置來設置背景的繪制區(qū)域
  • 之后再調(diào)用Drawable的draw()方法來完成背景的繪制工作。

那么這個mBGDrawable對象是從哪里來的呢?其實就是在XML中通過android:background屬性設置的圖片或顏色。
當然你也可以在代碼中通過setBackgroundColor()、setBackgroundResource()等方法進行賦值。

接下來,第三步的作用是對視圖的內(nèi)容進行繪制。
這里去調(diào)用了一下onDraw()方法

那么onDraw()方法里又寫了什么代碼呢?進去一看你會發(fā)現(xiàn),原來又是個空方法啊。其實也可以理解,因為每個視圖的內(nèi)容部分肯定都是各不相同的,這部分的功能交給子類來去實現(xiàn)也是理所當然的。

接下來第四步的作用是對當前視圖的所有子視圖進行繪制。
(但如果當前的視圖沒有子視圖,那么也就不需要進行繪制了。)
因此你會發(fā)現(xiàn)View中的dispatchDraw()方法又是一個空方法,而ViewGroup的dispatchDraw()方法中就會有具體的繪制代碼。

以上都執(zhí)行完后就會進入到第六步,也是最后一步,這一步的作用是對視圖的滾動條進行繪制
那么你可能會奇怪,當前的視圖又不一定是ListView或者ScrollView,為什么要繪制滾動條呢?其實不管是Button也好,TextView也好,任何一個視圖都是有滾動條的,只是一般情況下我們都沒有讓它顯示出來而已。繪制滾動條的代碼邏輯也比較復雜,這里就不再貼出來了,因為我們的重點是第三步過程。

通過以上流程分析,相信大家已經(jīng)知道,View是不會幫我們繪制內(nèi)容部分的,因此需要每個視圖根據(jù)想要展示的內(nèi)容來自行繪制。如果你去觀察TextView、ImageView等類的源碼,你會發(fā)現(xiàn)它們都有重寫onDraw()這個方法,并且在里面執(zhí)行了相當不少的繪制邏輯。繪制的方式主要是借助Canvas這個類,它會作為參數(shù)傳入到onDraw()方法中,供給每個視圖使用。Canvas這個類的用法非常豐富,基本可以把它當成一塊畫布,在上面繪制任意的東西,那么我們就來嘗試一下吧。

這里簡單起見,我只是創(chuàng)建一個非常簡單的視圖,并且用Canvas隨便繪制了一點東西,代碼如下所示:

public class MyView extends View {
    private Paint mPaint;
    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        mPaint.setColor(Color.YELLOW);
        canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint);
        mPaint.setColor(Color.BLUE);
        mPaint.setTextSize(20);
        String text = "Hello View";
        canvas.drawText(text, 0, getHeight() / 2, mPaint);
    }
}

可以看到,我們創(chuàng)建了一個自定義的MyView繼承自View,并在MyView的構(gòu)造函數(shù)中創(chuàng)建了一個Paint對象。Paint就像是一個畫筆一樣,配合著Canvas就可以進行繪制了。

這里我們的繪制邏輯比較簡單,在onDraw()方法中先是把畫筆設置成黃色,然后調(diào)用Canvas的drawRect()方法繪制一個矩形。然后在把畫筆設置成藍色,并調(diào)整了一下文字的大小,然后調(diào)用drawText()方法繪制了一段文字。

就這么簡單,一個自定義的視圖就已經(jīng)寫好了,現(xiàn)在可以在XML中加入這個視圖,如下所示:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <com.example.diyview.MyView
        android:layout_width="200dp"
        android:layout_height="100dp" />

</LinearLayout>

將MyView的寬度設置成200dp,高度設置成100dp,然后運行一下程序,結(jié)果如下圖所示:

Screenshot_2019-05-09-09-54-04-016_com.example.di.png

圖中顯示的內(nèi)容也正是MyView這個視圖的內(nèi)容部分了。由于我們沒給MyView設置背景,因此這里看不出來View自動繪制的背景效果。

當然了Canvas的用法還有很多很多,這里我不可能把Canvas的所有用法都列舉出來,剩下的就要靠大家自行去研究和學習了。

到此為止,我們把視圖繪制流程的第三階段也分析完了。整個視圖的繪制過程就全部結(jié)束了,你現(xiàn)在是不是對View的理解更加深刻了呢?

Home:返回首頁

Previous:onLayout()


作者:guolin
來源:CSDN
原文:https://blog.csdn.net/guolin_blog/article/details/16330267
版權(quán)聲明:本文為博主原創(chuàng)文章,轉(zhuǎn)載請附上博文鏈接!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容