標籤:
在上篇文章(http://blog.csdn.net/jerehedu/article/details/47081849)中,根據源碼探索了View的繪製過程,過程有三個主要步驟,分別為測量、布局、繪製。系統對繪製已經做了很好的封裝,我們主要對測量和版面配置階段進行分析,看一看android是如何對view進行測量和布局的。
根據上篇文章的分析,我們知道在ViewRootImpl的performMeasure方法中,實際上調用了mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);方法。根據源碼我們找到了該方法的原型,此方法在View類中,並且是final方法,不可被子類重寫,方法的具體源碼如下:
public final void measure(int widthMeasureSpec, int heightMeasureSpec) { boolean optical = isLayoutModeOptical(this); if (optical != isLayoutModeOptical(mParent)) { …… } // Suppress sign extension for the low bytes long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL; if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2); if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT || widthMeasureSpec != mOldWidthMeasureSpec || heightMeasureSpec != mOldHeightMeasureSpec) { …… 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; }…… mPrivateFlags |= PFLAG_LAYOUT_REQUIRED; } mOldWidthMeasureSpec = widthMeasureSpec; mOldHeightMeasureSpec = heightMeasureSpec; mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 | (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension }
根據方法內容和說明,可以知道本方法就是用來測量View的大小的,而需要的兩個參數是由父View構建的,用於說明父View對子View的測量的規格要求,實際上在這個方法中真正完成測量大小的是方法onMeasure,此方法我們稍後分析。在此之前我們先要明白measure方法中的兩個參數的含義,剛才有提到參數是父View對子View的測量規格要求,那麼Android是如何描述的呢,這裡用到了一個類MeasureSpec,此類為View中的一個內部類,關鍵源碼如下:
public static class MeasureSpec { private static final int MODE_SHIFT = 30; private static final int MODE_MASK = 0x3 << MODE_SHIFT; public static final int UNSPECIFIED = 0 << MODE_SHIFT; public static final int EXACTLY = 1 << MODE_SHIFT; public static final int AT_MOST = 2 << MODE_SHIFT; public static int makeMeasureSpec(int size, int mode) { if (sUseBrokenMakeMeasureSpec) { return size + mode; } else { return (size & ~MODE_MASK) | (mode & MODE_MASK); } } public static int getMode(int measureSpec) { return (measureSpec & MODE_MASK); } public static int getSize(int measureSpec) { return (measureSpec & ~MODE_MASK); } ……}
根據SDK,此類封裝了父View對子View的布局要求,每個執行個體都代表了對子View的高度或者寬度的要求,測量要求包含兩個部分,分別為尺寸和模式。模式主要由三種,具體如下:
1、 UNSPECIFIED:代表父View對子View沒有約束,子View可以為任意大小。
2、 EXACTLY:父View確定子View的大小,子View被限定在給定的邊界中,忽咯本身的大小。
3、 AT_MOST:子View最大可以達到指定大小的值。
該類中提供了用來計算和產生測量要求的方法,具體如下:
1、 public static int makeMeasureSpec(int size, int mode),此方法最終產生一個32位位元用來表明測量規格要求,其中32和31位用來表明模式,後30位代表了大小。
2、 public static int getMode(int measureSpec),此方法可以根據測量說明,計算模式。
3、 public static int getSize(int measureSpec),此方法根據測量說明,計算大小。
明白了MeasureSpec,我們在回過頭來,看一看onMeasure方法,該方法的源碼如下:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));}
此方法的預設實現非常簡單,調用了setMeasuredDimersion方法將測量好的尺寸儲存到mMeasuredWidth和mMeasuredHeight。而在setMeasuredDimersion方法中調用了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; }
很明顯,此方法根據提供的預設大小和測量要求計算View的實際大小。到此為止,View完了測量過程。不過大多數情況下,當我們自訂ViewGroup的時候,我們需要重寫onMeasure方法,在此方法中,可以遍曆所有的子View並要求他們對自己的大小進行測量,同時不要忘記調用setMeasuredDimension進行儲存測量結果,在ViewGroup是通過如下三個方法實現的,關鍵代碼如下:
方法mesureChildren,遍曆所有的非隱藏的子View,並調用measureChild方法設定子View的測量要求。
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); } }}
方法measureChild,擷取子View的測量規格,並調用measure進行測量實際大小。
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);}
方法getChildMeasureSpec用於擷取View的測量規格要求。
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 = 0; resultMode = MeasureSpec.UNSPECIFIED; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size.... find out how // big it should be resultSize = 0; resultMode = MeasureSpec.UNSPECIFIED; } break; } return MeasureSpec.makeMeasureSpec(resultSize, resultMode); }
疑問諮詢或技術交流,請加入官方QQ群: (452379712)
傑瑞教育
出處:http://blog.csdn.net/jerehedu/
本文著作權歸煙台傑瑞教育科技有限公司和CSDN共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文串連,否則保留追究法律責任的權利。
著作權聲明:本文為博主原創文章,未經博主允許不得轉載。
Android GUI之View測量