1. 簡介
-
View
的繪制過程分為三部分:measure
逃默、layout
呢堰、draw
。
measure
用來測量View的寬和高卵凑。
layout
用來計算View的位置庆聘。
draw
用來繪制View。
- 本章主要對
measure
過程進行詳細的分析勺卢。 - 本文源碼基于android 27。
2. measure的始點
measure
是從ViewRootImpl
的performTraversals()
方法開始的:
2.1 ViewRootImpl的performTraversals
private void performTraversals() {
//...
//獲得view寬高的測量規(guī)格象对,mWidth和mHeight表示窗口的寬高黑忱,lp.widthhe和lp.height表示DecorView根布局寬和高
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);//執(zhí)行測量
//...
}
首先會獲取view寬高的測量規(guī)格,測量規(guī)格在下一節(jié)會詳細講述勒魔,然后就是調(diào)用performMeasure()
了:
2.2 ViewRootImpl的performMeasure
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
//...
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
//...
}
performMeasure()
中就是調(diào)用View
的measure()
方法開始進行測量甫煞。
3.MeasureSpec
了解measure
的過程時,我們須先了解MeasureSpec
冠绢。MeasureSpec
抚吠,顧名思義,就是測量規(guī)格弟胀,其決定了一個View的寬和高楷力。
3.1 MeasureSpec組成
MeasureSpec
代表一個32位的int值,前2位代表SpecMode
孵户,后30位代表SpecSize
萧朝。其中:SpecMode
代表測量的模式,SpecSize
值在某種測量模式下的規(guī)格大小夏哭。來張圖解說明:
3.2 SpecMode測量模式
測量模式分為三種UNSPECIFIED
检柬、EXACTLY
、AT_MOST
竖配,其具體說明如下表所示:
測量模式 | 說明 | 應(yīng)用場景 |
---|---|---|
UNSPECIFIED (未指定) | 父容器沒有對當(dāng)前View有任何限制何址,當(dāng)前View可以任意取尺寸 | 系統(tǒng)內(nèi)部 |
EXACTLY(精確) | 父容器已經(jīng)確定當(dāng)前View的大小,無論View想要多大都會在這范圍內(nèi) | match_parent 和 具體數(shù)值 |
AT_MOST(最多) | 當(dāng)前View不能超過父容器規(guī)格大小进胯,具體數(shù)值由view去決定 | wrap-content |
3.3 MeasureSpec源碼分析
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;//模式移位數(shù)
private static final int MODE_MASK = 0x3 << MODE_SHIFT;//模式掩碼
//UNSPECIFIED模式:父容器沒有對當(dāng)前View有任何限制用爪,當(dāng)前View可以任意取尺寸
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
//EXACTLY模式:父容器已經(jīng)確定當(dāng)前View的大小,無論View想要多大都會在這范圍內(nèi)
public static final int EXACTLY = 1 << MODE_SHIFT;
//AT_MOST模式:當(dāng)前View不能超過父容器規(guī)格大小龄减,具體數(shù)值由view去決定
public static final int AT_MOST = 2 << MODE_SHIFT;
//根據(jù)提供的size和mode得到一個測量規(guī)格
public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
@MeasureSpecMode int mode) {
//sUseBrokenMakeMeasureSpec = targetSdkVersion <= Build.VERSION_CODES.JELLY_BEAN_MR1;
//即targetSdkVersion<=17時项钮,size與mode是直接相加的;>17則進行位運算
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);//位運算
}
}
//根據(jù)提供的size和mode得到一個測量規(guī)格
public static int makeSafeMeasureSpec(int size, int mode) {
if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) {
return 0;
}
return makeMeasureSpec(size, mode);
}
//獲取測量模式
@MeasureSpecMode
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
//獲取測量大小
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
}
可以看到,MeasureSpec
類還是挺簡單的希停,MeasureSpec
類通過將mode
和size
打包成一個32位int值來減少了對象內(nèi)存分配烁巫,并提供了打包和解包的方法。
3.4 確定MeasureSpec值
在measure
過程中宠能,系統(tǒng)會將View
的LayoutParams
和父容器所施加的規(guī)則轉(zhuǎn)換成對應(yīng)的MeasureSpec
亚隙,然后在onMeasure()
方法中根據(jù)這個MeasureSpec
來確定View
的測量寬高。父容器所施加的規(guī)則對于DecorView
與普通View
是不同的违崇,我們分開來看阿弃。
3.4.1 確定DecorView的MeasureSpec值
DecorView
诊霹,作為頂級的View
,我們平時setContentView()
所設(shè)置的布局可能只是DecorView
其中的一部分渣淳,如下圖所示:
關(guān)于DecorView
脾还,可以看看我的另一篇文章:從setContentView揭開DecorView。
3.4.1.1 ViewRootImpl的PerformTraveals
在ViewRootImpl
的PerformTraveals()
方法中會獲得DecorView
的MeasureSpec
值:
private void performTraversals() {
//...
//獲得view寬高的測量規(guī)格入愧,mWidth和mHeight表示窗口的寬高鄙漏,lp.widthhe和lp.height表示DecorView根布局寬和高
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);//執(zhí)行測量
//...
}
3.4.1.2 ViewRootImpl的getRootMeasureSpec
我們再來看看getRootMeasureSpec()
方法:
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
//如果View的布局參數(shù)為MATCH_PARENT,則其為精確模式棺蛛,大小為窗口的大小
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
//如果View的布局參數(shù)為WRAP_CONTENT怔蚌,則其為最多模式,大小不定旁赊,但不能超過窗口的大小
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// Window wants to be an exact size. Force root view to be that size.
//如果View的布局參數(shù)為準(zhǔn)確的數(shù)值桦踊,如100dp等,則其為精確模式终畅,大小為View設(shè)定的數(shù)值
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
可以看到:DecorView的MeasureSpec值是由窗口大小及其布局參數(shù)來決定的籍胯。
來張表格總結(jié):
布局參數(shù) | LayoutParams.MATCH_PARENT | LayoutParams.WRAP_CONTENT | 準(zhǔn)確數(shù)值 |
---|---|---|---|
MeasureSpec值 | EXACTLY | ||
windowSize(窗口大小) | AT_MOST | ||
windowSize(不能超過窗口大猩搿) | EXACTLY | ||
rootDimension(準(zhǔn)確數(shù)值) |
3.4.2 確定普通View的MeasureSpec值
普通View
的MeasureSpec
值通過getChildMeasureSpec()
方法來獲取芒炼,getChildMeasureSpec()
位于ViewGroup
中:
3.4.2.1 ViewGroup的getChildMeasureSpec
/**
*
* @param spec 父容器的測量規(guī)格(MeasureSpec)
* @param padding 子view的內(nèi)邊距和外邊距(padding,margin)
* @param childDimension 子view的布局參數(shù)(寬/高)
* @return 子view的測量規(guī)格(MeasureSpec)
*/
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
//父容器的測量模式
int specMode = MeasureSpec.getMode(spec);
//父容器的測量大小
int specSize = MeasureSpec.getSize(spec);
//子view大小 = 父容器大小-子view邊距(子view只在某些地方地方用到這個值,具體看下面的代碼)
int size = Math.max(0, specSize - padding);
//初始化子view的大小和模式(最終的結(jié)果需要下面代碼計算出來)
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
case MeasureSpec.EXACTLY://當(dāng)父容器模式為EXACTLY時
if (childDimension >= 0) {// 當(dāng)子view的LayoutParams>0术徊,即有確切的值
//子view大小為子自身所賦的值本刽,模式大小為EXACTLY
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {// 當(dāng)子view的LayoutParams為MATCH_PARENT時(-1)
//子view大小為父容器剩余大小,模式為EXACTLY
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {// 當(dāng)子view的LayoutParams為WRAP_CONTENT時(-2)
//子view決定自己的大小赠涮,但最大不能超過父容器剩余大小子寓,模式為AT_MOST
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
case MeasureSpec.AT_MOST: // 當(dāng)父容器的模式為AT_MOST時
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
case MeasureSpec.UNSPECIFIED:// 當(dāng)父容器的模式為UNSPECIFIED時
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
//這里的sUseZeroUnspecifiedMeasureSpec值為targetSdkVersion < Build.VERSION_CODES.M;即<23為true,否則為false.
//大于等于23時會將size作為提示值,否則為0
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
可以看到:普通View的MeasureSpec值是由父容器的MeasureSpec及其布局參數(shù)來決定的笋除。
來張表格總結(jié):
| 父容器測量模式(右側(cè))
子View布局參數(shù)(下側(cè)) | EXACTLY(精確) | AT_MOST(最多) | UNSPECIFIED(未指定) |
---|---|---|---|
具體dp/px | EXACTLY | ||
childDimension(子View尺寸) | EXACTLY | ||
childDimension(子View尺寸) | EXACTLY | ||
childDimension(子View尺寸) | |||
MATCH_PARENT | EXACTLY | ||
parentSize(父容器剩余大行庇选) | AT_MOST | ||
parentSize(不能超過父容器剩余大小) | UNSPECIFIED |
targetSdkVersion<23 ? 0 : size;
(大于等于23時會將size作為提示值) |
| WRAP_CONTENT | AT_MOST
parentSize(不能超過父容器剩余大欣) | AT_MOST
parentSize(不能超過父容器剩余大邢势痢)) | UNSPECIFIED
targetSdkVersion<23 ? 0 : size;
(大于等于23時會將size作為提示值) |
對上表作一些規(guī)律總結(jié):
- 以子View布局參數(shù)為標(biāo)準(zhǔn),橫向觀察:
- 當(dāng)子View使用具體的dp/px時国拇,無論父容器的測量模式是什么洛史,子View的測量模式都是EXACTLY且大小等于設(shè)置的具體數(shù)值;
- 當(dāng)子View使用match_parent時酱吝,子View的測量模式與父容器的測量模式保存一致也殖。
另外,UNSPECIFIED
模式一般很少用到务热,可以不用過多關(guān)注忆嗜。
4. measure過程分析
measure
過程根據(jù)View
的類型可以分為兩種情況:
- 測量單一
View
時己儒,只需測量自身; - 測量
ViewGroup
時捆毫,需要對ViewGroup
中包含的所有子View都進行測量闪湾。
我們對這兩種情況分別進行分析。
4.1 單一View的measure過程
單一View
的measure
過程由View
的measure()
來實現(xiàn):
4.1.1 View的measure
/**
*
* @param widthMeasureSpec view的寬測量規(guī)格
* @param heightMeasureSpec view的高測量規(guī)格
*/
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
//...
onMeasure(widthMeasureSpec, heightMeasureSpec);
//...
}
這里傳入的widthMeasureSpec/heightMeasureSpec
是當(dāng)前View的測量規(guī)格冻璃,通過getChildMeasureSpec()來獲得的响谓。具體細節(jié)可以看上面的分析。
measure()
方法為final
類型省艳,子類不能對其進行重寫。measure()
方法中主要就是對基本測量邏輯作判斷嫁审,最終會調(diào)用onMeasure()
方法跋炕。
4.1.2 View的onMeasure
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
上面就一行代碼,看起來很簡單律适,但是包含了三個方法:getSuggestedMinimumWidth()/getSuggestedMinimumHeight()
辐烂、getDefaultSize()
、setMeasuredDimension()
捂贿,我們分開來看:
4.1.3 View的getSuggestedMinimumWidth
getSuggestedMinimumHeight()
與getSuggestedMinimumWidth()
同理纠修,我們這里只看下getSuggestedMinimumWidth()
:
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
如果View
沒有設(shè)置背景,那么View
的寬度為mMinWidth
厂僧,而mMinWidth
就是android:minWidth
屬性所指定的值,若android:minWidth
沒有指定扣草,則默認為0;
如果View
設(shè)置了背景,那么View的寬度為mMinWidth
和mBackground.getMinimumWidth()
中的最大值颜屠。
那么mBackground.getMinimumWidth()
的值是多少呢辰妙,我們來看看:
4.1.4 Drawable的getMinimumWidth
public int getMinimumWidth() {
final int intrinsicWidth = getIntrinsicWidth();//返回背景圖Drawable的原始寬度
return intrinsicWidth > 0 ? intrinsicWidth : 0;
}
可以看出:mBackground.getMinimumWidth()
返回的是背景圖Drawable
的原始寬度,若無原始寬度則返回0甫窟。
那么Drawable
什么情況下有原始寬度密浑?如:ShapeDrawable
沒有,但BitmapDrawable
有粗井。
4.1.5 View的getDefaultSize
我們再來看看View
的getDefaultSize()
方法:
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);//獲取測量模式
int specSize = MeasureSpec.getSize(measureSpec);//獲取測量大小
switch (specMode) {
//測量模式為UNSPECIFIED時尔破,View的測量大小為默認大小,即getSuggestedMinimumWidth()/getSuggestedMinimumHeight()返回的結(jié)果浇衬。
case MeasureSpec.UNSPECIFIED:
result = size;
break;
//測量模式為AT_MOST懒构、EXACTLY時,View的測量大小為測量規(guī)格中的測量大小
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
4.1.6 View的setMeasuredDimension
再來看setMeasuredDimension()
:
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
//...
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
setMeasuredDimension()
里會調(diào)用setMeasuredDimensionRaw()
:
4.1.7 View的setMeasuredDimensionRaw
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
setMeasuredDimensionRaw()
中就是對測量出的寬高進行保存径玖。
到這里痴脾,單一View
的measure
過程就完成了。
4.1.8 時序圖
最后梳星,來張相關(guān)的時序圖:
4.1.9 流程圖
以及來張測量過程細節(jié)流程圖:
4.2 ViewGroup的measure過程
由于ViewGroup
包含了子View
赞赖,因此其measure
過程是通過遍歷所有的子View
并對子View
測量滚朵,然后合并所有子View
的尺寸,最后得到ViewGroup
的測量值前域。
如上圖所示辕近,要執(zhí)行ViewGroup
的measure
,首先從頂部的ViewGroup
開始遍歷匿垄,自上而下遞歸執(zhí)行子View
的measure
移宅。
ViewGroup
的measure
過程是從measureChildren()
開始的。
4.2.1 ViewGroup的measureChildren
/**
*
* @param widthMeasureSpec ViewGroup的寬測量規(guī)格
* @param heightMeasureSpec ViewGroup的高測量規(guī)格
*/
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < size; ++i) {//遍歷子View
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
再來看measureChild()
:
4.2.2 ViewGroup的measureChild
protected void measureChild(View child, int parentWidthMeasureSpec,int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();//獲得子view的布局參數(shù)
//獲得子view的寬/高測量規(guī)格
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
//子view開始測量
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
getChildMeasureSpec()
的代碼請看上面的分析椿疗。
4.2.3 View的measure
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
//...
onMeasure(widthMeasureSpec, heightMeasureSpec);
//...
}
這里的measure
跟上面單一View
的measure
方法是一樣的漏峰。
4.2.4 ViewGroup的onMeasure
如果子View
是一個單一的View
的話,則直接調(diào)用View
的onMeasure()
方法届榄,跟上面單一View
的measure
過程是一樣大的;
如果子View
是ViewGroup
的話浅乔,理論上應(yīng)該是應(yīng)該調(diào)用ViewGroup
的onMeasure()
方法的。
但實際上铝条,ViewGroup
中是沒有onMeasure()
這個方法的靖苇,ViewGroup
作為一個抽象類,需要其子類去實現(xiàn)測量過程的onMeasure()
方法班缰,比如LinearLayout
贤壁、RelativeLayout
等。因為LinearLayout
埠忘、RelativeLayout
這些布局具體不同的特性脾拆,其測量細節(jié)各有不同,無法進行統(tǒng)一的實現(xiàn)给梅,因此需要其子類去進行具體的實現(xiàn)假丧。因此我們自定義ViewGroup
時需要重寫onMeasure()
方法。
我們這里看下LinearLayout
的onMeasure()
實現(xiàn):
4.2.5 LinearLayout的onMeasure
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {//方向判斷
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
LinearLayout
會區(qū)分方向來進行不同的測量方法动羽,我們主要看下豎向的測量measureVertical()
包帚,橫向的原理差不多這里就不看了。
4.2.6 LinearLayout的measureVertical
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
//獲取子view數(shù)量
final int count = getVirtualChildCount();
//獲取LinearLayout的寬/高測量模式
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
//...
for (int i = 0; i < count; ++i) {//遍歷子View
final View child = getVirtualChildAt(i);
//..
// 最終會調(diào)用子View的measure()运吓,計算出子View的大小
measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
heightMeasureSpec, usedHeight);
// 獲取子View的測量高度
final int childHeight = child.getMeasuredHeight();
//..
final int totalLength = mTotalLength;
// 將子View的測量高度以及Margin加到LinearLayout的總高度上
mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
lp.bottomMargin + getNextLocationOffset(child));
}
//..
// 加上LinearLayout設(shè)置的Padding
mTotalLength += mPaddingTop + mPaddingBottom;
int heightSize = mTotalLength;
heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
//..
//保存測量值
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),heightSizeAndState);
}
最后會調(diào)用setMeasuredDimension()
來保存測量好的值渴邦,至此ViewGroup
的測量過程就完成了。
4.2.7 時序圖
最后拘哨,來張相關(guān)的時序圖:
4.2.8 流程圖
以及來張ViewGroup
測量過程細節(jié)流程圖:
5. 自定義View
5.1 自定義單一View
自定義單一View
谋梭,可以直接使用View中默認定義的onMeasure()
方法。如果有時需要更精準(zhǔn)的測量倦青,可以重寫onMeasure()
瓮床。
5.2 自定義ViewGroup
由于ViewGroup
沒實現(xiàn)onMeasure()
,所以自定義ViewGroup
需要重寫onMeasure()
方法。這里給個簡單的模板:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//定義存放測量后的View的寬和高的變量
int widthMeasure;
int heightMeasure;
//實現(xiàn)測量方法以及具體的測量細節(jié)
measureMethod();
//必不可少 保存測量后View寬和高的值
setMeasuredDimension(widthMeasure, heightMeasure);
}
6. 其他問題
6.1 獲取View的測量寬高
我們可以使用getMeasuredWidth()
和getMeasuredHeight()
來獲取View
測量出的寬高隘庄。
但是從上面的代碼分析可以看到踢步,在調(diào)用setMeasuredDimension()
方法之后,我們才能使用getMeasuredWidth()
和getMeasuredHeight()
來獲取View測量出的寬高丑掺,在這之前去調(diào)用這兩個方法得到的值都會是0获印。
結(jié)合Activity的啟動過程以及生命周期,在onCreate()
和onResume()
中街州,都還未開始進行測量的操作兼丰,所以這時候調(diào)用getMeasuredWidth()
和getMeasuredHeight()
的值都會是0。開始測量的相關(guān)代碼最早可以追涉到ActivityThread
的handleResumeActivity()
方法唆缴,可以看下這篇文章的介紹:Activity啟動過程詳解鳍征。
另外,在某些情況下面徽,需要多次測量才能確定View
最終寬高蟆技;因此這種情況下獲得的值可能是不準(zhǔn)確的,建議在layout
過程中onLayout()
去獲取最終寬高斗忌。
6.2 自定義View時WRAP_CONTENT不生效的問題
從上面getDefaultSize()
方法的代碼可以看到,無論是AT_MOST
模式還是EXACTLY
模式旺聚,View
的測量大小都為測量規(guī)格中的測量大小织阳,所以我們就會看到自定義View
使用WRAP_CONTENT
會起到跟MATCH_PARENT
的效果。
為了解決這個問題需要在LayoutParams
屬性為WRAP_CONTENT
時指定一下默認的寬和高砰粹,如:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width=100;//設(shè)置一個默認寬
int height=100;//設(shè)置一個默認高
if (getLayoutParams().width == ViewGroup.LayoutParams.WRAP_CONTENT && getLayoutParams().height == ViewGroup.LayoutParams.WRAP_CONTENT) {
setMeasuredDimension(width, height);//寬高都為WRAP_CONTENT都設(shè)置為默認寬高
} else if (getLayoutParams().width == ViewGroup.LayoutParams.WRAP_CONTENT) {
setMeasuredDimension(width, heightSize);//只有寬為WRAP_CONTENT時唧躲,只設(shè)置默認寬
} else if (getLayoutParams().height == ViewGroup.LayoutParams.WRAP_CONTENT) {
setMeasuredDimension(widthSize, height);//只有高為WRAP_CONTENT時,只設(shè)置默認高
}
}
這里的默認值請根據(jù)實際情況去確認碱璃。
像系統(tǒng)的TextView
這些都支持WRAP_CONTENT
弄痹,可以去看下其onMeasure()
的源碼實現(xiàn)。
如果我們自定義的View
只需要設(shè)置指定的具體寬高或者MATCH_PARENT
嵌器,可以不用重寫onMeasure()
方法肛真。