筆者以前有一篇文章已經使用onMeasure()解決了listview與scollview的顯示衝突問題,博客地址以下:html
onMeasure簡單方法 完美解決ListView與ScollView衝突問題!java
在此就針對View的測量以及onMeasure()涉及的幾個問題作一個詳細解釋:android
1、MeasureSpec的概念:佈局
MeasureSpec經過將SpecMode和SpecSize打包成一個int值來避免過多的對象內存分配,爲了方便操做,其提供了打包和解包的方法。SpecMode和SpecSize也是一個int值,一組SpecMode和SpecSize能夠打包爲一個MeasureSpec,而一個MeasureSpec能夠經過解包的形式來得出其原始的SpecMode和SpecSize。post
讀者只要記住如下一句話便可:spa
MeasureSpec的值由specSize和specMode共同組成的,其中specSize記錄的是大小,specMode記錄的是規格。.net
2、SpecMode的三種模式:htm
1. EXACTLY對象
當咱們將控件的「layout_width」屬性或者「layout_height」屬性指定爲具體數值時,好比「android:layout_width="200dp"」,或者指定爲「match_parent」時,系統會使用這個模式。blog
2. AT_MOST
當控件的「layout_width」屬性或者「layout_height」屬性設置爲「wrap_content」時,控件大小通常會隨着內容的大小而變化,可是不管多大,也不能超過父控件的尺寸。
3. UNSPECIFIED
表示開發人員能夠將視圖按照本身的意願設置成任意的大小,沒有任何限制。這種狀況比較少見,通常在繪製自定義View的時候纔會用到。
3、View的測量到底和什麼有關呢?
要探其原理,首先要和你們說明一點,一個View只須要MeasureSpec肯定,那麼在onMeasure中就能夠測量它的寬高,因此咱們能夠將問題直接轉化成「一個View的MeasureSpec是如何肯定的呢?」
普通的View的measure過程由VIewGroup傳遞而來,此處咱們根據源碼來作一個解釋,先看一下ViewGroup中的measureChildWithMargins():
- 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的寬高,都是經過getChildMeasureSpec()這個方法得到的,那麼這裏面又是怎麼實現的呢?咱們不妨Control+左鍵點進去看一下,代碼以下:
- 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) {
-
- case MeasureSpec.EXACTLY:
- if (childDimension >= 0) {
- resultSize = childDimension;
- resultMode = MeasureSpec.EXACTLY;
- } else if (childDimension == LayoutParams.MATCH_PARENT) {
-
- resultSize = size;
- resultMode = MeasureSpec.EXACTLY;
- } else if (childDimension == LayoutParams.WRAP_CONTENT) {
-
-
- resultSize = size;
- resultMode = MeasureSpec.AT_MOST;
- }
- break;
-
-
- case MeasureSpec.AT_MOST:
- if (childDimension >= 0) {
-
- resultSize = childDimension;
- resultMode = MeasureSpec.EXACTLY;
- } else if (childDimension == LayoutParams.MATCH_PARENT) {
-
-
- resultSize = size;
- resultMode = MeasureSpec.AT_MOST;
- } else if (childDimension == LayoutParams.WRAP_CONTENT) {
-
-
- resultSize = size;
- resultMode = MeasureSpec.AT_MOST;
- }
- break;
-
-
- case MeasureSpec.UNSPECIFIED:
- if (childDimension >= 0) {
-
- resultSize = childDimension;
- resultMode = MeasureSpec.EXACTLY;
- } else if (childDimension == LayoutParams.MATCH_PARENT) {
-
-
- resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
- resultMode = MeasureSpec.UNSPECIFIED;
- } else if (childDimension == LayoutParams.WRAP_CONTENT) {
-
-
- resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
- resultMode = MeasureSpec.UNSPECIFIED;
- }
- break;
- }
- return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
- }
代碼比較長,讀者能夠比較焦急——不用懼怕,咱們不須要徹底理解它的原理,咱們只須要知道View的測量是如何實現的就好了。
看到源碼方法中的三個參數,而且比較measureChildWithMargins()方法中傳遞給getChildMeasureSpec()三個值,咱們很快就能夠理解,一個View的測量過程是由父佈局的MeasureSpec和該View的LayoutParams決定的。
讀者能夠看measureChildWithMargins()中以下代碼:
- final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
- mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
- + widthUsed, lp.width);
要測量子部局的寬度的MeasureSpec,須要傳入3個參數:
第一個參數:父佈局的寬度的MeasureSpec
第二個參數:子部局的padding值,子部局的LayoutParams的Margin值
第三個參數:子部局的LayoutParams的寬度
若是對View的測量過程由更深刻的求知慾的,推薦讀者能夠本身看一下源碼。