DecorView
在了解view的繪制流程之前,首先我們要知道一個DecorView的概念,什么是DecorView?
DecorView是整個界面的最頂層View,它的尺寸通常就是屏幕尺寸,也就是說,DecorView是充滿屏幕的,它實際上是一個FrameLayout
,又包含了一個子元素,LinearLayout
,這個LinearLayout
又包含兩個FrameLayout
,一個用來顯示標題,一個用來顯示內容。顯示內容的FrameLayout
,其ID為 android.R.id.content
。
我們在 Activity 中設置 Layout 時,用的方法是setContentView
,指的就是這個content。參考下圖
View的繪制流程是從ViewRoot
的performTraversals
開始的,它經過measure,layout,draw三個過程最終將View繪制出來。
performTraversals
會依次調用performMeasure
,performLayout
,performDraw
三個方法,他們會依次調用measure,layout,draw方法,然后又調用了onMeasure
,onLayout
,dispatchDraw
。在onMeasure
方法中,父容器會對所有的子View進行Measure
,子元素又會作為父容器,重復對它自己的子元素進行Measure
,這樣Measure
過程就從DecorView
一級一級傳遞下去了。Layout和Draw方法也是如此。
我們關注的重點是onMeasure方法,它決定了所有View的尺寸。
MeasureSpec
MeasureSpec
是一個32位 int 數值,它包含了兩組信息。高兩位代表SpecMode
,低30位代表SpecSize
。
SpecMode
指測量模式,有以下三個值:
-
EXACTLY
表示父容器已經確定好子view的大小值,這時候view的大小就是SpecSize
的值。它對應了LayoutParams
中設置match_parent
和指定具體數值的情況 -
AT_MOST
表示父容器并不確定子View的具體大小,但是確定了一個上限,就是SpecSize
,子View不能超過這個大小。具體值是多少,要看View 的具體實現。 -
UNSPECIFIED
父容器不對子View進行限制,可以按照自己的意愿隨意設置,比較少見,通常用于系統內部。
說完這三種模式可能大家還是非常模糊,我們看一下MeasureSpec的源碼,就一目了然了。
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
public static final int EXACTLY = 1 << MODE_SHIFT;
public static final int AT_MOST = 2 << MODE_SHIFT;
public static int makeMeasureSpec(int size, int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
MeasureSpec
內部封裝了makeMeasureSpec
,getMode
,getSize
三個方法,方便我們對MeasureSpec
數據進行處理。
那么MeasureSpec
到底是怎樣使用的呢?接下來我們就要看onMeasure
方法了。
View的onMeasure方法以及MeasureSpec的獲取
首先我們回顧一下View 的繪制流程,在上文中有一句黑體顯示的話,意思就是所有的View測量都是從最頂層的DecorView
開始的,我們就先看一下DecorView
的Measure過程,它的MeasureSpec
是怎樣得到的。
在ViewRoot
的performTraversals
方法中可以看到:
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
DecorView
的MeasureSpec
是通過getRootMeasureSpec
來得到的,它傳入了兩個參數,lp.width和lp.height在創建ViewGroup實例的時候就被賦值了,它們都等于MATCH_PARENT。
然后來看一下getRootMeasureSpec
的代碼:
private int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
到這里,我們就知道了,DecorView
作為最頂級的根View,它的MeasureSpec
就是EXACTLY+WindowSize,也就是說他總是充滿全屏的。
最頂層的DecorView
尺寸確定好之后,下一步就是各個子View的Measure
過程了,系統會從ViewGroup
開始一級一級向下Measure
。但是,我們又知道,一個View的大小同時還要受到父View的限制,它的大小是由本身的LayoutParams
,和父View 的MeasureSpec
共同決定的。
對于普通的View(非ViewGroup),它的Measure
過程是由ViewGroup
傳遞過來的,看一下ViewGrou
p的measureChildWithMargin
方法就清楚了:
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);
}
可以看到,子View的Measure方法,實際上就是在這里調用的。在Measure之前,先獲得子View的MeasureSpec
,然后調用了child.measure。
子View的MeasureSpec
又是通過getChildMeasureSpec
來獲取的,代碼如下:
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent asked to see how big we want to be
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
這一段代碼有點長,但是不難理解,該方法在調用時傳入了三個參數:父View的MeasureSpec
,Padding
(父View已經被占用的空間),和子View的LayoutParams
(match_parent,wrap_content,或者精確的數值)
然后根據父view的SpecMode
進行判斷,用表格的方式表示如下,橫排表示父View的SpecMode
,豎排表示子View的SpecMode(LayoutParams)
,內容表示View最終的SpecMode
:
EXACTLY (match_parent 或給定數值 ) |
AT_MOST (wrap_content ) |
UNSPECIFIED |
|
---|---|---|---|
dp/px( >0) |
EXACTLY +給定值
|
EXACTLY +給定值
|
EXACTLY +給定值
|
MATCH_PARENT |
EXACTLY +父View 剩余空間(或0) |
AT_MOST +父View 剩余空間(或0) |
UNSPECIFIED + 0 |
WRAP_CONTENT |
AT_MOST +父View 剩余空間(或0) |
AT_MOST +父View 剩余空間(或0) |
UNSPECIFIED + 0 |
到這里就很清楚了,子View的大小是由父View的MeasureSpce和它本身的LayoutParams共同決定的:
- 子
View
是具體數值:最終結果一定是EXACTLY
+·給定值· - 子·View·是
MATCH_PARENT
,它的大小就是父View
的剩余空間,mode和父View相同(不考慮UNSPECIFIED ) - 子
View
是WRAP_CONTENT
,它的大小是父View的剩余空間,mode是AT_MOST
Measure的流程到這里基本已經清楚了:從頂級View開始,先調用MeasureChild
將父View的Spec傳入,通過getChildMeasureSpec
方法,獲取到子View的Spec,然后通過child.measure(widhSpec,heightSpec)
將得到的子View Spec傳遞過去。
View的Measure過程
通過上邊的介紹,我們已經確定了View的MeasureSpec
,接下來就是具體的Measure方法了,因為View的Measure最終調用的是onMeasure
,我們只要看onMeasure
方法就可以了:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
代碼很簡單,就是一個setMeasuredDimension
,用來設置View的尺寸,傳入的參數,在上文已經介紹的很清楚了,從ViewGroup
傳遞過來。但是他還調用了一個getDefaultSize
方法,我們來看一下:
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
這個方法邏輯也很簡單,不考慮UNSPECIFIED
的情況下,最終返回的結果一定是在ViewGroup
里傳遞來的MeasureSpec
。
到這里,整個Measure過程就已經結束了。View的最終尺寸大小,遵循的就是上面我們得出的三條結論。要注意一點,當我們直接繼承View時:
子View是WRAP_CONTENT,它的大小是父View的剩余空間,mode是AT_MOST
前兩個都是沒有問題的,但是WRAP_CONTENT
,它的大小和MATCH_PARENT
是一樣的,也就是說WRAP_CONTENT
會不起作用。解決這個問題也很簡單,必須重寫onMeasure
方法,然后自定義一個默認的width和height,當傳遞過來的SpecMode
為AT_MOST
時,設置尺寸為定義的寬高。
ViewGroup的Measure過程
相對于單一View來說,ViewGroup
的Measure
方法要復雜一些,因為它不僅僅是確定自己的尺寸,還要測量每一個子View,并得到他們的MeasureSpec
。ViewGroup
并沒有重寫Measure(final的)方法,也沒有重寫onMeasure
方法,因為ViewGrou
p是抽象類,onMeasure
方法需要在具體的實現類中去重寫。
ViewGroup只是為測量子View添加了兩個新的方法: measureChildren()
和measureChild()
。代碼如下
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);
}
首先去遍歷子View,調用MeasureChild
方法,在該方法中獲取各個子View的MeasureSpec
,然后調用子View的Measure方法,傳遞各個子View的大小。
Layout過程
Layout的作用,是ViewGroup為自己的子View指定位置,現在看一下View中的layout方法:
public void layout(int l, int t, int r, int b) {
if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
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);
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLayoutChangeListeners != null) {
ArrayList<OnLayoutChangeListener> listenersCopy =
(ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
int numListeners = listenersCopy.size();
for (int i = 0; i < numListeners; ++i) {
listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
}
}
}
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}
關鍵在第11行,它最終調用了onLayout(changed, l, t, r, b)
方法,來完成最終的Layout過程。
那么我們就看一下onLayout
方法,你會發現是這樣的:
View:
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
ViewGroup
@Override
protected abstract void onLayout(boolean changed,
int l, int t, int r, int b);
View中是一個空方法,ViewGroup中是一個抽象方法。為什么呢?其實答案很簡單。Layout過程是確定子元素在自己布局中的位置,view是不存在子元素的,所以是空方法,它只需要通過setFrame
來決定自身的位置。而ViewGroup
本身就是一個抽象方法,它的不同實現會有不同的Layout方式,所以在繼承ViewGroup
的布局中,必須指定自己的Layout方式,因此,ViewGroup中是一個抽象方法。
layout方法的大致流程如下:
首先通過setFrame
來設置View的四個頂點位置,并保存起來。在Layout時,會先用setFrame方法來保存四個頂點的坐標,并進行判斷,值如果發生了變化,就會調用onLayout
方法來重新定位子元素。
如果是單一View,調用setFrame
方法之后,其實就已經結束了,因為他沒有子元素,onLayout方法是空的,調用onlayout方法沒有任何意義。而對于ViewGroup來說,他是包含子元素的,需要在onLayout方法中繼續確定子元素的位置。
借用一下LinearLayout的源碼來分析:
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (mOrientation == VERTICAL) {
layoutVertical(l, t, r, b);
} else {
layoutHorizontal(l, t, r, b);
}
}
可以看見,VERTICAL
和HORIZONTAL
的方法是不一樣的。看一下layoutVertical
的代碼:
void layoutVertical(int left, int top, int right, int bottom) {
......
final int count = getVirtualChildCount();
......
for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
if (child == null) {
childTop += measureNullChild(i);
} else if (child.getVisibility() != GONE) {
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
final LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) child.getLayoutParams();
......
if (hasDividerBeforeChildAt(i)) {
childTop += mDividerHeight;
}
childTop += lp.topMargin;
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
i += getChildrenSkipCount(child, i);
}
}
}
源代碼比較長,我們只截取一部分,其實邏輯很簡單,就是遍歷所有的子元素,并調用setChildFrame方法來確定各個子元素的位置,因為是Vertical的排列方式,所以childTop 的值不斷增大,子元素會依次向下排列。
setChildFrame代碼如下:
private void setChildFrame(View child, int left, int top, int width, int height) {
child.layout(left, top, left + width, top + height);
}
只是調用子View的Layout方法而已。View的Layout方法上邊已經介紹過了,如果是單一View,調用setFrame結束,如果是ViewGroup,會繼續調用他的onlayout方法,這樣一層一層向下確定各個View的位置。整個流程結束以后,所有的Layout過程就結束了。
可能大家也注意到一個問題,layout方法會接收四個參數,分別代表四個頂點的位置,而該方法是通過child.layout(left, top, left + width, top + height);來調用的,實際上,只有left和top兩個值是確定的
另外兩個頂點是通過寬和高來確定的,寬和高是這樣獲取的:
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
得到四個頂點的值之后,最終會傳遞到setFrame方法,setFrame的部分代碼:
protected boolean setFrame(int left, int top, int right, int bottom) {
boolean changed = false;
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
這樣看起來,view最終的尺寸就是Measure過程中確定的尺寸
如果我們改動一下代碼,改變一下ViewGroup的setChildFrame或者view的layout方法:
child.layout(left, top, left + width+100, top + height+100);
或者
public void layout(int l, int t, int r, int b) {
super.layout(l, t, r+100, b+100);
}
這樣最終得到的View就會比Measure出來的View大了100的尺寸,在setFrame的時候,得到的四個坐標值也會大100,
看一下View的getwidth和getHeight代碼:
public final int getWidth() {
return mRight - mLeft;
}
public final int getHeight() {
return mBottom - mTop;
}
此時獲取到的View最終寬和高就會比Measure出來的寬和高(getMeasuredWidth,getMeasuredHeight)要大100px了。
所以,Measure出來的尺寸,通常情況下是View的最終尺寸,實際上View的最終尺寸是在Layout階段來決定的,它并不一定等于MeasureSpec的大小。
獲取View的尺寸
上邊我們提到了兩個方法:getMeasuredWidth
,getWidth
,用來獲取View的尺寸,我們知道,View的尺寸是通過Measure
和Layout
共同決定的,那么獲取View的尺寸就很簡單了,調用這兩個方法就可以了。實際上并不是這么簡單的。因為Activity的生命周期和View的繪制不是同步的。在onCreate
,onStart
,onResume
里簡單的調用這兩個方法,得到的結果是不確定的,因為View可能還沒有繪制完成。那么怎樣才能得到正確的View尺寸?
1.重寫View的onFocusChanged
方法。
在View得到或者失去焦點的時候,該方法都會被調用。可以這么理解,既然View已經得到焦點了,那么它的寬和高必定已經準備好了。所以獲取尺寸是完全可以的。所以我們可以重寫該方法,在此處調用getMeasuredWith
方法。
2.利用view.post(runnable)
View在處理post的消息時,肯定已經初始化好了,所以利用此方法也能正確的獲取到View尺寸。
3.ViewTreeObserver
它是view事件的一個觀察者,用來監聽ViewTree的各種事件,針對不同的事件它定義了許多不同的接口,可以利用onGlobalLayout
方法來獲取View尺寸。
4.Activity的onPostCreate()
Draw過程
measure和layout的過程都結束后,接下來就進入到draw的過程了,源碼如下:
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;
}
}
可以看到,第一步是從第9行代碼開始的,這一步的作用是對視圖的背景進行繪制。這里會先得到一個mBGDrawable
對象,然后根據layout過程確定的視圖位置來設置背景的繪制區域,之后再調用Drawable
的draw()
方法來完成背景的繪制工作。那么這個mBGDrawable
對象是從哪里來的呢?其實就是在XML中通過android:background屬性設置的圖片或顏色。當然你也可以在代碼中通過setBackgroundColor()
、setBackgroundResource()
等方法進行賦值。
接下來的第三步是對視圖的內容進行繪制。可以看到,這里去調用了一下onDraw()
方法,那么onDraw()
方法里又寫了什么代碼呢?進去一看你會發現,原來又是個空方法啊。其實也可以理解,因為每個視圖的內容部分肯定都是各不相同的,這部分的功能交給子類來去實現也是理所當然的。
第三步完成之后緊接著會執行第四步,這一步的作用是對當前視圖的所有子視圖進行繪制。但如果當前的視圖沒有子視圖,那么也就不需要進行繪制了。因此你會發現View中的dispatchDraw()
方法又是一個空方法,而ViewGroup
的dispatchDraw()
方法中就會有具體的繪制代碼。
以上都執行完后就會進入到第六步,也是最后一步,這一步的作用是對視圖的滾動條進行繪制。那么你可能會奇怪,當前的視圖又不一定是ListView
或者ScrollView
,為什么要繪制滾動條呢?其實不管是Button也好,TextView
也好,任何一個視圖都是有滾動條的,只是一般情況下我們都沒有讓它顯示出來而已。
通過以上流程分析,相信大家已經知道,View是不會幫我們繪制內容部分的,因此需要每個視圖根據想要展示的內容來自行繪制。如果你去觀察TextView
、ImageView
等類的源碼,你會發現它們都有重寫onDraw()
這個方法,并且在里面執行了相當不少的繪制邏輯。繪制的方式主要是借助Canvas這個類,它會作為參數傳入到onDraw()
方法中,供給每個視圖使用。
另外,view還有一個特殊方法:setWillNotDraw。默認情況下,view是不啟用這個參數的,而viewGroup
會啟用。因為單一View是肯定需要繪制自身的,而ViewGroup
只是作為單一View的載體,draw工作是交給子View的,它不需要繪制自身,也就是默認ViewGroup
是透明的,如果想讓ViewGroup繪制自身,需要調用setWillNotDraw(false)
,來啟用draw功能,然后重寫的onDraw
方法才會起作用。