ViewRoot和DecorView
這是在View三大流程之前(measure
, layout
, draw
),需要了解的概念.
ViewRoot
對(duì)應(yīng)于ViewRootImpl
, 它是連接WindowManager
和DecorView
的紐帶. View的三大流程都是通過ViewRoot
來完成的. 當(dāng)一個(gè)Activity
對(duì)象在ActivityThread
被創(chuàng)建后. 會(huì)將DecorView
添加到Window
中, 同時(shí)會(huì)創(chuàng)建ViewRootImp
對(duì)象, 并將ViewRootImpl
對(duì)象和DecorView
建立關(guān)聯(lián).
View繪制流程是從ViewRoot的PerformTraversals()
開始的. 經(jīng)過三大流程才能將一個(gè)View繪制出來.
PerformTraversals()
會(huì)依次調(diào)用performMeasure
, performLayout
, performDraw
. 而前兩種內(nèi)部的調(diào)用基本一致,都是先調(diào)用measure()/layout()
,然后再調(diào)用onMeasure()/onLayout()
在這個(gè)方法中會(huì)對(duì)所有子元素進(jìn)行測(cè)量和繪制.依次向內(nèi)部傳遞. performDraw()
有點(diǎn)不同是在draw
調(diào)用的dispatchDraw()
.
-
measure過程: 決定了View寬高, measure后可以通過
getMeasureWidth和getMeasureHeight
來獲取View的寬高. 一般情況下是最終寬高. -
layout過程: 決定了View的頂點(diǎn)坐標(biāo)和實(shí)際View的寬高. 完成后通過
getTop, getBottom, getLeft, getRight
獲得四個(gè)頂點(diǎn), 通過getWidth,和getHeight
獲得寬高 - draw過程: 只有draw()方法完成之后View的內(nèi)容才會(huì)顯示出來.
setContentView(R.layout.activity_inside_intercept);
((ViewGroup) getWindow().getDecorView().findViewById(android.R.id.content)).getChildAt(0);
上面第一行可以說無時(shí)無刻不存在. 而下面這行在上一章說過就是獲得我們?cè)O(shè)置的布局.那DecorView
布局究竟是怎么樣的, 下圖.
DecorView
就是一個(gè)FrameLayout. 而一般情況下它的布局就如上面圖那樣(具體和主題有關(guān)系). 而我們經(jīng)常setContentView(xxx)
. 就是把我們編寫的xml的布局添加到了DecorView
的android.R.id.content
的控件布局中. 所以也就能說通為什么getChildAt(0)
會(huì)獲得我們的的布局.
并且為什么我們用的關(guān)聯(lián)布局的方法是setContent…
總結(jié)
圖:
MeasureSpec
很大程度上決定一個(gè)View的尺寸規(guī)格, 之所以不是絕對(duì), 是因?yàn)檫@個(gè)過程還受父容器的影響.
理解MeasureSpec
MeasureSpec
本身是一個(gè)32位的int值, 但是卻表示了兩種信息.
- 高2位: 代表了SpecMode, 測(cè)量模式
- 低30位: 代表了SpecSize, 在上述測(cè)量模式中的大小
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 makeSafeMeasureSpec(int size, int mode) {
if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) {
return 0;
}
return makeMeasureSpec(size, mode);
}
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
.....
}
是不是挺有意思. 三種類型分別高二位01, 00, 10來代表. 直接利用位運(yùn)算. 來實(shí)現(xiàn)可以讓頻繁計(jì)算的東西使用最接近計(jì)算機(jī)的運(yùn)算方式. 不需要額外的轉(zhuǎn)換. 也避免了過多的對(duì)象內(nèi)存分配.
說一下SpecMode的三種模式
- UNSPECIFIED: 父容器不對(duì)View有任何的限制,要多大就給多大, 這種情況一般用于系統(tǒng)內(nèi)部,表示一中測(cè)量狀態(tài)
-
EXACTLY: 父容器已經(jīng)檢測(cè)出View所需要的精確大小, 這個(gè)時(shí)候View的最終大小就是SpecSize所指定的值. 對(duì)應(yīng)著LayoutParams中的
match_parent
和具體的數(shù)值. -
AT_MOST: 父容器制定了一個(gè)可用的大小及SpecSize, View的大小不能超過這個(gè)值, 它對(duì)應(yīng)與LayoutParams中的
wrap_content
MeasureSpec和LayoutParams關(guān)系
通常設(shè)置的LayoutParams
,系統(tǒng)會(huì)在父容器的的約束下轉(zhuǎn)換成對(duì)應(yīng)的MeasureSpec
,然后根據(jù)這個(gè)MeasureSpec
來確定View測(cè)量后的寬高. 所以View自身的MeasureSpec
是需要LayoutParams
和父容器一起組合生成的.
上面講述的是普通View, 但是頂級(jí)View(DecorView)有所不同. DecorView是物理窗口尺寸和自身的LayoutParams
決定的. 具體在ViewRootImpl類measureHierarchy()
進(jìn)行生成的.
MeasureSpec一旦確定, onMeasure中就可以測(cè)量View的寬高.
對(duì)于我們?nèi)粘2僮鞯腣iew
View的measure
過程是由ViewGroup傳遞而來的. 看ViewGroup#measureChildWithMargins()
方法
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);
}
上面會(huì)對(duì)子元素進(jìn)行measure
, 而在此之前,會(huì)通過getChildMeasureSpec()
來得到子元素的MeasureSpec
. 通過調(diào)用方法傳入的參數(shù)看到. 生成View的MeasureSpec
和父容器的MeasureSpec
, View自身方向的padding``margin
, 和自身的LayoutParams這三個(gè)因素相關(guān)聯(lián).
而其中的getChildmeasureSpec()
方法: 就是根據(jù)父容器的MeasureSpec
同時(shí)結(jié)合View自身的LayoutParams來確定子元素的MeasureSpec
.這個(gè)方法總結(jié)如下:
-
dp/px: 不管父容器的
MeasureSpec
是什么. View都是EXACTLY
(精確模式), 而大小遵循自身LayoutParams
的大小. -
match_parent: 如果父容器是
EXACTLY
(精確模式),那么子View也是EXACTLY
(精確模式)并且大小是父容器的剩余空間. 如果父容器是AT_MOST
(最大模式),那么子View也是AT_MOST
(最大模式)并且大小不會(huì)超過父容器的剩余空間. -
wrap_content: 不管父容器是什么. View都是
AT_MOST
(最大模式), 并且大小不能超過父容器剩余空間.
上述沒有說明UNSPECIFIED
在match_parent
和wrap_content
中. 因?yàn)檫@個(gè)模式主要用于系統(tǒng)多次Measure的情形,一般來說不需要關(guān)注.
View的工作流程
主要指measure, layout, draw三大流程. 即測(cè)量,布局,繪制.
measure過程
這里面存在兩種場(chǎng)景:
- View: 通過了
measure
方法就完成了測(cè)量過程 - ViewGroup: 除了測(cè)量自己,還會(huì)遍歷去調(diào)用所有子元素的
measure
方法. 各個(gè)子元素在遞歸去執(zhí)行這個(gè)流程
View的measure過程
View的measure過程由其measure()
方法來完成, measure()
方法是一個(gè)final類型, 而在內(nèi)部調(diào)用了onMeasure()
這個(gè)可不是final, 所以也可以自定義的時(shí)候復(fù)寫. 看一下內(nèi)部.
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
setMeasureDimension()
會(huì)設(shè)置View寬高的測(cè)量值.
這里需要看一下getDefaultSize()
這個(gè)方法.
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;
}
看到如果這個(gè)view是EXACTLY
(精準(zhǔn)模式), 那么返回的大小就是SpecSize. UNSPECIFIED
一般用于系統(tǒng)測(cè)量先不說. 而AT_MOST
(最大模式)的時(shí)候. 雖然是不同模式但是默認(rèn)情況下和精確模式是一樣的結(jié)果.
getSuggestedMinimumWidth()
和getSuggestedMinimumHeight()
. 看一下實(shí)現(xiàn).
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
protected int getSuggestedMinimumHeight() {
return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}
首先會(huì)看是否設(shè)置了背景.
-
無背景: 那么寬度為
mMinWidth
,這個(gè)值對(duì)應(yīng)布局中的android:minWidth
屬性,默認(rèn)為0. -
有背景: 那么取
mMinWidth
和mBackground.getMinimumHeight()
最大值.
而getMinimumHeight()
根據(jù)看一下:
public int getMinimumHeight() {
final int intrinsicHeight = getIntrinsicHeight();
return intrinsicHeight > 0 ? intrinsicHeight : 0;
}
原來getMinimumHeight()
返回的就是Drawable
的原始高度. 如果沒有就返回0. 關(guān)于原始高度舉個(gè)例子ShapeDrawable
無原始寬高, BitmapDrawble
有原始寬高就是圖片的尺寸.
整理getDefaultSize()
: 直接繼承View的自定義控件需要重寫onMeasure()
方法并設(shè)置wrap_content
時(shí)的自身大小,否則在布局中使用wrap_content
雖然View自身的MeasureSpec
的低30位保存了父容器計(jì)算自身的剩余大小. 但是在自定義的時(shí)候如果不進(jìn)行處理wrap_content,那么就會(huì)調(diào)用默認(rèn)setMeasureDimension()方法. 而默認(rèn)中方法的實(shí)參傳遞的是getDefaultSize()
這個(gè)方法中對(duì)AT_MOST這種模式?jīng)]有處理. 直接沿用和精確模式的大小(相當(dāng)于設(shè)置了wrap_content卻得到了match_parent的顯示結(jié)果)
可以針對(duì)這個(gè)問題, 做出對(duì)應(yīng)的編碼進(jìn)行解決:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSpaceSize = MeasureSpec.getSize(widthMeasureSpec);
int widthSpaceMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSpaceSize = MeasureSpec.getSize(heightMeasureSpec);
int heightSpaceMode = MeasureSpec.getMode(heightMeasureSpec);
//設(shè)置兩個(gè)默認(rèn)值寬高
int defaultHeight = 100;
int defaultWidth = 100;
// 針對(duì)AT_MOST模式進(jìn)行特殊處理
if (widthSpaceMode == MeasureSpec.AT_MOST
&& heightSpaceMode == MeasureSpec.AT_MOST){
setMeasuredDimension(defaultWidth, defaultHeight);
}else if (widthSpaceMode == MeasureSpec.AT_MOST){
setMeasuredDimension(defaultWidth, heightSpaceSize);
}else if (heightSpaceMode == MeasureSpec.AT_MOST){
setMeasuredDimension(widthMeasureSpec, defaultHeight);
}
}
ViewGroup的Measure
對(duì)于
ViewGroup
不光會(huì)測(cè)量自己,還會(huì)遍歷調(diào)用所有的子元素的measure()
. 和View
不同的是ViewGroup
是一個(gè)抽象類,它沒有重寫onMeasure
,但提供了measureChildren()
的方法.
這個(gè)measureChildren()
方法內(nèi)部比較簡(jiǎn)單就是遍歷自己的孩子然后調(diào)用->measureChild()
這個(gè)measureChild()
這個(gè)方法前面貼過源碼. 就是取出子元素的LayoutParams
,并調(diào)用->getChildMeasureSpec()
. 通過傳入子元素的LayoutParams里面的寬高屬性, 子元素的padding和margin, 父元素當(dāng)前(當(dāng)前ViewGroup)的MeasureSpec屬性來計(jì)算出子元素的MeasureSpec
最后調(diào)用->child.measure()
傳入之前計(jì)算的測(cè)量規(guī)格.
ViewGroup為什么沒有定義測(cè)量的具體過程? 因?yàn)榫唧w的測(cè)量過程需要交給子類去實(shí)現(xiàn)的. 比如LinearLayout
,RelativeLayout
.
看一下LinearLayout
的onMeasure()
是如何定義的.
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
根據(jù)設(shè)置的排列方式這里分之了兩種測(cè)量方法. 稍微看一下大概輪廓,選擇measureVertical()
不貼源碼了這個(gè)方法300行呢!
首先這個(gè)方法會(huì)遍歷每個(gè)子元素并執(zhí)行->measureChildBeforeLayout()
方法.這個(gè)方法內(nèi)部會(huì)調(diào)用子元素的measure()
, 這樣子元素會(huì)依次測(cè)量. 并且會(huì)通過mTotalLenght
這個(gè)變量來存儲(chǔ)LinearLayout在豎直方向上的初步高度, 每測(cè)量一個(gè)就會(huì)增加. 當(dāng)子元素測(cè)量完之后,LinearLayout會(huì)測(cè)量自己的大小.
在對(duì)自己進(jìn)行測(cè)量的時(shí)候. 如果布局中的高度采用的是match_parent
或者具體數(shù)值
, 那么它的測(cè)量過程和View一樣,即高度為specSize
. 如果布局中采用wrap_content
那么高度就是所有的子元素總和但是不能超過父元素剩余空間, 還有豎直方向LinearLayout的padding
. 具體可參考resolveSizeAndState()
的實(shí)現(xiàn).
到這里基本上measure
測(cè)量過程已經(jīng)做了比較詳細(xì)的分析. 這個(gè)過程也是三大過程中最復(fù)雜的一個(gè). 在measure
完成之后就可以通過getMeasuredWidth/Height
方法獲取View的測(cè)量寬高. 但是請(qǐng)注意:某些極端情況下,measure可能執(zhí)行多次. 所以盡量在onLayout()
方法中去獲得最終寬高.
正確獲取寬高方法
首先明確一點(diǎn):View的measure和Activity的生命周期方法不是同步執(zhí)行.所以無法保證在某個(gè)生命周期(onCreate,onStart)獲取到正確的測(cè)量寬高
- onWindowFocusChanged()
- view.post(runnable)
- ViewTreeObserve
- view.measure()
-
onWindowFocusChanged()
:View已經(jīng)初始化完畢,寬高已經(jīng)準(zhǔn)備好. 這里需要注意只要Activity的焦點(diǎn)發(fā)生變化此方法就會(huì)被調(diào)用.所以如果你的界面會(huì)頻繁的進(jìn)行onPause
和onResume
.并且里面有很多關(guān)聯(lián)依賴的方法. 那就請(qǐng)注意這不是一個(gè)好辦法. - 通過
post
可以將一個(gè)runnable投遞到消息隊(duì)列的尾部,然后等待Looper
調(diào)用此runnable的時(shí)候.View已經(jīng)初始化完畢. - 使用
ViewTreeObserver
. 當(dāng)View的可見性發(fā)生了改變的時(shí)候.onGlobalLayout()
將發(fā)生回調(diào).注意伴隨著View樹的狀態(tài)改變等,這個(gè)回調(diào)方法可能會(huì)被調(diào)用多次. 使用代碼如下
ViewTreeObserver viewTreeObserver = tv_main.getViewTreeObserver();
viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
tv_main.getViewTreeObserver().removeOnGlobalLayoutListener(this);
tv_main.getMeasuredHeight();
tv_main.getMeasuredWidth();
}
});
- view.measure(widthMeasureSpec, heightMeasureSpec)
也可以手動(dòng)進(jìn)行測(cè)量,但是需要分情況處理.
match_parent
當(dāng)View是此屬性的時(shí)候無法使用measure()
,首先使用這種方法需要的參數(shù),是通過父容器和子元素組合來生成的子元素的MeasureSpec
屬性. 所以在外部我們不知道父元素的參數(shù)值得時(shí)候只能處理不需要父元素?cái)?shù)據(jù)就可以生成子元素的MeasureSpec的模式
所以很清楚, 這個(gè)match_patch
這個(gè)模式,在給其子元素構(gòu)造MeasureSpec
的時(shí)候需要得值parentSize
,所以得到的也是無效.
具體數(shù)值px/dx
假設(shè)這里是100px, 首先構(gòu)成寬高對(duì)應(yīng)的MeasureSpec
屬性
int widthSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
int heightSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
tv_main.measure(widthSpec, heightSpec);
wrap_content
int widthSpec = View.MeasureSpec.makeMeasureSpec(((1 << 30)-1), View.MeasureSpec.AT_MOST);
int heightSpec = View.MeasureSpec.makeMeasureSpec(((1 << 30)-1), View.MeasureSpec.AT_MOST);
tv_main.measure(widthSpec, heightSpec);
通過(1<<30)-1 可以構(gòu)成一個(gè)MeasureSpec
低30位的最大值. 用理論上View能支持的最大值去構(gòu)造
關(guān)于網(wǎng)上一些在make的使用傳入UNSPECIFIED
,屬于違背了內(nèi)部實(shí)現(xiàn)的規(guī)范.不用最好
關(guān)于網(wǎng)上另一種measure()
直接傳入LayoutParams.WRAP_CONTENT
. 其實(shí)也只有當(dāng)子元素為wrap_content
和子元素為match_parent
并且父元素是wrap_conetnt
時(shí)會(huì)碰巧有效.
layout過程
在ViewGroup
中會(huì)先通過layout()
方法確定本身的位置. 然后調(diào)用onLayout()
方法遍歷所有的子元素,并調(diào)用子元素的layout()
方法確定子元素的位置…依次循環(huán).
提出View
的layout
方法, 這里抽取部分代碼
public void layout(int l, int t, int r, int b) {
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);}
}
這樣來看,大致流程通過setFrame()
方法來設(shè)定View的四個(gè)頂點(diǎn)的位置, 即mLeft,mTop,mBottom,mRight,這四個(gè)頂點(diǎn)一旦確定.當(dāng)前View的位置也就確定. 然后會(huì)調(diào)用onLayout()
方法. 這個(gè)方法是確定子元素的View位置.
這里的和onMeasure()
類似, onLayout()
具體實(shí)現(xiàn)和具體的布局有關(guān), 所以View和ViewGroup均沒有真正實(shí)現(xiàn)onLayout()
方法.
看一下LinearLayout
的onLayout()
源碼
@Override
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);
}
}
和onMeasure()
一樣分支,接下來跟進(jìn)layoutVertical()
貼出主要代碼
void layoutVertical(int left, int top, int right, int bottom) {
//省略一部分...
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();
int gravity = lp.gravity;
if (gravity < 0) {
gravity = minorGravity;
}
//省略一部分...
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);
}
}
}
上面代碼大體邏輯: 首先遍歷所有孩子并調(diào)用setChildFrame()
來為子元素指定對(duì)應(yīng)的位置. 其中childTop
會(huì)逐漸增大, 這就意味著后面的子元素會(huì)被放置在靠下的位置. 而setChildFrame()
內(nèi)部?jī)H有一行代碼, 就是調(diào)用子元素的layout()
并傳入它自身應(yīng)該存放的位置.
private void setChildFrame(View child, int left, int top, int width, int height) {
child.layout(left, top, left + width, top + height);
}
而在setChildFrame()
中傳入的寬高就是子元素的測(cè)量寬高.
而在子元素的layout()
中通過setFrame()
來設(shè)置元素的四個(gè)頂點(diǎn).
getWidth()layout中的寬 和getMeasureWidth()中的寬永遠(yuǎn)一樣么?
在一般情況下,測(cè)量measure和layout時(shí)候的值是完全一樣的. 因?yàn)?code>layout()中接受的參數(shù)就是通過測(cè)量的結(jié)果獲取到的. 并且內(nèi)部直接通過setFrame()
賦值到自己的四個(gè)成員變量上. 但是如果對(duì)layout()
進(jìn)行了復(fù)寫.如下
@Override
protected void layout(int l, int t, int r, int b) {
super.layout( l, t+200, r, b+200);
}
如果進(jìn)行了這樣的復(fù)寫, 那么最終寬高永遠(yuǎn)會(huì)與測(cè)量的出來的值相差200.
draw過程
這個(gè)過程只是將View繪制到屏幕上面.
- 繪制背景
background.draw(canvas)
- 繪制自己
onDraw()
- 繪制children
dispatchDraw()
- 繪制裝飾
onDrawScrollBars()
View繪制過程傳遞是通過dispatchDraw()
實(shí)現(xiàn)的. 傳遞了自己的畫布. 這個(gè)方法會(huì)遍歷子元素并且調(diào)用元素的draw()
View一個(gè)特有的方法setWillNotDraw()
, 這個(gè)方法是設(shè)置了true
那么系統(tǒng)會(huì)進(jìn)行相應(yīng)的優(yōu)化. 在View中默認(rèn)是關(guān)閉的. 而ViewGroup默認(rèn)是開啟的. 如果我們繼承了自定義ViewGroup如果還需要繪制自己的內(nèi)容那么需要顯示的關(guān)閉此標(biāo)記.
參看文章
《Android 開發(fā)藝術(shù)探索》書集
《Android 開發(fā)藝術(shù)探索》 04-View的工作原理
View的繪制-measure流程詳解
View的繪制-layout流程詳解
View的繪制-draw流程詳解