View的繪制流程(一)
每一個視圖的繪制過程都必須經歷三個最主要的階段瘾敢,即onMeasure()垒棋、onLayout()和onDraw()
Measure
知識點
1. MeasureSpec
- 我們通過Android系統(tǒng)的MeasureSpec類來測量view烟零。
MeasureSpec是一個32位的int值带猴,其中高2位為測量模式荚斯,低30位為測量的大小惩猫。
作用:一個MeasureSpec封裝了從父容器傳遞給子容器的布局要求。
含義:MeasureSpec是由父View的MeasureSpec和子View的LayoutParams通過簡單的計算得出一個針對子View的測量要求猬仁,這個測量要求就是MeasureSpec帝璧。
- MeasureSpec的三種測量模式
UPSPECIFIED : 父容器對于子容器沒有任何限制,子容器想要多大就多大。
EXACTLY: 父容器已經為子容器設置了尺寸,子容器應當服從這些邊界,不論子容器想要多大的空間:當我們的控件layout_width(Height)屬性指定為具體的值或者是match_parent時湿刽。
AT_MOST:子容器可以是聲明大小內的任意大械乃浮:當我們的控件layout_width(Height)屬性指定為wrap_content時。
2. ViewRootImpl
每個Activity都包含一個Window對象诈闺,在Android中Window對象通常由PhoneWindow來實現(xiàn)(是Activity和整個View系統(tǒng)交互的接口渴庆,每個Window都對應著一個View和一個ViewRootImpl,Window(Window無法直接訪問雅镊,要通過WindowManager)和View通過ViewRootImpl來建立聯(lián)系襟雷,執(zhí)行添加,刪除仁烹,更新View等操作)耸弄。PhoneWindow將一個DecorView設置為整個應用窗口的根View。DecorView將要顯示的具體內容呈現(xiàn)在了PhoneWindow上卓缰。所以繪制的入口是由ViewRootImpl的performTraversals方法來發(fā)起Measure计呈,Layout,Draw等流程的征唬。
View的Measure過程
View測量前的準備工作 ---> MeasureSpec
在ViewRoot的performTraversals()方法中震叮,調用子View的measure()方法。measure()方法接收兩個參數(shù)鳍鸵,widthMeasureSpec和heightMeasureSpec,這兩個值分別用于確定視圖的寬度和高度的規(guī)格和大小尉间。雖然這兩個參數(shù)從父View傳遞過來偿乖,是父View的MeasureSpec和子View自己的LayoutParams共同決定的击罪,而子View的LayoutParams其實就是我們在xml寫的時候設置的layout_width和layout_height轉化而來的。(父View的Measure過程會等子View測量之后再對自己進行測量)贪薪。
舉個例子:傳遞給子View的MeasureSpc的計算是通過調用measureChildWithMargins()來計算的
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) {
// 子View的LayoutParams媳禁,你在xml的layout_width和layout_height,
// layout_xxx的值最后都會封裝到這個個LayoutParams。
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
//根據(jù)父View的測量規(guī)格和父View自己的Padding画切,
//還有子View的Margin和已經用掉的空間大锌⒒(widthUsed),就能算出子View的MeasureSpec,具體計算
過程看getChildMeasureSpec方法霍弹。
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);
//通過父View的MeasureSpec和子View的自己LayoutParams的計算毫别,算出子View的MeasureSpec,然后父
容器傳遞給子容器的
// 然后讓子View用這個MeasureSpec(一個測量要求典格,比如不能超過多大)去測量自己岛宦,如果子View是ViewGroup 那還會遞歸往下測量。
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
// spec參數(shù) 表示父View的MeasureSpec
// padding參數(shù) 父View的Padding+子View的Margin耍缴,父View的大小減去這些邊距砾肺,才能精確算出
// 子View的MeasureSpec的size
// childDimension參數(shù) 表示該子View內部LayoutParams屬性的值(lp.width或者lp.height)
// 可以是wrap_content、match_parent防嗡、一個精確指(an exactly size),
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec); //獲得父View的mode
int specSize = MeasureSpec.getSize(spec); //獲得父View的大小
//父View的大小-自己的Padding+子View的Margin变汪,得到值才是子View的大小。
int size = Math.max(0, specSize - padding);
int resultSize = 0; //初始化值蚁趁,最后通過這個兩個值生成子View的MeasureSpec
int resultMode = 0; //初始化值裙盾,最后通過這個兩個值生成子View的MeasureSpec
switch (specMode) {
// Parent has imposed an exact size on us
//1、父View是EXACTLY的 荣德!
case MeasureSpec.EXACTLY:
//1.1闷煤、子View的width或height是個精確值 (an exactly size)
if (childDimension >= 0) {
resultSize = childDimension; //size為精確值
resultMode = MeasureSpec.EXACTLY; //mode為 EXACTLY 。
}
//1.2涮瞻、子View的width或height為 MATCH_PARENT/FILL_PARENT
else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size; //size為父視圖大小
resultMode = MeasureSpec.EXACTLY; //mode為 EXACTLY 鲤拿。
}
//1.3、子View的width或height為 WRAP_CONTENT
else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size; //size為父視圖大小
resultMode = MeasureSpec.AT_MOST; //mode為AT_MOST 署咽。
}
break;
// Parent has imposed a maximum size on us
//2近顷、父View是AT_MOST的 !
case MeasureSpec.AT_MOST:
//2.1宁否、子View的width或height是個精確值 (an exactly size)
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension; //size為精確值
resultMode = MeasureSpec.EXACTLY; //mode為 EXACTLY 窒升。
}
//2.2、子View的width或height為 MATCH_PARENT/FILL_PARENT
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; //size為父視圖大小
resultMode = MeasureSpec.AT_MOST; //mode為AT_MOST
}
//2.3慕匠、子View的width或height為 WRAP_CONTENT
else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size; //size為父視圖大小
resultMode = MeasureSpec.AT_MOST; //mode為AT_MOST
}
break;
// Parent asked to see how big we want to be
//3饱须、父View是UNSPECIFIED的 !
case MeasureSpec.UNSPECIFIED:
//3.1台谊、子View的width或height是個精確值 (an exactly size)
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension; //size為精確值
resultMode = MeasureSpec.EXACTLY; //mode為 EXACTLY
}
//3.2蓉媳、子View的width或height為 MATCH_PARENT/FILL_PARENT
else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = 0; //size為0譬挚! ,其值未定
resultMode = MeasureSpec.UNSPECIFIED; //mode為 UNSPECIFIED
}
//3.3、子View的width或height為 WRAP_CONTENT
else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = 0; //size為0! 酪呻,其值未定
resultMode = MeasureSpec.UNSPECIFIED; //mode為 UNSPECIFIED
}
break;
}
//根據(jù)上面邏輯條件獲取的mode和size構建MeasureSpec對象减宣。
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
DecorView的widthMeasureSpec和heightMeasureSpec是從ViewRootImpl中傳遞來的。也是類似的計算方法玩荠∑犭纾可以參考郭神的博客
MeasureSpec的計算流程大致就是:通過父View的MeasureSpec和子View的LayoutParams來計算出子View的計算標準MeasureSpec,并傳遞給子View的measure()方法讓子View測量自己和自己子View的尺寸阶冈。
下面介紹幾個常見的情況:
-
如果父View的MeasureSpec的模式是EXACTLY闷尿,那么父View的MeasureSpec.size就是確定大小的
- 如果此時子View的布局文件中屬性layout_width/height等于match_parent(指定的值),那么子View的Size就是父View的size(在布局文件中給定的值)眼溶。
- 如果此時子View的布局文件中屬性layout_width/height等于wrap_content悠砚,那么子View的大小需要根據(jù)自己的content來確定,但是最終的大小不能超過父View的size堂飞。子View首先通過遍歷調用自身的子View并分別調用他們的measure()方法計算他們的尺寸灌旧,然后再計算自身的大小。而此時傳遞給自身子View的MeasureSpec參數(shù)中mode為AT_MOST绰筛,size暫定為父View的size枢泰。表示的意思就是自身的大小沒有不確切的值,其子View的大小最大為給定的MeasureSpec的大小铝噩,不能超過它(這就是AT_MOST 的意思)衡蚂。
-
如果父View的MeasureSpec的模式是AT_MOST,說明父View的大小是不確定骏庸,最大的大小是MeasureSpec 的size值毛甲,不能超過這個值。
- 如果此時子View的布局文件中屬性layout_width/height等于match_parent具被,由于父View的大小無法確定玻募,所以無法確子View的大小。此時返回的MeasureSpec的mode變成了AT_MOST一姿,size是父View傳遞過來的MeasureSpec的size的值七咧,代表最大不能超過這個值。
- 如果此時子View的布局文件中屬性layout_width/height等于wrap_content叮叹,由于父View的大小無法確定的同時艾栋,在沒有計算出子View的content的大小是無法確定子View的大小。所以傳遞給子View的MeasureSpec的mode是AT_MOST蛉顽,size就是父View中傳遞來的MeasureSpec中的size蝗砾。代表大小暫定這個值,最大不能超過這個值。
- 如果此時子View的布局文件中屬性layout_width/height等于特定的值遥诉,那么傳遞給子View的MeasureSpec的mode是EXACTLY拇泣,size是指定的值。
-
如果父View的MeasureSpec的模式是UPSPECIFIED矮锈,表示沒有任何束縛和約束,不像AT_MOST表示最大只能多大睁蕾,不也像EXACTLY表示父View確定的大小苞笨,子View可以得到任意想要的大小,不受約束子眶。
- 如果此時子View的布局文件中屬性layout_width/height等于match_parent/wrap_content,由于父View的大小沒有任何約束瀑凝,所以子View的大小也就沒有任何約束,此時傳遞給子View的MeasureSpec的mode是UPSPECIFIED臭杰,size是0粤咪。
- 如果此時子View的布局文件中屬性layout_width/height等于特定的值,那么傳遞給子View的MeasureSpec的mode是EXACTLY渴杆,size是指定的值寥枝。
View測量過程 ---> measure()方法
當我們計算好子View的計算標準MeasureSpec時,調用子View的measure()方法磁奖。
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
......
onMeasure(widthMeasureSpec,heightMeasureSpec);
.....
}
源碼中囊拜,View.measure()的方法是final,所以無法覆寫比搭。View的測試工作在onMeasure()方法中完成冠跷。要想自定義View的測量,必須重寫onMeasure()方法身诺。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(
getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
在onMeasure()方法中蜜托,調用了setMeasuredDimension()。這個方法可以簡單理解就是給mMeasuredWidth和mMeasuredHeight設值霉赡,如果這兩個值一旦設置了橄务,那么意味著對于這個View的測量結束了,這個View的寬高已經有測量的結果出來了同廉。
注意:setMeasuredDimension()方法一定要在onMeasure()方法中調用仪糖,否則會拋出異常。
//獲取的是android:minHeight屬性的值或者View背景圖片的大小值
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
//@param size參數(shù)一般表示設置了android:minHeight屬性或者該View背景圖片的大小值
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: //表示該View的大小父視圖未定迫肖,設置為默認值
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
getDefaultSize()的size參數(shù)由getSuggestedMinimumWidth()返回锅劝,這個值由布局文件中的android:minHeight/width和View的background決定尺寸共同決定。這個size就是該view的默認大小蟆湖。默認的測試方法(getDefaultSize())中故爵,當MeasureSpec的mode不是UNSPECIFIED時,都將MeasureSpec中的size返回。
相反在一些View的派生類中(TextView诬垂、Button劲室、ImageView等),都是對onMeasure()方法重寫结窘。如果該View(TextView)的MeasureSpec的mode是AT_MOST很洋,會用其content進行計算后的值和MeasureSpec中的size做比較,取小的一個作為子View(TextView)的尺寸隧枫。
ViewGroup測試過程--->onMeasure()
ViewGroup 類并沒有實現(xiàn)onMeasure喉磁。當ViewGroup沒有確定的值時(wrap_content),需要對其子View進行遍歷官脓,并獲取所有子View的大小后协怒,再來決定自己的大小。
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);
}
}
}
首先調用上述方法卑笨,遍歷所有的子View孕暇,然后調用下面的方法逐個進行測量
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測量完畢后,才去計算ViewGroup的尺寸(無論ViewGroup的layout_width/height屬性是什么)赤兴。
但是測量的工作實在onMeasure()中完成的妖滔,那么來看下FrameLayout是如何實現(xiàn)onMeasure()方法
//FrameLayout 的測量
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
....
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
// 遍歷自己的子View,只要不是GONE的都會參與測量搀缠,measureChildWithMargins方法在最上面
// 的源碼已經講過了铛楣,如果忘了回頭去看看,基本思想就是父View把自己的MeasureSpec
// 傳給子View結合子View自己的LayoutParams 算出子View 的MeasureSpec艺普,然后繼續(xù)往下傳簸州,
// 傳遞葉子節(jié)點,葉子節(jié)點沒有子View歧譬,根據(jù)傳下來的這個MeasureSpec測量自己就好了岸浑。
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
maxWidth = Math.max(maxWidth, child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
maxHeight = Math.max(maxHeight, child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
....
....
}
}
.....
.....
//所有的孩子測量之后,經過一系類的計算之后通過setMeasuredDimension設置自己的寬高瑰步,
//對于FrameLayout 可能用最大的字View的大小矢洲,對于LinearLayout,可能是高度的累加缩焦,
//具體測量的原理去看看源碼读虏。總的來說袁滥,父View是等所有的子View測量結束之后盖桥,再來測量自己。
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT));
....
}
這里通過深度優(yōu)先遍歷來完成所有子View的測量题翻,然后再對自身進行測量揩徊。
注意:在setMeasuredDimension()方法調用之后,我們才能使用getMeasuredWidth()和getMeasuredHeight()來獲取視圖測量出的寬高,以此之前調用這兩個方法得到的值都會是0塑荒。