measure源碼
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
//判斷是否使用視覺邊界布局
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
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
//計算key值
long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
//mMeasureCache是LongSparseLongArray類型的成員變量,
//其緩存著View在不同widthMeasureSpec、heightMeasureSpec下量算過的結果
//如果mMeasureCache為空,我們就新new一個對象賦值給mMeasureCache
if (mMeasureCache == null)
mMeasureCache = new LongSparseLongArray(2);
//是否強制測量
final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
// Optimize layout by avoiding an extra EXACTLY pass when the view is
// already measured as the correct size. In API 23 and below, this
// extra pass is required to make LinearLayout re-distribute weight.
//mOldWidthMeasureSpec和mOldHeightMeasureSpec分別表示上次對View進行量算時的widthMeasureSpec和heightMeasureSpec
//執行View的measure方法時,View總是先檢查一下是不是真的有必要費很大力氣去做真正的量算工作
final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
|| heightMeasureSpec != mOldHeightMeasureSpec;
//parent是否給定了精確的大小
final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
&& MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
&& getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
final boolean needsLayout = specChanged
&& (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);
if (forceLayout || needsLayout) {
// first clears the measured dimension flag
//通過按位操作,重置View的狀態mPrivateFlags,將其標記為未量算狀態
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
//對阿拉伯語、希伯來語等從右到左書寫、布局的語言進行特殊處理
resolveRtlPropertiesIfNeeded();
//在View真正進行量算之前,View還想進一步確認能不能從已有的緩存mMeasureCache中讀取緩存過的量算結果
//如果是強制layout導致的量算,那么將cacheIndex設置為-1,即不從緩存中讀取量算結果
//如果不是強制layout導致的量算,那么我們就用上面根據measureSpec計算出來的key值作為緩存索引cacheIndex。
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
//如果運行到此處,表示我們沒有從緩存中找到量算過的尺寸或者是sIgnoreMeasureCache為true導致我們要忽略緩存結果
//此處調用onMeasure方法,并把尺寸限制條件widthMeasureSpec和heightMeasureSpec傳入進去
//onMeasure方法中將會進行實際的量算工作,并把量算的結果保存到成員變量中
onMeasure(widthMeasureSpec, heightMeasureSpec);
//onMeasure執行完后,通過位操作,重置View的狀態mPrivateFlags,將其標記為在layout之前不必再進行量算的狀態
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
//onMeasure執行完后,通過位操作,重置View的狀態mPrivateFlags,將其標記為在layout之前不必再進行量算的狀態
long value = mMeasureCache.valueAt(cacheIndex);
// Casting a long to int drops the high 32 bits, no mask needed
//一旦我們從緩存中讀到值,我們就可以調用setMeasuredDimensionRaw方法將當前量算的結果到成員變量中
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
//如果我們自定義的View重寫了onMeasure方法,但是沒有調用setMeasuredDimension()方法,
//那么此處就會拋出異常,提醒開發者在onMeasure方法中調用setMeasuredDimension()方法
//Android是如何知道我們有沒有在onMeasure方法中調用setMeasuredDimension()方法的呢?
//方法很簡單,還是通過解析狀態位mPrivateFlags。
//setMeasuredDimension()方法中會將mPrivateFlags設置為PFLAG_MEASURED_DIMENSION_SET狀態,即已量算狀態,
//此處就檢查mPrivateFlags是否含有PFLAG_MEASURED_DIMENSION_SET狀態即可判斷setMeasuredDimension是否被調用
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()");
}
mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
}
//mOldWidthMeasureSpec和mOldHeightMeasureSpec保存著最近一次量算時的MeasureSpec,
//在量算完成后將這次新傳入的MeasureSpec賦值給它們
mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
//最后用上面計算出的key作為鍵,量算結果作為值,將該鍵值對放入成員變量mMeasureCache中,
//這樣就實現了對本次量算結果的緩存,以便在下次measure方法執行的時候,有可能將其從中直接讀出,
//從而省去實際量算的步驟
mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
(long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
}
- 判斷是否使用視覺邊界布局,默認為否,如果使用,需要重新布局
- 根據widthMeasureSpec、heightMeasureSpec計算緩存key
- 判斷是否需要重新計算尺寸
- specChanged,將當前widthMeasureSpec、heightMeasureSpec和舊的值比較。
- isSpecExactly,是否使用了精確大小布局
- forceLayout,mPrivateFlags & PFLAG_FORCE_LAYOUT。是否有強制布局標識
- 如果需要重新計算
- 去除PFLAG_MEASURED_DIMENSION_SET標識
- 獲取緩存index
- 如果緩存index=-1或者已經標識忽略緩存,則重新計算
- onMeasure(widthMeasureSpec, heightMeasureSpec);
- 置View的狀態mPrivateFlags,將其標記為在layout之前不必再進行量算的狀態
- 如果有緩存并未標識忽略緩存則直接使用緩存尺寸setMeasuredDimensionRaw()
- 判斷PFLAG_MEASURED_DIMENSION_SET是否設置
- PFLAG_MEASURED_DIMENSION_SET在setMeasuredDimensionRaw()方法中設置,所以onMeasure()方法中必須調用setMeasuredDimensionRaw方法
- 設置mOldWidthMeasureSpec、mOldHeightMeasureSpec并緩存尺寸
流程圖:
st=>start: measure
cond=>condition: 是否使用視覺布局?
op_calc=>operation: 重新計算尺寸
op_gen_cache_key=>operation: 根據尺寸計算緩存key
cond_isLayout=>condition: 是否需要強制布局(與old尺寸比較||強制布局標識)
op_et_cache=>operation: 清除尺寸計算標示,獲取緩存尺寸
cond_is_cache=>condition: 是否存在緩存||忽略緩存
op_on_measure=>operation: 計算并設置尺寸
op_use_cache=>operation: 使用緩存尺寸
cond_is_set_measure=>condition: 是否設置尺寸(setMeasuredDimensionRaw)
op_no_set_measuret=>operation: IllegalStateException
op_reset=>operation: 設置就尺寸,緩存尺寸
e=>end: 計算尺寸結束
st->cond->op
cond(no)->op_gen_cache_key
cond(yes)->op_calc
op_calc->op_gen_cache_key
op_gen_cache_key->cond_isLayout
cond_isLayout(yes)->op_et_cache
cond_isLayout(no)->op_reset
op_et_cache->cond_is_cache
cond_is_cache(no)->op_on_measure
cond_is_cache(yes)->op_use_cache
op_use_cache->cond_is_set_measure
op_on_measure->cond_is_set_measure
cond_is_set_measure(no)->op_no_set_measuret
cond_is_set_measure(yes)->op_reset
op_reset->e
最后編輯于 :
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。