您好,登錄后才能下訂單哦!
本篇內容主要講解“MeasureSpec在View測量中的作用是什么”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“MeasureSpec在View測量中的作用是什么”吧!
首先,我們看下這個類:
public static class MeasureSpec { private static final int MODE_SHIFT = 30; private static final int MODE_MASK = 0x3 << MODE_SHIFT; //00后面跟30個0 public static final int UNSPECIFIED = 0 << MODE_SHIFT; //01后面跟30個0 public static final int EXACTLY = 1 << MODE_SHIFT; //10后面跟30個0 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); } } //獲取mode public static int getMode(int measureSpec) { //保留高2位,剩下30個0 return (measureSpec & MODE_MASK); } //獲取size public static int getSize(int measureSpec) { //替換高兩位00,保留低30位 return (measureSpec & ~MODE_MASK); } }
我留下了比較重要的三個方法:
makeMeasureSpec。用于生成一個MeasureSpec,生成的方式就是size+mode,得到一個32位的int值。
獲取mode。也就是取前2位的值作為mode。
獲取size。也就是取后30位的值作為size。
至此,我們至少知道了MeasureSpec是一個32位的int值,高2位為mode(測量模式),低30位為size(測量大小)。
這么做的目的主要是避免過多的對象內存分配。
所以我們可以大致猜測,這個MeasureSpec就是用來標記View的測量參數,其中測量模式可能和View具體怎么顯示有關,而測量大小就是值的View實際大小。
當然,這只是我們的初步猜測。
要搞清楚具體信息,就要從View樹的繪制測量開始說起。
上文說到,測量代碼是從ViewRootImpl的measureHierarchy開始的,然后會執行到performMeasure方法:
private void measureHierarchy(){ childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width); childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height); performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); } private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) { try { mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } }
很明顯,在這里就會進行第一次MeasureSpec的計算,并且傳給了下層的mView,也就是DecorView。
那我們就來看看DecorView的MeasureSpec測量規格計算方式:
private static int getRootMeasureSpec(int windowSize, int rootDimension) { int measureSpec; switch (rootDimension) { case ViewGroup.LayoutParams.MATCH_PARENT: // Window can't resize. Force root view to be windowSize. measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY); break; case ViewGroup.LayoutParams.WRAP_CONTENT: // Window can resize. Set max size for root view. measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST); break; default: // Window wants to be an exact size. Force root view to be that size. measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY); break; } return measureSpec; }
所以DecorView是和它的LayoutParams有關,其實也就是跟Window的調整有關,如果Window是子窗口,那么就可以調整,比如Dialog的寬高設置為WRAP_CONTENT,那么DecorView對應的測量規格就是AT_MOST。
到此,我們也可以初步得到這個測量規格mode的含義:
如果View的值是確定大小,比如MATCH_PARENT或者固定值,那么它的測量模式就是MeasureSpec.EXACTLY。
如果View的值是自適應,比如WRAP_CONTENT,那么它的測量模式就是 MeasureSpec.AT_MOST。
具體是不是這樣呢?我們繼續到下層View一探究竟。
對于具體的View/ViewGroup 測量,就涉及到另外的一個方法measureChildWithMargins,這個方法也是在很多布局中會看到,比如LinearLayout。
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); }
代碼不多,首先獲取子View的LayoutParams。然后根據 padding、margin、width 以及 parentWidthMeasureSpec 算出寬的測量模式——childWidthMeasureSpec。
高度測量模式同理。
到此,我們的認識又前進了一步,對于子View的測量模式MeasureSpec肯定是和兩個元素有關:
子View的LayoutParams(包括margin,width)
父View的MeasureSpec (再加上padding)
繼續看看getChildMeasureSpec方法:
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 = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; resultMode = MeasureSpec.UNSPECIFIED; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size.... find out how // big it should be resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; resultMode = MeasureSpec.UNSPECIFIED; } break; } //noinspection ResourceType return MeasureSpec.makeMeasureSpec(resultSize, resultMode); }
代碼其實很簡單,就是對子View的LayoutParams和父View的specMode、specSize,共同計算出子View的MeasureSpec。
舉其中一個例子,當父view的測量模式為MeasureSpec.EXACTLY,子View寬的LayoutParams為MATCH_PARENT。想象一下,這種情況,子View的寬肯定就會占滿父View的大小,所以子View的測量模式中的mode肯定就是確定值,為MeasureSpec.EXACTLY,而大小就是父View的大小了。對應的代碼就是:
case MeasureSpec.AT_MOST: if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size. So be it. resultSize = size; resultMode = MeasureSpec.EXACTLY; }
綜合所有的情況,很經典的一張表格就來了:
這里我們也可以明確了MeasureSpec中mode的含義:
MeasureSpec.EXACTLY。父View可以確定子View的精確大小,比如子View大小是固定的值,在所有的情況下都會是EXACTLY模式。
MeasureSpec.AT_MOST。父View給定一個最大的值,意思是子View大小可以不確定,但是肯定不能超過某個最大的值,例如窗口的大小。
MeasureSpec.UNSPECIFIED。父View對子View完全沒限制,要多大給多大。這個模式似乎聽起來有點奇怪?待會我們再細談。
到此,似乎就結束了?當然沒啦,獲取子View的MeasureSpec之后,子View又會怎么處理呢?
繼續上文,測量子View的測量規格之后,會調用child.measure方法。
protected void measureChildWithMargins() { 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); } public final void measure(int widthMeasureSpec, int heightMeasureSpec) { onMeasure(widthMeasureSpec, heightMeasureSpec); }
child.measure方法也就是View的measure方法,也就是走到了onMeasure方法,繼續看看:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); } protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) { if (optical != isLayoutModeOptical(mParent)) { measuredWidth += optical ? opticalWidth : -opticalWidth; measuredHeight += optical ? opticalHeight : -opticalHeight; } setMeasuredDimensionRaw(measuredWidth, measuredHeight); }
哦~最后原來是給子View的measuredWidth和measuredHeight賦值了,所賦的值就是getDefaultSize方法返回的大小。
而這個measuredWidth是干嘛的呢?搜索一下:
public final int getMeasuredWidth() { //MEASURED_SIZE_MASK用于限制大小的 return mMeasuredWidth & MEASURED_SIZE_MASK; }
這不就是我們獲取view的大小調用的方法嗎?所以小結一下:
父view通過父View的MeasureSpec和子View的LayoutParams算出了子View的MeasureSpec。
然后子View通過MeasureSpec計算了measuredWidth
而這個measuredWidth也就是我們可以獲取View寬高所調用的方法。
最后就是看看getDefaultSize方法干了啥,也就是驗證MeasureSpec中size是不是就是我們要獲取的View的寬高呢?
getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec) 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; }
可以看到,在AT_MOST和EXACTLY這兩種常用的情況下,確實是等于測量大小specSize的。
只是在一個特殊情況,也就是UNSPECIFIED的時候,這個大小會等于getSuggestedMinimumWidth()方法的大小。
問題來了,UNSPECIFIED模式到底是啥,getSuggestedMinimumWidth()方法又做了什么?
很多文章會忽略這個模式,其實它也是很重要的,在前兩天的討論群中,我們還討論了這個問題,一起看看吧~
首先,我們看看什么時候會存在UNSPECIFIED模式呢?它的概念是父View對子View的大小沒有限制,很容易想到的一個控件就是ScrollView,那么在ScrollView中肯定有對這個模式的設置:
@Override 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 usedTotal = mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin + heightUsed; final int childHeightMeasureSpec = MeasureSpec.makeSafeMeasureSpec( Math.max(0, MeasureSpec.getSize(parentHeightMeasureSpec) - usedTotal), MeasureSpec.UNSPECIFIED); child.measure(childWidthMeasureSpec, childHeightMeasureSpec); }
沒錯,在ScrollView中重寫了measureChildWithMargins方法,比對下剛才ViewGroup的measureChildWithMargins方法,發現有什么不對了嗎?
childWidthMeasureSpec的計算沒有什么變化,還是調用了getChildMeasureSpec方法,但是childHeightMeasureSpec不對勁了,直接調用了makeSafeMeasureSpec方法生成了MeasureSpec,而且!而且!直接把SpecMode設置成了MeasureSpec.UNSPECIFIED。
也就是對于子View的高度是無限制的,這也符合ScrollView的理念。
所以當ScrollView嵌套一個普通View的時候,就會觸發剛才getDefaultSize中UNSPECIFIED的邏輯,也就是View的實際大小為getSuggestedMinimumWidth的大小。
繼續看看getSuggestedMinimumWidth到底獲取的是什么大小:
protected int getSuggestedMinimumWidth() { return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth()); }
就一句代碼:
如果view的背景為null,則等于最小寬度mMinWidth。
如果view的背景不為null,則等于最小寬度和 背景的最小寬度 中取較大值。
所以如果View沒有設置背景,沒有設置mMinWidth,那么ScrollView嵌套View的情況,View的寬度就是為0,即使設置了固定值也沒用。
這只是UNSPECIFIED在普通View中的處理情況,不同的情況對UNSPECIFIED的處理方式都不一樣,比如TextView、RecycleView等等。
下次會專門出一篇UNSPECIFIED的文章,到時候見。
今天回顧了MeasureSpec的相關知識點:
MeasureSpec的基本概念:
MeasureSpec為一個32位的int值。
SpecMode為高兩位,一共三種模式,代表父View對子View的大小限制模式,比如最大可用大小——AT_MOST。
SpecSize為低30位,代表父View給子View測量好的寬高。這個寬高大概率等于View的實際寬高,但是也有例外情況,也就是UNSPECIFIED的情況。
測量流程中的MeasureSpec:
View輸的測量流程開始于ViewRootImpl的measureHierarchy,也是在這里開始了第一次MeasureSpec的計算。
第一次MeasureSpec的計算也就是DecorView的MeasureSpec計算,是通過自身的LayoutParams相關,也就是和Window大小有關。
然后就開始子View/ViewGroup的MeasureSpec計算,是通過父View的MeasureSpec和子View的LayoutParams相關。
計算完子View的MeasureSpec之后,就開始調用onMeasure方法,計算出View的實際大小。
如果是UNSPECIFIED模式,實際大小為。否則實際大小就等于計算好的specSize。
到此,相信大家對“MeasureSpec在View測量中的作用是什么”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。