最基本的 三個方法
- onMeasure()
- onLayout()
- onDraw()
View在Activity中顯示出來嫂用,要經(jīng)歷測量、布局和繪制三個步驟甘穿,
分別對應(yīng)三個動作:measure、layout和draw呆奕。
測量:onMeasure()決定View的大小衬吆;
布局:onLayout()決定View在ViewGroup中的位置梁钾;
繪制:onDraw()決定繪制這個view
自定義控件分類
- 自定義View: 只需要重寫onMeasure()和onDraw()
- 自定義ViewGroup :則只需要重寫onMeasure()和onLayout()
視圖View主要分為兩類
- 單一視圖 即一個View,如TextView 不包含子View
- 視圖組 即多個View組成的ViewGroup逊抡,如LinearLayout 包含子View
View
View類是Android中各種組件的基類姆泻,如View是ViewGroup基類View表現(xiàn)為顯示在屏幕上的各種視圖
Android中的UI組件都由View、ViewGroup組成冒嫡。
View的構(gòu)造函數(shù):共有4個
//如果view是在Java代碼里面new的拇勃,則調(diào)用第一個構(gòu)造函數(shù)
public CarsonView(context context) {
super(context);
}
//如果view是在.xm1里聲明的孝凌,則調(diào)用第二個構(gòu)造函數(shù)
//自定義屬性是從attributeSet參數(shù)傳進來的
public Carsonview(Context context方咆, AttributeSet attrs) {
super(context, attrs)蟀架;
}
//不會自動調(diào)用
//一般是在第二個構(gòu)造函數(shù)里主動調(diào)用
//如view有style屬性時
public Carsonview(Context context瓣赂, AttributeSet attrs, int defsty1eAttr){super(context片拍, attrs煌集, defSty1eAttr);
}
//API21之后才使用
//不會自動調(diào)用
//一般是在第二個構(gòu)造函數(shù)里主動調(diào)用
//如view有style屬性時
public CarsonView(Context context穆碎, AttributeSet attrs牙勘, int defsty1eAttr, intdefstyleRes) {
super(context所禀, attrs方面, defSty1eAttr, defStyleRes)色徘;
}
ViewGroup 的方法調(diào)用
View的方法調(diào)用
View的層次結(jié)構(gòu)
Android 坐標系
- 屏幕的左上角為坐標原點
- 向右為x軸增大方向
View 位置(坐標)描述
View的位置由4個頂點決定的4個頂點的位置描述分別由4個值決定:
View的位置是相對于父控件而言的
- Top: 子View上邊界到父view上邊界的距離
- Left: 子View左邊界到父view左邊界的距離
- Bottom: 子View下邊距到父View上邊界的距離
- Right: 子View右邊界到父view左邊界的距離
位置獲取方式
- getTop()
- getLeft();
- getRight();
- getBottom();
MotionEvent get()和getRaw()的區(qū)別
//get()觸摸點相對于其所在組件坐標系的坐標event.getX()恭金;
event.getX();
//getRawX():觸摸點相對于屏幕默認坐標系的坐標event.getRawx()褂策;
event.getRawY()横腿;
Android 支持的顏色模式
- ARGB8888 四通道高精度(32位)
- ARGB4444 四通道低精度(16位)
- RGB565 Android屏幕默認模式(16位)
- Alpha8 僅有透明通道(8位)
· 字母表示通道類型;
· 數(shù)值表示該類型用多少位二進制來描述。
· 例子:ARGB8888,表示有四個通道(ARGB);每個對應(yīng)的通道均
用8位來描述斤寂。
A(Alpha) 透明度 (0-255)
R(Red) 紅色 (0-255)
G(Green) 綠色 (0-255)
B(Blue) 藍色 (0-255)
View的繪制流程
View樹的繪制流程是通過ViewRoot去負責繪制的耿焊,ViewRoot這個類的命名有點坑,最初看到這個名字遍搞,翻譯過來是view的根節(jié)點罗侯,但是事實完全不是這樣,ViewRoot其實不是View的根節(jié)點溪猿,它連view節(jié)點都算不上钩杰。
它的主要作用是View樹的管理者纫塌,負責將DecorView和PhoneWindow組合”起來,而View樹的根節(jié)點嚴格意義上來說只有DecorView讲弄;
每個DecorView都有一個ViewRoot與之關(guān)聯(lián)措左,這種關(guān)聯(lián)關(guān)系是由WindowManager去進行管理的;
- 依次調(diào)用如下順序:
WindowManager.addView()
ViewRootImpl.setView
ViewRootImpl.requestLayout
ViewRootImpl.scheduleTraversals
TraversalRunnable.run
ViewRootImpl.doTraversal
ViewRootImpl.performTraversals
- performTraversals 開始計算視圖大小
https://www.cnblogs.com/xyhuangjinfu/p/5435201.html
1避除、系統(tǒng)為什么要有measure過程怎披?
開發(fā)人員在繪制UI的時候,基本都是通過XML布局文件的方式來配置UI瓶摆,而每個View必須要設(shè)置的兩個群屬性就是layout_width和layout_height钳枕,這兩個屬性代表著當前View的尺寸。
這兩個屬性的取值只能為三種類型:
1赏壹、固定的大小鱼炒,比如100dp。
2蝌借、剛好包裹其中的內(nèi)容昔瞧,wrap_content。
3菩佑、想要和父布局一樣大自晰,match_parent / fill_parent。
由于Android希望提供一個更優(yōu)雅的GUI框架稍坯,所以提供了自適應(yīng)的尺寸酬荞,也就是 wrap_content 和 match_parent 。
試想一下瞧哟,那如果這些屬性只允許設(shè)置固定的大小混巧,那么每個View的尺寸在繪制的時候就已經(jīng)確定了,所以可能都不需要measure過程勤揩。但是由于需要滿足自適應(yīng)尺寸的機制括丁,所以需要一個measure過程糠排。
2辛慰、measure過程都干了點什么事?
由于上面提到的自適應(yīng)尺寸的機制负蠕,所以在用自適應(yīng)尺寸來定義View大小的時候蛙埂,View的真實尺寸還不能確定。
但是View尺寸最終需要映射到屏幕上的像素大小遮糖,所以measure過程就是干這件事绣的,把各種尺寸值,經(jīng)過計算,得到具體的像素值被辑。
measure過程會遍歷整棵View樹,然后依次測量每個View真實的尺寸敬惦。
具體是每個ViewGroup會向它內(nèi)部的每個子View發(fā)送measure命令盼理,然后由具體子View的onMeasure()來測量自己的尺寸。
最后測量的結(jié)果保存在View的mMeasuredWidth和mMeasuredHeight中俄删,保存的數(shù)據(jù)單位是像素宏怔。
3、對于自適應(yīng)的尺寸機制畴椰,如何合理的測量一顆View樹臊诊?
系統(tǒng)在遍歷完布局文件后,針對布局文件斜脂,在內(nèi)存中生成對應(yīng)的View樹結(jié)構(gòu)抓艳。
這個時候,整棵View樹中的所有View對象帚戳,都還沒有具體的尺寸玷或,因為measure過程最終是要確定每個View打的準確尺寸,也就是準確的像素值片任。
但是剛開始的時候偏友,View中l(wèi)ayout_width和layout_height兩個屬性的值,都只是自適應(yīng)的尺寸对供,也就是match_parent和wrap_content位他,這兩個值在系統(tǒng)中為負數(shù),所以系統(tǒng)不會把它們當成具體的尺寸值产场。
所以當一個View需要把它內(nèi)部的match_parent或者wrap_content轉(zhuǎn)換成具體的像素值的時候鹅髓,他需要知道兩個信息。
1京景、針對于match_parent迈勋,父布局當前具體像素值是多少,因為match_parent就是子View想要和父布局一樣大醋粟。
2靡菇、針對wrap_content,子View需要根據(jù)當前自己內(nèi)部的content米愿,算出一個合理的能包裹所有內(nèi)容的最小值厦凤。但是如果這個最小值比當前父布局還大,那不行育苟,父布局會告訴你较鼓,我只有這么大,你也不應(yīng)該超過這個尺寸。
由于樹這種數(shù)據(jù)結(jié)構(gòu)的特殊性博烂,我們在研究measure的過程時香椎,可以只研究一個ViewGroup和2個View的簡單場景。大概示意圖如下:
也就是說禽篱,在measure過程中畜伐,ViewGroup會根據(jù)自己當前的狀況,結(jié)合子View的尺寸數(shù)據(jù)躺率,進行一個綜合評定玛界,然后把相關(guān)信息告訴子View,然后子View在onMeasure自己的時候悼吱,
一邊需要考慮到自己的content大小慎框,
一邊還要考慮的父布局的限制信息,
然后綜合評定后添,測量出一個最優(yōu)的結(jié)果笨枯。
4、那么ViewGroup是如何向子View傳遞限制信息的遇西?
談到傳遞限制信息猎醇,那就是MeasureSpec類了,該類貫穿于整個measure過程努溃,用來傳遞父布局對子View尺寸測量的約束信息硫嘶。簡單來說,該類就保存兩類數(shù)據(jù)梧税。
1沦疾、子View當前所在父布局的具體尺寸。
2第队、父布局對子View的限制類型哮塞。
那么限制類型又分為三種類型:
1、UNSPECIFIED凳谦,不限定忆畅。意思就是,子View想要多大尸执,我就可以給你多大家凯,你放心大膽的measure吧,不用管其他的如失。也不用管我傳遞給你的尺寸值绊诲。(其實Android高版本中推薦,只要是這個模式褪贵,尺寸設(shè)置為0)
2掂之、EXACTLY抗俄,精確的。意思就是世舰,根據(jù)我當前的狀況动雹,結(jié)合你指定的尺寸參數(shù)來考慮,你就應(yīng)該是這個尺寸跟压,具體大小在MeasureSpec的尺寸屬性中胰蝠,自己去查看吧,你也不要管你的content有多大了裆馒,就用這個尺寸吧。
3丐怯、AT_MOST喷好,最多的。意思就是读跷,根據(jù)我當前的情況梗搅,結(jié)合你指定的尺寸參數(shù)來考慮,在不超過我給你限定的尺寸的前提下效览,你測量一個恰好能包裹你內(nèi)容的尺寸就可以了无切。
ScrollView內(nèi)部嵌套ListView的問題
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
}
- ListView的onMeasure
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final View child = obtainView(0, mIsScrap);
childHeight = child.getMeasuredHeight();
if (heightMode == MeasureSpec.UNSPECIFIED) {
heightSize = mListPadding.top + mListPadding.bottom + childHeight + getVerticalFadingEdgeLength() * 2;
if (heightMode == MeasureSpec.AT_MOST) {
// TODO: after first layout we should maybe start at the first visible position, not 0
heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);
}
setMeasuredDimension(widthSize, heightSize);
mWidthMeasureSpec = widthMeasureSpec;
}
}
當MeasureSpec mode為UNSPECIFIED的時候,只測量第一個item打的高度丐枉,跟問題描述相符哆键,所以我們猜測可能是因為ScrollView傳遞了一個UNSPECIFIED限制給ListView。
- ScrollView的onMeasure代碼:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
調(diào)用了父類的onMeasure:
看看FrameLayout的onMeasure:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
}
}
}
調(diào)用了measureChildWithMargins瘦锹,但是因為ScrollView覆寫了該方法籍嘹,所以看看ScrollView的measureChildWithMargins方法:
@Override
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final int childHeightMeasureSpec =
MeasureSpec.makeSafeMeasureSpec(MeasureSpec.getSize(parentHeightMeasureSpec),
MeasureSpec.UNSPECIFIED);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
果然,它向ListView的onMeasure傳遞了一個UNSPECIFIED的限制弯院。
為什么呢辱士,想想,因為ScrollView听绳,本來就是可以在豎直方向滾動的布局颂碘,所以,它對它所有的子View的高度就是UNSPECIFIED椅挣,意思就是头岔,不限制子View有多高,因為我本來就是需要豎直滑動的鼠证,它的本意就是如此切油,所以它對子View高度不做任何限制。
為什么這種解決方法可以解決這個問題名惩?
看看ListView的onMeasure:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final View child = obtainView(0, mIsScrap);
childHeight = child.getMeasuredHeight();
if (heightMode == MeasureSpec.UNSPECIFIED) {
heightSize = mListPadding.top + mListPadding.bottom + childHeight + getVerticalFadingEdgeLength() * 2;
if (heightMode == MeasureSpec.AT_MOST) {
// TODO: after first layout we should maybe start at the first visible position, not 0
heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);
}
setMeasuredDimension(widthSize, heightSize);
mWidthMeasureSpec = widthMeasureSpec;
}
}
只要讓heightMode == MeasureSpec.AT_MOST澎胡,它就會測量它的完整高度,所以第一個數(shù)據(jù),限制mode的值就確定下來了攻谁。第二個數(shù)據(jù)就是尺寸上限稚伍,如果給個200,那么當ListView數(shù)據(jù)過多的時候戚宦,該ListView最大高度就是200了个曙,還是不能完全顯示內(nèi)容,怎么辦受楼?那么就給個最大值吧垦搬,最大值是多少呢,Integer.MAX_VALUE?
先看一下MeasureSpec的代碼說明:
private static final int MODE_SHIFT = 30;
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;
他用最高兩位存儲mode艳汽,用其他剩余未存儲size猴贰。所以Integer.MAX_VALUE >> 2,就是限制信息所能攜帶的最大尺寸數(shù)據(jù)河狐。所以最后就需要用這兩個值做成一個限制信息米绕,傳遞給ListView的height維度。
也就是如下代碼:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
}
layout
https://www.cnblogs.com/xyhuangjinfu/p/5435253.html
View框架的工作流程為:測量每個View大胁鲆铡(measure)-->把每個View放置到相應(yīng)的位置(layout)-->繪制每個View(draw)栅干。
1、系統(tǒng)為什么要有l(wèi)ayout過程捐祠?
View框架在經(jīng)過第一步的measure過程后碱鳞,成功計算了每一個View的尺寸。但是要成功的把View繪制到屏幕上踱蛀,只有View的尺寸還不行劫笙,還需要準確的知道該View應(yīng)該被繪制到什么位置。
除此之外星岗,對一個ViewGroup而言填大,還需要根據(jù)自己特定的layout規(guī)則,來正確的計算出子View的繪制位置俏橘,已達到正確的layout目的允华。這也就是layout過程的職責。
該位置是View相對于父布局坐標系的相對位置寥掐,而不是以屏幕坐標系為準的絕對位置靴寂。
這樣更容易保持樹型結(jié)構(gòu)的遞歸性和內(nèi)部自治性。
而View的位置召耘,可以無限大百炬,超出當前ViewGroup的可視范圍,這也是通過改變View位置而實現(xiàn)滑動效果的原理污它。
2剖踊、layout過程都干了點什么事庶弃?
由于View是以樹結(jié)構(gòu)進行存儲,所以典型的數(shù)據(jù)操作就是遞歸操作德澈,所以歇攻,View框架中,采用了內(nèi)部自治的layout過程梆造。
每個葉子節(jié)點根據(jù)父節(jié)點傳遞過來的位置信息缴守,設(shè)置自己的位置數(shù)據(jù)镇辉,每個非葉子節(jié)點屡穗,除了負責根據(jù)父節(jié)點傳遞過來的位置信息忽肛,設(shè)置自己的位置數(shù)據(jù)外(如果有父節(jié)點的話)源祈,還需要根據(jù)自己內(nèi)部的layout規(guī)則(比如垂直排布等)手销,計算出每一個子節(jié)點的位置信息锋拖,然后向子節(jié)點傳遞layout過程。
對于ViewGroup,除了根據(jù)自己的parent傳遞的位置信息,來設(shè)置自己的位置之外颂跨,還需要根據(jù)自己的layout規(guī)則,為每一個子View計算出準確的位置(相對于子View的父布局的位置)。
對于View,根據(jù)自己的parent傳遞的位置信息,來設(shè)置自己的位置。
View對象的位置信息,在內(nèi)部是以4個成員變量的保存的,分別是mLeft匕得、mRight、mTop考阱、mBottom。他們的含義如圖所示。
Android View框架的draw機制
1申鱼、系統(tǒng)為什么要有draw過程?
View框架在經(jīng)過了measure過程和layout過程之后,就已經(jīng)確定了每一個View的尺寸和位置。那么接下來,也是一個重要的過程,就是draw過程,draw過程是用來繪制View的過程盒音,它的作用就是使用graphic框架提供的各種繪制功能譬圣,繪制出當前View想要的樣子。
2、draw過程都干了點什么事?
View框架中谴仙,draw過程主要是繪制View的外觀。ViewGroup除了負責繪制自己之外掀虎,還需要負責繪制所有的子View春霍。而不含子View的View對象莲趣,就負責繪制自己就可以了潘鲫。
draw過程的主要流程如下:
1、繪制 backgroud(drawBackground)
2振定、如果需要的話帝簇,保存canvas的layer胧后,來準備fading(不是必要的步驟)
3眶痰、繪制view的content(onDraw方法)
4七婴、繪制children(dispatchDraw方法)
5撩笆、如果需要的話,繪制fading edges嚼锄,然后還原layer(不是必要的步驟)
6罩驻、繪制裝飾器娶桦、比如scrollBar(onDrawForeground)
LayoutParams
LayoutParams翻譯過來就是布局參數(shù)扁藕,子View通過LayoutParams告訴父容器(ViewGroup)應(yīng)該如何放置自己。
從這個定義中也可以看出來LayoutParams與ViewGroup是息息相關(guān)的,因此脫離ViewGroup談LayoutParams是沒有意義的干签。
事實上拆撼,每個ViewGroup的子類都有自己對應(yīng)的LayoutParams類容劳,典型的如LinearLayout.LayoutParams和FrameLayout.LayoutParams等,可以看出來LayoutParams都是對應(yīng)ViewGroup子類的內(nèi)部類
MarginLayoutParams
MarginLayoutParams是和外間距有關(guān)的闸度。
事實也確實如此竭贩,和LayoutParams相比,MarginLayoutParams只是增加了對上下左右外間距的支持莺禁。
實際上大部分LayoutParams的實現(xiàn)類都是繼承自MarginLayoutParams留量,因為基本所有的父容器都是支持子View設(shè)置外間距的屬性優(yōu)先級問題MarginLayoutParams主要就是增加了上下左右4種外間距。
在構(gòu)造方法中哟冬,先是獲取了margin屬性楼熄;
如果該值不合法,就獲取horizontalMargin浩峡;如果該值不合法可岂,再去獲取leftMargin和rightMargin屬性 (verticalMargin、topMargin和bottomMargin同理)翰灾。
我們可以據(jù)此總結(jié)出這幾種屬性的優(yōu)先級
margin > horizontalMargin和verticalMargin > leftMargin和RightMargin青柄、topMargin和bottomMargin
屬性覆蓋問題優(yōu)先級更高的屬性會覆蓋掉優(yōu)先級較低的屬性。
此外预侯,還要注意一下這幾種屬性上的注釋
Call {@link ViewGroup#setLayoutParams(LayoutParams)}after reassigning a new value
LayoutParams與View如何建立聯(lián)系
- 在XML中定義View
- 在Java代碼中直接生成View對應(yīng)的實例對象
自定義LayoutParams
- 創(chuàng)建自定義屬性
<resources>
<declare-styleable name="xxxViewGroup_Layout">
<致开!一自定義的屬性 一>
<attr name="layout_simple_attr" format="integer"/><!一 使用系統(tǒng)預(yù)置的屬性一>
<attr name="android:layout_gravity"/>
</declare-styleable>
</resources>
- 繼承MarginLayout
public static class LayoutParams extends ViewGroup.MarginLayoutParams {
public int simpleAttr;
public int gravity;
public LayoutParams(Context c, Attributeset attrs) {
super(c, attrs);
TypedArray typedArray = c.obtainstyledAttributes(attrs,
R.styleable.SimpleviewGroup_Layout);
simpleAttr =
typedArray.getInteger(R.styleable.SimpleviewGroup_Layout_layout_simple_attr, 0);
gravity=typedArray.getInteger(R.styleable.SimpleviewGroup_Layout_android_layout_gravity,-1);
typedArray.recycle();
}
public Layoutparams(int width, int height) {
super(width, height);
}
public LayoutParams(MarginLayoutParams source) {super(source);
}
public LayoutParams(ViewGroup.LayoutParams source) {super(source);
}
- 重寫與LayoutParams的方法
//檢查LayoutParams是否合法
@override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof SimpleviewGroup.LayoutParams萎馅;
}
//生成默認的LayoutParams
@override
protected ViewGroup.LayoutParams generateDefaultLayoutParamsO {
return new simpleviewGroup. LayoutParams(LayoutParams.MATCH_PARENT双戳,LayoutParams.WRAP_CONTENT);
}
//對傳入的LayoutParams進行轉(zhuǎn)化
@override
protected viewGroup. LayoutParams generatelayoutParams (viewGroup.LayoutParams p)return new simpleviewGroup.LayoutParams(p)糜芳;
}
//對傳入的Layoutparams進行轉(zhuǎn)化
@override
public viewGroup.LayoutParams generatelayoutParams(Attributeset attrs) {
return new SimpleviewGroup.LayoutParams(getcontextO飒货, attrs);
}
MeasureSpec
- widthMeasureSpec
- heightMeasureSpec
specMode
UNSPECIFIED 父控件不對你有任何限制峭竣,你想要多大給你多大塘辅,想上天就上天。
這種情況一般用于系統(tǒng)內(nèi)部皆撩,表示一種測量狀態(tài)扣墩。(這個模式主要用于系統(tǒng)內(nèi)部多次Measure的情形哲银,并不是真的說你想要多大最后就真有多大)EXACTLY 父控件已經(jīng)知道你所需的精確大小,你的最終大小應(yīng)該就是這么大呻惕。
AT_MOST你的大小不能大于父控件給你指定的size荆责,但具體是多少,得看你自己的實現(xiàn)亚脆。
specSize
在某種specMode下的參考尺寸
MeasureSpecs 的意義
通過將 SpecMode 和 SpecSize 打包成一個int值可以避免過多的對象內(nèi)存分配做院,為了方便操作,其提供了打包/解包方法
MeasureSpecs 值的確定
MeasureSpec (測量規(guī)格濒持,32位的int值) =
Mode (測量模式键耕,高2位即31顶燕,32位)+
size(具體測量大小滑凉,低30位)
子View的MeasureSpec值是
根據(jù)子View的布局參數(shù)(LayoutParams) 和父容器的MeasureSpec值計算得來的鲫竞,具體計算邏輯封裝在getChildMeasureSpec()里斋竞。
/**
目標是將父控件的測量規(guī)格和child view的布局參數(shù)Layoutparams相結(jié)合涮坐,得到一個最可能符合條件的child view的測量規(guī)格颖变。
@param spec父控件的測量規(guī)格
@param padding 父控件里已經(jīng)占用的大小
@param childdimension child view布局LayoutParams里的尺寸
@return child view 的測量規(guī)格
*/
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://父控件是精確的
//如果child的布局參數(shù)有固定值法牲,比如"layout_width" = "100dp"
//那么顯然child的測量規(guī)格也可以確定下來了奢浑,測量大小就是100dp虏杰,測量模式也是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.
//如果child的布局參數(shù)是"wrap_content"讥蟆,也就是想要根據(jù)自己的邏輯決定自己大小,
//比如TextView根據(jù)設(shè)置的字符串大小來決定自己的大小
//那就自己決定唄纺阔,不過你的大小肯定不能大于父控件的大小嘛
//所以測量模式就是AT_MOST瘸彤,測量大小就是父控件的size
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);
}
注: parentSize 為父容器中目前可使用的大小
針對上表,這里再做一下具體的說明
對于應(yīng)用層View笛钝,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams來共同決定
對于不同的父容器和view本身不同的LayoutParams质况, view就可以有多種MeasureSpec。
1.當view采用固定寬高的時候玻靡,不管父容器的MeasureSpec是什么结榄,view的MeasureSpec都是精確模式并且其大小遵循Layoutparams中的大小囤捻;
2.當view的寬高是match_parent時臼朗,這個時候如果父容器的模式是精準模式,那么view也是精準模式并且其大小是父容器的剩余空間蝎土,如果父容器是最大模式视哑,那么view也是最大模式并且其大小不會超過父容器的剩余空間;
3.當view的寬高是wrap_content時誊涯,不管父容器的模式是精準還是最大化挡毅,view的模式總是最大化并且大小不能超過父容器的剩余空間。
- Unspecified模式暴构,這個模式主要用于系統(tǒng)內(nèi)部多次measure的情況下跪呈,一般來說段磨,我們不需要關(guān)注此模式(這里注意自定義View放到ScrollView的情況需要處理)。