溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

MeasureSpec在View測(cè)量中的作用是什么

發(fā)布時(shí)間:2021-07-05 16:57:47 來(lái)源:億速云 閱讀:162 作者:chen 欄目:web開發(fā)

本篇內(nèi)容主要講解“MeasureSpec在View測(cè)量中的作用是什么”,感興趣的朋友不妨來(lái)看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來(lái)帶大家學(xué)習(xí)“MeasureSpec在View測(cè)量中的作用是什么”吧!

介紹

首先,我們看下這個(gè)類:

public static class MeasureSpec {        private static final int MODE_SHIFT = 30;        private static final int MODE_MASK  = 0x3 << MODE_SHIFT;         //00后面跟30個(gè)0        public static final int UNSPECIFIED = 0 << MODE_SHIFT;        //01后面跟30個(gè)0        public static final int EXACTLY     = 1 << MODE_SHIFT;        //10后面跟30個(gè)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個(gè)0            return (measureSpec & MODE_MASK);        }         //獲取size        public static int getSize(int measureSpec) {         //替換高兩位00,保留低30位            return (measureSpec & ~MODE_MASK);        }     }

我留下了比較重要的三個(gè)方法:

  • makeMeasureSpec。用于生成一個(gè)MeasureSpec,生成的方式就是size+mode,得到一個(gè)32位的int值。

  • 獲取mode。也就是取前2位的值作為mode。

  • 獲取size。也就是取后30位的值作為size。

至此,我們至少知道了MeasureSpec是一個(gè)32位的int值,高2位為mode(測(cè)量模式),低30位為size(測(cè)量大小)。

這么做的目的主要是避免過(guò)多的對(duì)象內(nèi)存分配。

所以我們可以大致猜測(cè),這個(gè)MeasureSpec就是用來(lái)標(biāo)記View的測(cè)量參數(shù),其中測(cè)量模式可能和View具體怎么顯示有關(guān),而測(cè)量大小就是值的View實(shí)際大小。

當(dāng)然,這只是我們的初步猜測(cè)。

要搞清楚具體信息,就要從View樹的繪制測(cè)量開始說(shuō)起。

DecorView的測(cè)量

上文說(shuō)到,測(cè)量代碼是從ViewRootImpl的measureHierarchy開始的,然后會(huì)執(zhí)行到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);        }    }

很明顯,在這里就會(huì)進(jìn)行第一次MeasureSpec的計(jì)算,并且傳給了下層的mView,也就是DecorView。

那我們就來(lái)看看DecorView的MeasureSpec測(cè)量規(guī)格計(jì)算方式:

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有關(guān),其實(shí)也就是跟Window的調(diào)整有關(guān),如果Window是子窗口,那么就可以調(diào)整,比如Dialog的寬高設(shè)置為WRAP_CONTENT,那么DecorView對(duì)應(yīng)的測(cè)量規(guī)格就是AT_MOST。

到此,我們也可以初步得到這個(gè)測(cè)量規(guī)格mode的含義:

  • 如果View的值是確定大小,比如MATCH_PARENT或者固定值,那么它的測(cè)量模式就是MeasureSpec.EXACTLY。

  • 如果View的值是自適應(yīng),比如WRAP_CONTENT,那么它的測(cè)量模式就是 MeasureSpec.AT_MOST。

具體是不是這樣呢?我們繼續(xù)到下層View一探究竟。

View/ViewGroup的測(cè)量

對(duì)于具體的View/ViewGroup  測(cè)量,就涉及到另外的一個(gè)方法measureChildWithMargins,這個(gè)方法也是在很多布局中會(huì)看到,比如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。然后根據(jù) padding、margin、width 以及  parentWidthMeasureSpec 算出寬的測(cè)量模式&mdash;&mdash;childWidthMeasureSpec。

高度測(cè)量模式同理。

到此,我們的認(rèn)識(shí)又前進(jìn)了一步,對(duì)于子View的測(cè)量模式MeasureSpec肯定是和兩個(gè)元素有關(guān):

  • 子View的LayoutParams(包括margin,width)

  • 父View的MeasureSpec (再加上padding)

繼續(xù)看看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);     }

代碼其實(shí)很簡(jiǎn)單,就是對(duì)子View的LayoutParams和父View的specMode、specSize,共同計(jì)算出子View的MeasureSpec。

舉其中一個(gè)例子,當(dāng)父view的測(cè)量模式為MeasureSpec.EXACTLY,子View寬的LayoutParams為MATCH_PARENT。想象一下,這種情況,子View的寬肯定就會(huì)占滿父View的大小,所以子View的測(cè)量模式中的mode肯定就是確定值,為MeasureSpec.EXACTLY,而大小就是父View的大小了。對(duì)應(yīng)的代碼就是:

case MeasureSpec.AT_MOST: if (childDimension == LayoutParams.MATCH_PARENT) {     // Child wants to be our size. So be it.     resultSize = size;     resultMode = MeasureSpec.EXACTLY; }

綜合所有的情況,很經(jīng)典的一張表格就來(lái)了:

MeasureSpec在View測(cè)量中的作用是什么

這里我們也可以明確了MeasureSpec中mode的含義:

  • MeasureSpec.EXACTLY。父View可以確定子View的精確大小,比如子View大小是固定的值,在所有的情況下都會(huì)是EXACTLY模式。

  • MeasureSpec.AT_MOST。父View給定一個(gè)最大的值,意思是子View大小可以不確定,但是肯定不能超過(guò)某個(gè)最大的值,例如窗口的大小。

  • MeasureSpec.UNSPECIFIED。父View對(duì)子View完全沒(méi)限制,要多大給多大。這個(gè)模式似乎聽起來(lái)有點(diǎn)奇怪?待會(huì)我們?cè)偌?xì)談。

到此,似乎就結(jié)束了?當(dāng)然沒(méi)啦,獲取子View的MeasureSpec之后,子View又會(huì)怎么處理呢?

View對(duì)于MeasureSpec的處理

繼續(xù)上文,測(cè)量子View的測(cè)量規(guī)格之后,會(huì)調(diào)用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方法,繼續(xù)看看:

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);  }

哦~最后原來(lái)是給子View的measuredWidth和measuredHeight賦值了,所賦的值就是getDefaultSize方法返回的大小。

而這個(gè)measuredWidth是干嘛的呢?搜索一下:

public final int getMeasuredWidth() {  //MEASURED_SIZE_MASK用于限制大小的         return mMeasuredWidth & MEASURED_SIZE_MASK;     }

這不就是我們獲取view的大小調(diào)用的方法嗎?所以小結(jié)一下:

  • 父view通過(guò)父View的MeasureSpec和子View的LayoutParams算出了子View的MeasureSpec。

  • 然后子View通過(guò)MeasureSpec計(jì)算了measuredWidth

  • 而這個(gè)measuredWidth也就是我們可以獲取View寬高所調(diào)用的方法。

最后就是看看getDefaultSize方法干了啥,也就是驗(yàn)證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這兩種常用的情況下,確實(shí)是等于測(cè)量大小specSize的。

只是在一個(gè)特殊情況,也就是UNSPECIFIED的時(shí)候,這個(gè)大小會(huì)等于getSuggestedMinimumWidth()方法的大小。

問(wèn)題來(lái)了,UNSPECIFIED模式到底是啥,getSuggestedMinimumWidth()方法又做了什么?

UNSPECIFIED

很多文章會(huì)忽略這個(gè)模式,其實(shí)它也是很重要的,在前兩天的討論群中,我們還討論了這個(gè)問(wèn)題,一起看看吧~

首先,我們看看什么時(shí)候會(huì)存在UNSPECIFIED模式呢?它的概念是父View對(duì)子View的大小沒(méi)有限制,很容易想到的一個(gè)控件就是ScrollView,那么在ScrollView中肯定有對(duì)這個(gè)模式的設(shè)置:

@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);    }

沒(méi)錯(cuò),在ScrollView中重寫了measureChildWithMargins方法,比對(duì)下剛才ViewGroup的measureChildWithMargins方法,發(fā)現(xiàn)有什么不對(duì)了嗎?

childWidthMeasureSpec的計(jì)算沒(méi)有什么變化,還是調(diào)用了getChildMeasureSpec方法,但是childHeightMeasureSpec不對(duì)勁了,直接調(diào)用了makeSafeMeasureSpec方法生成了MeasureSpec,而且!而且!直接把SpecMode設(shè)置成了MeasureSpec.UNSPECIFIED。

也就是對(duì)于子View的高度是無(wú)限制的,這也符合ScrollView的理念。

所以當(dāng)ScrollView嵌套一個(gè)普通View的時(shí)候,就會(huì)觸發(fā)剛才getDefaultSize中UNSPECIFIED的邏輯,也就是View的實(shí)際大小為getSuggestedMinimumWidth的大小。

繼續(xù)看看getSuggestedMinimumWidth到底獲取的是什么大?。?/p>

protected int getSuggestedMinimumWidth() {         return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());     }

就一句代碼:

  • 如果view的背景為null,則等于最小寬度mMinWidth。

  • 如果view的背景不為null,則等于最小寬度和 背景的最小寬度 中取較大值。

所以如果View沒(méi)有設(shè)置背景,沒(méi)有設(shè)置mMinWidth,那么ScrollView嵌套View的情況,View的寬度就是為0,即使設(shè)置了固定值也沒(méi)用。

這只是UNSPECIFIED在普通View中的處理情況,不同的情況對(duì)UNSPECIFIED的處理方式都不一樣,比如TextView、RecycleView等等。

下次會(huì)專門出一篇UNSPECIFIED的文章,到時(shí)候見。

總結(jié)

今天回顧了MeasureSpec的相關(guān)知識(shí)點(diǎn):

  • MeasureSpec的基本概念:

  • MeasureSpec為一個(gè)32位的int值。

  • SpecMode為高兩位,一共三種模式,代表父View對(duì)子View的大小限制模式,比如最大可用大小&mdash;&mdash;AT_MOST。

SpecSize為低30位,代表父View給子View測(cè)量好的寬高。這個(gè)寬高大概率等于View的實(shí)際寬高,但是也有例外情況,也就是UNSPECIFIED的情況。

測(cè)量流程中的MeasureSpec:

  • View輸?shù)臏y(cè)量流程開始于ViewRootImpl的measureHierarchy,也是在這里開始了第一次MeasureSpec的計(jì)算。

  • 第一次MeasureSpec的計(jì)算也就是DecorView的MeasureSpec計(jì)算,是通過(guò)自身的LayoutParams相關(guān),也就是和Window大小有關(guān)。

  • 然后就開始子View/ViewGroup的MeasureSpec計(jì)算,是通過(guò)父View的MeasureSpec和子View的LayoutParams相關(guān)。

  • 計(jì)算完子View的MeasureSpec之后,就開始調(diào)用onMeasure方法,計(jì)算出View的實(shí)際大小。

  • 如果是UNSPECIFIED模式,實(shí)際大小為。否則實(shí)際大小就等于計(jì)算好的specSize。

到此,相信大家對(duì)“MeasureSpec在View測(cè)量中的作用是什么”有了更深的了解,不妨來(lái)實(shí)際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI