Android視圖的繪制流程(上) View的測(cè)量
綜述
View的繪制流程可以分為三大步,它們分別是measure,layout和draw過(guò)程。measure表示View的測(cè)量過(guò)程,用于測(cè)量View的寬度和高度;layout用于確定View在父容器的位置;draw則是負(fù)責(zé)將View繪制到屏幕中。下面主要來(lái)看一下View的Measure過(guò)程。
測(cè)量過(guò)程
View的繪制流程是從ViewRoot的performTraversals方法開始的,ViewRoot對(duì)應(yīng)ViewRootImpl類。ViewRoot在performTraversals中會(huì)調(diào)用performMeasure方法來(lái)進(jìn)行對(duì)根View的測(cè)量過(guò)程。而在performMeasure方法中又會(huì)調(diào)用View的measure方法。對(duì)于View的measure方法它是一個(gè)final類型,也就是說(shuō)這個(gè)measure方法不能被子類重寫。但是在measure方法中調(diào)用了onMeasure方法。所以View的子類可以重寫onMeasure方法來(lái)實(shí)現(xiàn)各自的Measure過(guò)程。在這里也就是主要對(duì)onMeasure方法進(jìn)行分析。
MeasureSpec
MeasureSpec是View類中的一個(gè)靜態(tài)內(nèi)部類。一個(gè)MeasureSpec封裝了父布局傳遞給子布局的布局要求。每個(gè)MeasureSpec都代表著一個(gè)高度或?qū)挾鹊囊?。每個(gè)MesureSpec都是由specSize和specMode組成,它代表著一個(gè)32位的int值,其中高2位代表specSize,低30位代表specMode。
MeasureSpec的測(cè)量模式有三種,下面介紹一下這三種測(cè)量模式:
UNSPECIFIED
父容器對(duì)子View沒(méi)有任何的限制,子View可以是任何的大小。
EXACTLY
父容器為子View大小指定一個(gè)具體值,View的最終大小就是specSize。對(duì)應(yīng)View屬性match_parent和具體值。
AT_MOST
子View的大小最大只能是specSize,也就是所子View的大小不能超過(guò)specSize。對(duì)應(yīng)View屬性的wrap_content.
在MeasureSpec中可以通過(guò)specSize和specMode并使用makeMeasureSpec方法來(lái)創(chuàng)建一個(gè)MeasureSpec,還可以通過(guò)getMode和getSize來(lái)獲取MeasureSpec的specMode和specSize。
View的測(cè)量過(guò)程
在上面已經(jīng)說(shuō)到,View的Measure過(guò)程是由measure方法來(lái)完成的,而measure方法通過(guò)調(diào)用onMeasure方法來(lái)完成View的Measure過(guò)程。那么就來(lái)看一下onMeasure方法。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
在View的onMeasure方法中只是調(diào)用了setMeasuredDimension方法,setMeasuredDimension方法的作用就是設(shè)置View的高和寬的測(cè)量值。對(duì)于View測(cè)量后寬和高的值是通過(guò)getDefaultSize方法來(lái)獲取的。下面就來(lái)一下這個(gè)getDefaultSize方法。
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;
}
對(duì)于MeasureSpec的AT_MOST和EXACTLY模式下,直接返回的就是MeasureSpec的specSize,也就是說(shuō)這個(gè)specSize就是View測(cè)量后的大小。而對(duì)于在UNSPECIFIED模式下,View的測(cè)量值則為getDefaultSize方法中的第一個(gè)參數(shù)size。這個(gè)size所對(duì)應(yīng)的寬和高是通過(guò)getSuggestedMinimumWidth和getSuggestedMinimumHeight兩個(gè)方法獲取的。下面就來(lái)看一下這兩個(gè)方法。
protected int getSuggestedMinimumHeight() {
return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
在這里可以看到對(duì)于View寬和高的取值是根據(jù)View是否存在背景進(jìn)行設(shè)置的。在這里以View的寬度來(lái)進(jìn)行說(shuō)明。若是View沒(méi)有背景則是View的寬度mMinWidth。對(duì)于mMinWidth值得設(shè)置可以在XML布局文件中設(shè)置minWidth屬性,它的默認(rèn)值為0。也可以通過(guò)調(diào)用View的setMinimumWidth()方法其賦值。若是View存在背景的話,則取View本身最小寬度mMinWidth和View背景的最小寬度它們中的最大值。
ViewGroup的測(cè)量過(guò)程
對(duì)于ViewGroup的Measure過(guò)程,ViewGroup處理Measure自己本身的大小,還需要遍歷子View,并調(diào)用它們的measure方法,然后各個(gè)子元素再去遞歸執(zhí)行Measure過(guò)程。在ViewGroup中并沒(méi)有重寫onMeasure方法,因?yàn)閂iewGroup它是一個(gè)抽象類,對(duì)于不同的具體ViewGroup它的onMeasure方法中所實(shí)現(xiàn)的過(guò)程不一樣。但是在ViewGroup中提供了一個(gè)measureChildren方法,對(duì)子View進(jìn)行測(cè)量。下面就來(lái)看一下這個(gè)measureChildren方法。
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);
}
}
}
在這里獲取ViewGroup中所有的子View。然后遍歷ViewGroup中子View并調(diào)用measureChild方法來(lái)完成對(duì)子View的測(cè)量。下面看一下measureChild方法。
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);
}
在這段代碼中通過(guò)getChildMeasureSpec方法獲取子View寬和高的MeasureSpec。然后調(diào)用子View的measure方法開始對(duì)View進(jìn)行測(cè)量。下面就來(lái)看一下是如何通過(guò)getChildMeasureSpec方法來(lái)獲取View的MeasureSpec的。
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;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
在這段代碼對(duì)于MeasureSpec的獲取主要是根據(jù)父容器的MeasureSpec和View本身的LayoutParams。下面通過(guò)一張表格來(lái)看一下它們之間的對(duì)應(yīng)關(guān)系。
到這里通過(guò)getChildMeasureSpec方法獲取到子View的MeasureSpec以后,便調(diào)用View的Measure方法,開始對(duì)View進(jìn)行測(cè)量。
正如剛才說(shuō)的那樣對(duì)于ViewGroup它是一個(gè)抽象類,并沒(méi)有重寫View的onMeasure方法。但是到具體的ViewGroup時(shí),例如FrameLayout,LinearLayout,RelativeLayout等,它們通過(guò)重寫onMeasure方法來(lái)來(lái)完成自身以及子View的Measure過(guò)程。下面以FrameLayout為例,看一下的Measure過(guò)程。在FrameLayout中,它的Measure過(guò)程也算是比較簡(jiǎn)單,下面就來(lái)看一下FrameLayout中的onMeasure方法。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
final boolean measureMatchParentChildren =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
mMatchParentChildren.clear();
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) {
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);
childState = combineMeasuredStates(childState, child.getMeasuredState());
if (measureMatchParentChildren) {
if (lp.width == LayoutParams.MATCH_PARENT ||
lp.height == LayoutParams.MATCH_PARENT) {
mMatchParentChildren.add(child);
}
}
}
}
// Account for padding too
maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
// Check against our minimum height and width
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
// Check against our foreground's minimum height and width
final Drawable drawable = getForeground();
if (drawable != null) {
maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
}
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
count = mMatchParentChildren.size();
if (count > 1) {
for (int i = 0; i < count; i++) {
final View child = mMatchParentChildren.get(i);
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec;
if (lp.width == LayoutParams.MATCH_PARENT) {
final int width = Math.max(0, getMeasuredWidth()
- getPaddingLeftWithForeground() - getPaddingRightWithForeground()
- lp.leftMargin - lp.rightMargin);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
width, MeasureSpec.EXACTLY);
} else {
childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
lp.leftMargin + lp.rightMargin,
lp.width);
}
final int childHeightMeasureSpec;
if (lp.height == LayoutParams.MATCH_PARENT) {
final int height = Math.max(0, getMeasuredHeight()
- getPaddingTopWithForeground() - getPaddingBottomWithForeground()
- lp.topMargin - lp.bottomMargin);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
height, MeasureSpec.EXACTLY);
} else {
childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
lp.topMargin + lp.bottomMargin,
lp.height);
}
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
}
在這部分代碼中邏輯也很簡(jiǎn)單,主要完成了兩件事。首先FrameLayout完成自身的測(cè)量過(guò)程,然后在遍歷子View,執(zhí)行View的measure方法,完成View的Measure過(guò)程。在這里代碼比較簡(jiǎn)單就不在進(jìn)行詳細(xì)描述。
總結(jié)
最后對(duì)View和ViewGroup的Measure過(guò)程做一下總結(jié)。對(duì)于View,它的Measure很簡(jiǎn)單,在獲取到View的高和寬的測(cè)量值之后,便為其設(shè)置高和寬。而對(duì)于ViewGroup來(lái)說(shuō),除了完成自身的Measure過(guò)程以外,還需要遍歷子View,完成子View的測(cè)量過(guò)程。
相關(guān)文章
Android常見XML轉(zhuǎn)義字符(總結(jié))
下面小編就為大家?guī)?lái)一篇Android常見XML轉(zhuǎn)義字符(總結(jié))。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-04-04
Android ScrollView的頂部下拉和底部上拉回彈效果
本篇文章主要介紹了Android ScrollView的頂部下拉和底部上拉回彈效果,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-05-05
Android的OkHttp包處理用戶認(rèn)證的代碼實(shí)例分享
OkHttp包(GitHub主頁(yè)github.com/square/okhttp)是一款高人氣安卓HTTP支持包,這里我們來(lái)看一下Android的OkHttp包處理用戶認(rèn)證的代碼實(shí)例分享:2016-07-07
獲取控件大小和設(shè)置調(diào)整控件的位置XY示例
我需要的設(shè)置控件相對(duì)屏幕左上角的X 、Y位置,而不是自己本身位置的偏移,下面與大家介紹下怎么獲取設(shè)置控件的信息2013-06-06
Android 跨進(jìn)程模擬按鍵(KeyEvent )實(shí)例詳解
這篇文章主要介紹了Android 跨進(jìn)程模擬按鍵(KeyEvent )實(shí)例詳解的相關(guān)資料,類似手機(jī)遙控器的需求就可以這么做,需要的朋友可以參考下2016-11-11
Android實(shí)現(xiàn)頁(yè)面滑動(dòng)切換動(dòng)畫
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)頁(yè)面滑動(dòng)切換動(dòng)畫,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-12-12
android 上傳aar到私有maven服務(wù)器的示例
這篇文章主要介紹了android 上傳aar到私有maven服務(wù)器,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-11-11
Android提高之SurfaceView與多線程的混搭實(shí)例
這篇文章主要介紹了Android提高之SurfaceView與多線程的混搭,很實(shí)用的功能,需要的朋友可以參考下2014-08-08

