通過上一文章分析我們找到了ViewRootImpl類中的performTraversals方法是測量佈局和繪製的起點。並且由Activity中的setContentView觸發。這一篇我們將從performTraversals方法分析View的測量過程。
private void performTraversals() {
...
//測量
if (!mStopped) {
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
//佈局
if (didLayout) {
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
...
}
if (!cancelDraw && !newSurface) {
if (!skipDraw || mReportNextDraw) {
if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).startChangingAnimations();
}
mPendingTransitions.clear();
}
//繪製
performDraw();
}
}
...
}複製程式碼
每個View的MeasureSpec都是由父容器的MeasureSpec+自己的LayoutParams轉換成自己的MeasureSpec。轉換的原始碼在View基礎看過。這裡我們主要看對於DecorView來說,它已經是頂層view了,沒有父容器,那麼它的MeasureSpec怎麼來的?
回到原始碼我們看通過getRootMeasureSpec方法獲取的
/**
* @param windowSize 螢幕的寬或者高
*
* @param rootDimension 下邊展開這個引數
*
* @return 根檢視的度量規範
*/
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
//這種模式就是撐滿布局,大小是佈局大小windowSize。模式的EXACTLY
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
//包裹內容,最大大小勢能超過windowSize,模式是AT_MOST
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// 確定的值,那麼就是吧確定的rootDimension給他
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}複製程式碼
第二個引數是中的值是獲取的這個物件的寬度和高度
final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams();複製程式碼
他在預設情況下都是match_parment,也就意味著根檢視能給子類全部的螢幕大小區域。
到目前為止,就已經獲得了一份DecorView的MeasureSpec,然後呼叫performMeasure方法
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}複製程式碼
這個方法很簡單,直接呼叫mView的measure方法,並且傳入根佈局的度量規範,那麼這個mView是誰呢?
通過檢視原始碼可以知道這個mView就是DecorView
這是ViewRootImpl#setView
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;複製程式碼
到此:
- 我們得到了DecorView的MeasureSpec
- 呼叫了DecorView中的measure方法並傳入DecorView的MeasureSpec
現在我們去找DecorView中的measure方法
由於DecorView繼承自FrameLayout,而FrameLayout和DecorView和ViewGroup中沒有measure方法(final表示不能重寫,所以它的子類不可能有measure方法),因此呼叫的是父類View的measure方法,我們直接看它的原始碼
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
onMeasure(widthMeasureSpec, heightMeasureSpec);
}複製程式碼
onMeasure(widthMeasureSpec, heightMeasureSpec)
方法。並且DecorView重寫了onMeasure
方法,在DecorView#onMeasure方法中主要是進一步確定自己的
widthMeasureSpec
、heightMeasureSpec
,並呼叫super.onMeasure(widthMeasureSpec, heightMeasureSpec)
即FrameLayout#onMeasure方法由於不同的ViewGroup有著不同的性質,那麼它們的onMeasure必然是不同的,因此這裡不可能把所有佈局方式的onMeasure方法都分析一遍,因此這裡選擇了FrameLayout的onMeasure方法來進行分析,其它的佈局方式讀者可以自行分析。那麼我們繼續來看看這個方法:
注意:這裡沒有呼叫super.onMeasure方法
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//獲取當前佈局內的子View數量
int count = getChildCount();
////遍歷所有型別不為GONE的子View,
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
// 這個方法的作用:結合layoutparmas(使用者引數)生成子類的測量規範
//呼叫測量子類的方法,傳入上邊生成的測量規範
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
}
}
//這兩個方法都是View中的方法,
//設定DecorView的大小 這裡設定的是螢幕的寬高
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
}
複製程式碼
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);
}複製程式碼
child物件呼叫的measure是View中的measure,而View中的measure又呼叫線性佈局中的onMeasure方法,那麼我們去找線性佈局中的onMeasure方法
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}複製程式碼
這裡我們要畫一張圖片來表示DecorView的佈局
所以我們需要繼續去看measureVertical的原始碼裡邊的關鍵程式碼
父容器的MeasureSpec+自己的LayoutParams轉換成自己的MeasureSpec
final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
mPaddingLeft + mPaddingRight +
lp.leftMargin + lp.rightMargin, lp.width);複製程式碼
繼續呼叫子類的測量
child.measure(childWidthMeasureSpec,
MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));複製程式碼
同時設定自己大小
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
heightSizeAndState);複製程式碼
當子View是基本View的時候,他不會再去呼叫子類的測量方法,僅僅去獲取父容器的MeasureSpec+自己的LayoutParams轉換成自己的MeasureSpec,然後只需要設定自己的大小即可。
到此關於VIew的測量分析完畢。
梳理一下測量流程:
1.Activity中的setContentView方法觸發ViewRootImpl中的performTraversals方法執行
2.performTraversals方法觸發performMeasure方法執行(注意這裡的MeasureSpec生成方法是make建立的)
3.DecorView物件呼叫View#measure方法執行
4.View#measure方法呼叫不同的onMeasure方法執行(因為子類重寫了)
5.在繼承ViewGroup的onMeasure方法中會做兩件事
- 獲取父容器的MeasureSpec+子ViewLayoutParams轉換成自己的MeasureSpec然後呼叫子View的測量方法(measureChildWithMargins方法內邏輯即使)
- 通過setMeasuredDimension方法設定自己的大小
6.在繼承View的onMeasure方法中只會做一件事,參考父容器的MeasureSpec,然後通過setMeasuredDimension方法設定自己的大小
分析原始碼需要注意:
- 呼叫measure方法是同一個方法
- 呼叫onMeasure確是不同的onMeasure方法。
而且可以發現:測量的時候是先去測量子View的大小,之後再去測量自己的大小
,