前言
上一篇DecorView添加到Window過程的源碼分析我們找到了UI繪制流程的起始點,也就是在ViewRootImpl
的performTraversals()
依次執(zhí)行performMeasure
贮懈、performLayout
午乓、performDraw
,那么這個MeasureSpec又是什么呢可缚?它是View的一個內(nèi)部類,從名字我們可以看出刺桃,這是一個測量規(guī)格,它決定了View的測量過程厦瓢。
MeasureSpec
/**
* A MeasureSpec encapsulates the layout requirements passed from parent to child.
* Each MeasureSpec represents a requirement for either the width or the height.
* A MeasureSpec is comprised of a size and a mode. There are three possible
*/
* MeasureSpec封裝從父對象傳遞給孩子的布局要求脉课。
* 每個MeasureSpec表示寬度或高度的要求。
* 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)
}
}
}
從MeasureSpec類的定義我們知道亚兄,它封裝了對子View的布局要求,由尺寸和模式組成采驻,其實MeasureSpec代表一個32位的int值,高2位表示SpecMode匈勋,低30位表示SpecSize,而SpecSize是指在某種SpecMode下的規(guī)格大小礼旅,從源碼我們看出它內(nèi)部定義了很多常量,從api17以后開始采用位運算洽洁,因為位運算的效率最高痘系,我們看下三種模式
-
UNSPECIFIED = 0 << MODE_SHIFT:即: 00
000000 00000000 00000000 00000000
父容器不對子View有任何限制,子View要多大給多大,有系統(tǒng)內(nèi)部調(diào)用饿自,我們不需要研究汰翠,例如ScrollView -
EXACTLY =1<< MODE_SHIFT:即: 01
000000 00000000 00000000 00000000
父容器已經(jīng)測量出子View所需要的大小,即measureSpec中封裝的specsize,對應(yīng)于LayoutParams中的match_parent
和設(shè)置的固定值 -
AT_MOST =2 << MODE_SHIFT:即: 10
000000 00000000 00000000 00000000
父窗口限定了一個最大值給子View即SpecSize,對應(yīng)于LayoutParams中的wrap_content
size & ~MODE_MASK
是獲得SpecSize龄坪,mode & MODE_MASK
是獲得SpecMode,之后再或運算即可得到MeasureSpec「椿剑看下圖具體運算
我們在使用View時是直接設(shè)置LayoutParams,但是在View測量的時候健田,系統(tǒng)會將LayoutParams在父容器的約束下進行相對應(yīng)的MeasureSpec,然后在根據(jù)這個MeasureSpec來確定View的測量后的寬高佛纫,由此可見妓局,MeasureSpec不是LayoutParams唯一決定的,還需要父容器一起來決定呈宇,在進一步?jīng)Q定View的寬高好爬。但是頂級View,也就是上文我們分析到的DecorView和普通的View的MeasureSpec計算有些區(qū)別甥啄,對于DecorView存炮,其MeasureSpec是由屏幕的尺寸和LayoutParams決定的,而DecorView的默認LayoutParams就是match_parent
(在初始化DecorView時可知)蜈漓,對于普通View來說穆桂,其MeasureSpec是由父容器的MeasureSpec和自身的LayoutParams決定。在performTraversals()
方法中有如下一段
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
我們再來看下getRootMeasureSpec
方法的實現(xiàn)
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// Window wants to be an exact size. Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
通過上述代碼迎变,對于DecorView來說就是走第一個case
,對于普通View來說充尉,也就是我們Activity顯示布局的根View是一個ViewGroup,我們再來看下ViewGroup的measureChildWithMargins()
方法
ViewGroup.java#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);
}
可以發(fā)現(xiàn)在調(diào)用子View的measure()
之前會先通過getChildMeasureSpec()
方法來得到子View的MeasureSpec衣形,通過分析驼侠,我們明顯可以發(fā)現(xiàn)子View的MeasureSpec的創(chuàng)建與父容器的MeasureSpec和自身的LayoutParams有關(guān),我們再來看下getChildMeasureSpec()
ViewGroup.java#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 = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
上面代碼主要是先獲得到父容器的SpecMode谆吴,在通過子View自身設(shè)置的LayoutParams來進一步?jīng)Q定子View的SpecMode,最后在通過MeasureSpec.makeMeasureSpec(resultSize, resultMode);
返回子View的MeasureSpec然后再去measure()
.getChildMeasureSpec()這個方法很清晰的展示出子View創(chuàng)建MeasureSpec的過程倒源,下面我們通過表格在說明一下
- 當(dāng)View采用固定寬/高時(即設(shè)置固定的dp/px),不管父容器的MeasureSpec是什么,View的MeasureSpec都是EXACTLY模式句狼,并且大小遵循我們設(shè)置的值笋熬。
- 當(dāng)View的寬/高是match_parents時,如果父容器的模式是精準(zhǔn)模式腻菇,那么View也是精準(zhǔn)模式并且其大小是父容器的剩余空間胳螟;如果父容器是最大模式那么View也是最大模式并且其大小不會超過父容器的剩余空間
- 當(dāng)View的寬/高是wrap_content時,View的MeasureSpec都是AT_MOST模式并且其大小不能超過父容器的剩余空間筹吐。
只要提供父容器的MeasureSpec和子元素的LayoutParams,就可以確定出子元素的MeasureSpec,進一步便可以確定出測量后的大小糖耸。
總結(jié)
好了,至此為止丘薛,MeasureSpec含義模式以及創(chuàng)建規(guī)則就基本說完了嘉竟,下一篇開始我們分析onMeasure()等調(diào)用過程和相關(guān)方法。
推薦
DecorView添加到Window過程的源碼分析
AppCompatActivity的setContentView()源碼分析
Activity的setContentView()源碼分析