文章大部分內容引自Android開發藝術探索
MeasureSpec
-
系統內部是通過MeasureSpec來進行view的測量,但是正常情況下我們無法使用它,但是我們可以使用LayoutParams,在view測量的時候,系統會將LayoutParams在父容器的約束下轉換成對應的MeasureSpec,然後再根據這個MeasureSpec確定view測量後的寬高。(也就是說子元素的MeasureSpec是又父容器傳遞的,是有自身的LayoutParams有父容器的MeasureSpec共同決定的)
-
對於頂級View(DecorView)其MeasureSpec由自身的LayoutParams和窗口的尺寸共同決定;但是對於普通View(我們佈局中的view)來說,其measure過程由viewGroup傳遞而來(
measureChildWithMargins()
),viewGroup會在其onMeasure()
方法中調用measureChildWithMargins()
/** * sdk版本29 * Ask one of the children of this view to measure itself, taking into * account both the MeasureSpec requirements for this view and its padding * and margins. The child must have MarginLayoutParams The heavy lifting is * done in getChildMeasureSpec. 大部分工作在getChildMeasureSpec中完成 * * @param child The child to measure * @param parentWidthMeasureSpec The width requirements for this view * @param widthUsed Extra space that has been used up by the parent * horizontally (possibly by other children of the parent) * @param parentHeightMeasureSpec The height requirements for this view * @param heightUsed Extra space that has been used up by the parent * vertically (possibly by other children of the parent) */ 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); }
可以看出上述方法會對子元素進行measure,在調用子元素的measure之前會先通過
getChildMeasureSpec
方法來得到子元素的MeasureSpec。從代碼上驗證又可以得出子元素的MeasureSpec的創建與父容器的MeasureSpec和子元素本身的LayoutParams有關,此外還和view的padding和margin有關。/** * 測量孩子最重要的部分,計算出子元素的MeasureSpec並傳遞給它,此方法表明了一個子view的寬或高的合適的尺寸 * Does the hard part of measureChildren: figuring out the MeasureSpec to * pass to a particular child. This method figures out the right MeasureSpec * for one dimension (height or width) of one child view. * * 目標是從父元素自身的MeasureSpec和子元素的LayoutParams來得出最合適的結果(子元素的MeasureSpec) * The goal is to combine information from our MeasureSpec with the * LayoutParams of the child to get the best possible results. For example, * if the this view knows its size (because its MeasureSpec has a mode of * EXACTLY), and the child has indicated in its LayoutParams that it wants * to be the same size as the parent, the parent should ask the child to * layout given an exact size. * * @param spec The requirements for this view * @param padding The padding of this view for the current dimension and * margins, if applicable * @param childDimension How big the child wants to be in the current * dimension * @return a MeasureSpec integer for the child */ 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,父元素是MATCH_PARENT或者確定的尺寸 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. 子元素是WRAP_CONTENT是,子元素的尺寸就是父元素剩餘的所有尺寸,也就是說會 充滿父佈局,這就是爲什麼我們自定義view時需要處理WRAP_CONTENT來達到自適應尺寸 resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // Parent has imposed a maximum size on us,父元素是WRAP_CONTENT 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,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams共同決定。那麼針對不同的父容器和View本身不同的LayoutParams,View就可以有多種MeasureSpec。The last:當View採用固定寬高時,不管父容器的MeasureSpec是什麼,View的MeasureSpec都是精確模式(EXACTLY
)並且其大小遵循LayoutParams的大小;當View的寬/高是MATCH_PARENT
時,如果父容器是精確模式EXACTLY
,那麼View也是精確模式並且其大小是父容器的剩餘空間,如果父容器是最大模式AT_MOST
,那麼View也是最大模式並且大小不會超過父容器的剩餘空間;當View的寬/高是WRAP_CONTENT
,不管父容器的模式是精準模式還是最大模式,View的模式總是最大模式AT_MOST
並且其大小不能超過---------------------------------------------------------------------------------------------------------------------精確模式EXACTLY
,父容器已經檢測出View的精確大小,這個時候View的最終大小就是SpecSize(int specSize = MeasureSpec.getSize(spec);
)所指定的值,它對應LayoutParams中的MATCH_PARENT和具體數值這兩種模式;最大模式AT_MOST
,父容器指定了一個可用大小及SpecSize,View的大小不能大於這個值,具體是什麼要看不同View的具體實現,它對應LayoutParams中的WRAP_CONTENT