View繪制流程
一圖勝千言
這里說明下Acitivity的onResume是在handleResumeActivity之前執(zhí)行的蛤织,所以在onResume中獲取View的寬高為0。
實(shí)際上通用的繪制流程應(yīng)該是從WindowManager#addView開始鳄炉。
View measure()分析
首先View的Measure方法聲明為final边苹,子類無法繼承,故關(guān)于View多態(tài)的實(shí)現(xiàn)就只能在onMeasure方法中實(shí)現(xiàn)
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
//1.判斷View本身LayoutMode是否是視覺邊界布局沃于,僅用于檢測ViewGroup
boolean optical = isLayoutModeOptical(this);
//2.判斷父容器是否是視覺布局邊界它呀,是則重新調(diào)整測量規(guī)格(mParent可能是ViewRootImpl)
if (optical != isLayoutModeOptical(mParent)) {
//View的background需要設(shè)置.9背景圖才會(huì)生效,否則insets的left洛勉、right全為0
Insets insets = getOpticalInsets();
int oWidth = insets.left + insets.right;
int oHeight = insets.top + insets.bottom;
widthMeasureSpec = MeasureSpec.adjust(widthMeasureSpec, optical ? -oWidth : oWidth);
heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
}
// Suppress sign extension for the low bytes,禁止低位進(jìn)行符號(hào)擴(kuò)展
//3.根據(jù)當(dāng)前測量規(guī)格生成一個(gè)與之對應(yīng)的key(相同的測量規(guī)格產(chǎn)生的測量值肯定一樣的)粘秆,供后續(xù)索引緩存測量值
long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
//4.初始化測量緩存稀疏數(shù)組,該數(shù)組可自行擴(kuò)容收毫,只是初始化為2
if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
//5.如果強(qiáng)制layout(eg.view.forceLayout())或者本次測量規(guī)格與上次測量規(guī)格不同攻走,進(jìn)入該if語句
if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
widthMeasureSpec != mOldWidthMeasureSpec ||
heightMeasureSpec != mOldHeightMeasureSpec) {
final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
// first clears the measured dimension flag
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
resolveRtlPropertiesIfNeeded();
//6.如果需要強(qiáng)制layout則進(jìn)行重新測量,反之則從緩存中查詢是否有與目前測量規(guī)格對應(yīng)的key此再,
//如果有則取用緩存中的測量值昔搂,反之則執(zhí)行onMeasure方法重新測量,未索引到返回一個(gè)負(fù)數(shù)
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
//7.targetSDK小于Kitkat版本(API20),sIgnoreMeasureCache則為true
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
long value = mMeasureCache.valueAt(cacheIndex);
// Casting a long to int drops the high 32 bits, no mask needed
setMeasuredDimensionRaw((int) (value >> 32), (int) value);
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
// flag not set, setMeasuredDimension() was not invoked, we raise
// an exception to warn the developer
if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
throw new IllegalStateException("View with id " + getId() + ": "
+ getClass().getName() + "#onMeasure() did not set the"
+ " measured dimension by calling"
+ " setMeasuredDimension()");
}
//8.標(biāo)志位賦值引润,表明需要在layout方法中執(zhí)行onlayout方法
mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
//9.緩存本次測量規(guī)格,及測量寬高
mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
(long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
}
/**
* Return true if o is a ViewGroup that is laying out using optical bounds.
* @hide
*/
public static boolean isLayoutModeOptical(Object o) {
return o instanceof ViewGroup && ((ViewGroup) o).isLayoutModeOptical();
}
關(guān)于LayoutMode請參考Android LayoutMode需要翻墻
關(guān)于MeasureSpec請參考Android MeasureSpec
小結(jié):
View的measure方法只是一個(gè)測量優(yōu)化者痒玩,主要做了2級(jí)測量優(yōu)化:
1.如果flag不為強(qiáng)制Layout或者與上次測量規(guī)格相比未改變淳附,那么將不會(huì)進(jìn)行重新測量(執(zhí)行onMeasure方法),直接使用上次的測量值蠢古;
2.如果滿足非強(qiáng)制測量條件奴曙,即前后測量規(guī)格發(fā)生變化,則會(huì)先根據(jù)目前測量規(guī)格生成的key索引緩存數(shù)據(jù)草讶,索引到就無需進(jìn)行重新測量;如果targetSDK小于API 20則二級(jí)測量優(yōu)化無效洽糟,依舊會(huì)重新測量,不會(huì)采用緩存測量值。
3.View#requestLayout 只會(huì)讓該View及其父容器重新走一遍坤溃,如果該View是ViewGroup拍霜,其里面的子View測量優(yōu)化還是依舊有效的
View onMeasure()分析
View的onMeasure方法比較簡單,目的是將測量值賦給mMeasuredWidth和mMeasuredHeight
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
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;
//解析測量規(guī)格獲得寬薪介、高祠饺,這就是為何View無論你填match_parent還是wrap_content,它始終是填滿父容器的原因
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
//表明測量尺寸已經(jīng)設(shè)置汁政,與measure方法中的first clears the measured dimension flag相呼應(yīng)
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
小結(jié):
View的onMeasure方法才是真正的測量者道偷,它根據(jù)測量規(guī)格以及其他條件來決定自己最終的測量大小。
需要注意记劈,自定義View重寫該方法時(shí)勺鸦,務(wù)必保證調(diào)用setMeasuredDimension()將測量寬、高存起來目木,measure方法分析中有提到换途,如果不調(diào)用該方法將會(huì)拋出非法狀態(tài)異常。
ViewGroup onMeasure分析
ViewGroup繼承至View實(shí)現(xiàn)了ViewParent接口嘶窄,是一個(gè)抽象類怀跛。前面也提到View的measure方法不能被繼承,所以ViewGroup沒有measure方法柄冲。查看源碼發(fā)現(xiàn)它并沒有重寫onMeasure方法吻谋,那就去看看其實(shí)現(xiàn)類是否有重寫,就看最簡單的實(shí)現(xiàn)類FrameLayout现横。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
//判斷是否要再次測量layout_width/height屬性為match_parent的child
//即FrameLayout的layout_width/height屬性為wrap_content漓拾,則會(huì)再次測量屬性為match_parent的child
final boolean measureMatchParentChildren =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
mMatchParentChildren.clear();
//首次遍歷測量子控件
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
//GONE類型child不測量,這就是為何GONE不會(huì)占用位置戒祠,因?yàn)闆]有測量
if (mMeasureAllChildren || child.getVisibility() != GONE) {
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
..................
}
}
..................
//保存自身的測量寬高
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
//再次測量layout_width/height屬性為match_parent的child,部分View三次執(zhí)行onMeasure的原因
count = mMatchParentChildren.size();
if (count > 1) {
for (int i = 0; i < count; i++) {
final View child = mMatchParentChildren.get(i);
..........
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
}
進(jìn)入MeasureChildWithMargins函數(shù)
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
//根據(jù)ViewGroup自身的測量規(guī)格生成child的測量規(guī)格
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);
//調(diào)用child的measure方法骇两,并將child的測量規(guī)格傳遞給child
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
進(jìn)入getChildMeasureSpec方法,看看到底是如何測量child的規(guī)格的
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
//獲取父容器的測量模式姜盈、測量大小
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
//計(jì)算出父容器允許child的最大尺寸
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// 父容器測量模式為精確模式
case MeasureSpec.EXACTLY:
//如果child的layout_width/height為具體的數(shù)值eg.20dp低千,那么child的測量規(guī)格就為大小20dp,模式為精確模式
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
}
//如果child的layout_width/height為MATCH_PARENT馏颂,那么child的測量規(guī)格就為大小父容器允許的最大值示血,模式為精確模式
else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
}
//如果child的layout_width/height為WRAP_CONTENT,那么child的測量規(guī)格就為大小父容器允許的最大值救拉,模式為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;
小結(jié):
ViewGroup的測量主要是根據(jù)其自身測量規(guī)格难审,結(jié)合child的LayoutParams進(jìn)行判斷分析,生成一個(gè)child的測量規(guī)格信息亿絮,傳遞給child的measure方法告喊。所謂的測量規(guī)格即是一個(gè)建議麸拄,建議view的寬、高應(yīng)該為多少黔姜,至于采取與否完全取決于view自己(嗯應(yīng)該是取決于程序員O(∩_∩)OBG小)。
另ViewGroup提供了三個(gè)測量方法供我們使用地淀,在實(shí)際運(yùn)用中可以偷偷懶失球,不用自己去實(shí)現(xiàn)測量邏輯:
- measureChildWithMargins 測量單個(gè)child,margin參數(shù)有效
- measureChild 測量單個(gè)child帮毁,margin參數(shù)無效
- measureChildren 測量所有child內(nèi)部調(diào)用measureChild
延伸閱讀:
【View為什么會(huì)至少執(zhí)行2次onMeasure实苞、onLayout】暫未發(fā)布