measure - 測量
確定View的測量寬高
上面說到 performTraversals() 函數的時候,內部調用了 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);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
又調用了View中的 measure() 函數!
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
// 計算key值
long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
// 初始化mMeasureCache對象,用來緩存測量結果
if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
// ...
if (forceLayout || needsLayout) {
// ...
// 嘗試去查找緩存
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
//沒有讀取到緩存或者忽略緩存時
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// 測量自己
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;
}
// ...
mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
}
mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
// 緩存
mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
(long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
來看 onMeasure() 函數
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
- 1
- 2
- 3
setMeasureDimension() 函數倒是很簡單!目的就是存儲計算出來的測量寬高~
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int opticalWidth = insets.left + insets.right;
int opticalHeight = insets.top + insets.bottom;
measuredWidth += optical ? opticalWidth : -opticalWidth;
measuredHeight += optical ? opticalHeight : -opticalHeight;
}
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
現在來主要關注 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;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
當specMode 是 UNSPECIFIED 的時候,View的寬/高爲getDefaultSize()的第一個參數,也就是getSuggestedMinimumWidth() 或者 getSuggestedMinimumHeight()
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
- 1
- 2
- 3
當View沒有背景時,返回mMinWidth,該變量默認值是0,對應android:minWidth這個屬性。所以,如果不指定該屬性的話,就是0。
如果View有背景,則返回背景的原始寬度。
getSuggestedMinimumHeight()的內部邏輯與getSuggestedMinimumWidth()類似。
所以,當SpecMode是UNSPECIFIED的時候,View的測量寬/高 就是 getSuggestedMinimumWidth() 和 getSuggestedMinimumHeight() 兩個方法的返回值!
當SpecMode是AT_MOST 或者 EXACTLY 時,View的測量 寬/高 就是 measureSpec 中的SpecSize ,也就是測量後的大小~
分析到這裏,View的measure過程也就分析完了~
那麼ViewGroup的measure是什麼時候開始的呢???
還得從DecorView來說起!
在 Activity.attach() 函數中創建了PhoneWindow對象!
public PhoneWindow(Context context, Window preservedWindow) {
this(context);
// ...
if (preservedWindow != null) {
mDecor = (DecorView) preservedWindow.getDecorView();
// ...
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
在PhoneWindow的構造函數中,創建了DecorView對象!
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks
- 1
DecorView繼承FrameLayout,所以它是一個ViewGroup!
所以整個window的繪製是從DecorView這個ViewGroup開始的!
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
- 1
這裏的mView就是DecorView
其實ViewGroup 是一個抽象類,並沒有重寫onMeasure()函數!ViewGroup的子類們重寫了onMeasure()函數!
既然DecorView繼承了FrameLayout,那就拿FrameLayout來分析一下它的 measure過程。
FrameLayout的measure流程
從decorView的measure() 方法體內看出,內部調用了onMeasure()。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 1. 獲取子View的個數
int count = getChildCount();
// 2. 如果寬/高的SpecMode有一個不是EXACTLY,則爲true
final boolean measureMatchParentChildren =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
// 3. 清空集合
mMatchParentChildren.clear();
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
// 4. 遍歷子view
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
// 5. GONE類型的view不測量
if (mMeasureAllChildren || child.getVisibility() != GONE) {
// 6. 測量子view的寬高
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
// 7. 計算最大寬度
maxWidth = Math.max(maxWidth,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
// 8. 計算最大高度
maxHeight = Math.max(maxHeight,
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
childState = combineMeasuredStates(childState, child.getMeasuredState());
// 9. 如果measureMatchParentChildren 爲true,並且子view設置的寬/高屬性是match_parent,就把這個子view添加到集合中
if (measureMatchParentChildren) {
if (lp.width == LayoutParams.MATCH_PARENT ||
lp.height == LayoutParams.MATCH_PARENT) {
mMatchParentChildren.add(child);
}
}
}
}
// 10. 補充計算最大寬度,最大高度
maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
// 11. 再次比較計算最大寬度,最大高度
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
// 12. 如果有背景,則需要再次與背景圖的寬高相比較得出最大寬高
final Drawable drawable = getForeground();
if (drawable != null) {
maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
}
// 13. 設置當前ViewGroup的測量寬高
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT));
// 14. 遍歷需要二次測量的子view
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) { // 子view寬度設置的是MATCH_PARENT
final int width = Math.max(0, getMeasuredWidth()
- getPaddingLeftWithForeground() - getPaddingRightWithForeground()
- lp.leftMargin - lp.rightMargin);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
width, MeasureSpec.EXACTLY);
} else { // 子view寬度設置的不是MATCH_PARENT
childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
lp.leftMargin + lp.rightMargin,
lp.width);
}
final int childHeightMeasureSpec;
if (lp.height == LayoutParams.MATCH_PARENT) { // 子view高度設置的是MATCH_PARENT
final int height = Math.max(0, getMeasuredHeight()
- getPaddingTopWithForeground() - getPaddingBottomWithForeground()
- lp.topMargin - lp.bottomMargin);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
height, MeasureSpec.EXACTLY);
} else { // 子view高度設置不是MATCH_PARENT
childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
lp.topMargin + lp.bottomMargin,
lp.height);
}
// 15. 測量子view
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
從上面FrameLayout的onMeasure()方法體可以看出來,對子view進行了兩次測量,準確的來說不是所有的子view都進行了二次測量~
這是爲什麼呢?
來往下看~
mMatchParentChildren 是一個集合,是用來存儲需要二次測量的子view的!
private final ArrayList<View> mMatchParentChildren = new ArrayList<>(1);
- 1
那麼都有哪些子view需要放到這個集合裏面進行二次測量呢?
當 measureMatchParentChildren == true 的時候!
final boolean measureMatchParentChildren =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
- 1
- 2
- 3
那麼什麼情況下 FrameLayout這個ViewGroup 的寬或者高的SpecMode不爲EXACTLY呢?
我在 View的繪製流程分析之一 已經進行了分析!
再來一張圖(轉載~):
拋去UNSPECIFIED 這個mode不管,當前FrameLayout這個ViewGroup的測量模式不爲EXACTLY有三種情況!
- FrameLayout的父容器的SpecMode爲AT_MOST,並且這個FrameLayout的 寬 / 高 屬性是match_parent
- FrameLayout的 寬 / 高 屬性是wrap_content
總而言之,就是FrameLayout這個ViewGroup的寬或高不是一個固定的值,也就是不是EXACTLY模式!
這種情況下,再去判斷子view(FrameLayout的子view)的寬高屬性是否是match_parent,如果是則把這個子view添加到集合中去!
其實很容易理解!通過第一次遍歷所有(不是GONE)的子view之後,就把父佈局,也就是這個FrameLayout的寬高給測量出來了。
但是對於那些寬高設置爲match_parent,它們的寬高依賴於父容器的大小,所以需要再次遍歷它們設置它們的寬高~
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
先來看看第一次遍歷時,對子view的測量過程
直接調用的父類ViewGroup中的方法
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);
// 調用子view的measure()函數進行測量
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
在計算子view的寬度或高度的測量規格時,把父容器的padding值,和子view的margin值 加了上去!
關於getChildMeasureSpec() 這個函數的分析,在上一篇blog已經說過了! getChildMeasureSpec()
那麼得到了寬高的測量規格之後,就可以調用measure進行測量了!
child.measure() 調用了View中的measure() 函數,此函數是final類型!不允許子類重寫!
其實也很容易理解, 所有的容器測量都要先遍歷子view進行測量,然後在確定容器的大小,所以最終實際的任務大多在一個個子view的測量上,容器的大小只需要針對這些子view的測量大小加加減減而已 !
view的measure過程在本篇blog上面已經進行了分析!本質上還是調用onMeasure() 函數!
如果這個View是一個ViewGroup,則會回調具體的容器類的onMeasure() 函數,如果不是則調用View或者它的子類(如果重寫了)的onMeasure() 函數!
此時回過頭來再看FrameLayout.onMeasure() 方法體下面這一句
// 13. 設置當前ViewGroup的測量寬高
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT));
- 1
- 2
- 3
當對所有的子view測量完畢之後,調用這個函數把計算得來的父容器的測量結果進行保存!
重點在 resolveSizeAndState() 函數!
public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
final int specMode = MeasureSpec.getMode(measureSpec);
final int specSize = MeasureSpec.getSize(measureSpec);
final int result;
switch (specMode) {
case MeasureSpec.AT_MOST:
if (specSize < size) { // 父容器給的尺寸小於測量出來的尺寸,改變result值!
result = specSize | MEASURED_STATE_TOO_SMALL;
} else {
result = size;
}
break;
case MeasureSpec.EXACTLY: // 精確模式下,結果就是測量的值
result = specSize;
break;
case MeasureSpec.UNSPECIFIED:
default:
result = size;
}
return result | (childMeasuredState & MEASURED_STATE_MASK);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
計算出來size之後,就通過 setMeasuredDimension() 函數保存測量寬高!
這樣從ViewGroup到一個個子View的測量,保存每個view/viewGroup的測量寬高!
分析到這裏,performTraversals() 函數裏面 performMeasure() 也就執行完畢了!