measure和layout的過程都結(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é)果如下圖所示:

圖中顯示的內(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)載請附上博文鏈接!