Android自定義View之OnMeasure過程淺析


自定義view的流程分為measure政钟、layoutdraw三個主要步驟潮秘,今天我們通過源碼來分下下measure的過程我們從頂級view開始琼开,頂級viewDecorViewview的事件都是先經(jīng)過這個DecorView,接下來我們來看看這個DecorViewMeasureSpec的創(chuàng)建過程:
進入ViewRootImpl中枕荞,查看measureHierarchy方法柜候,有如下代碼:

final DisplayMetrics packageMetrics = res.getDisplayMetrics();  
           res.getValue(com.android.internal.R.dimen.config_prefDialogWidth, mTmpValue, true);  
           int baseSize = 0;  
           if (mTmpValue.type == TypedValue.TYPE_DIMENSION) {  
               baseSize = (int)mTmpValue.getDimension(packageMetrics);  
           }  
           if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": baseSize=" + baseSize  
                   + ", desiredWindowWidth=" + desiredWindowWidth);  
           if (baseSize != 0 && desiredWindowWidth > baseSize) {  
               childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);  
               childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);  
               performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);  

這里只是截選一部分的源碼, 我們看到這個baseSize躏精, 其實就是屏幕的尺寸大小渣刷, 獲取寬的MeasureSpc的方法:

childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);

這里傳入的參數(shù)是屏幕尺寸以及DecorView自身的大小, 接著我們來看 getRootMeasureSpec方法:

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

就是這個方法確定了DecorViewMeasureSpec矗烛, 這里分了三種情況辅柴,

  1. 如果傳入的view大小為math_parent,那么這個view的mode為EXACTLY瞭吃, 大小為屏幕的尺寸.
  1. 如果傳入的view大小為wrap_content碌嘀,那么這個view的mode為AT_MOST,大小為屏幕的尺寸.
  2. 如果傳入的view大小為一個具體的值,那么這個view的mode為EXACTLY虱而,大小為view本身大小筏餐。

以上就是DecorView的MeaureSpec的整個創(chuàng)建的過程了。

看了頂級view之后我們來看普通的view牡拇, 普通的view的measure過程是由viewgroup傳遞過來的,接著我們來看看viewgroupmeasureChildWithMargins方法:

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的MeasureSpec穆律,并且將其傳入子view的measure方法中惠呼, 這里重點來看下viewgroup是如何創(chuàng)建子view的MeasuerSpec的。來看getChildMeasureSpec方法內(nèi)部的實現(xiàn):

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

這個方法很長峦耘,但是我們只需要注意到AT_MOSTEXACTLY這兩種情況就行剔蹋,稍微分析下這個過程:
首先要理解這個方法的三個參數(shù), 第一個是父view的MeasureSpec, 第二個是父view已占用的大小辅髓,第三個是view的LayoutParams的大小泣崩,如果不理解可以看看ViewGroupMeasureChildWithMargins方法中的調(diào)用:

final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,  
               mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin  
                       + widthUsed, lp.width);  

第二個參數(shù)很長 , mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin + withUsed 洛口,這些所有的值都有一個共同特點矫付,就是這些位置是不能擺放任何view的,即父view已經(jīng)占用的地盤第焰,現(xiàn)在是不是對參數(shù)更加理解了呢买优。

接著我們回到getChildMeasureSpec方法中繼續(xù)看看viewGroup到底是怎么創(chuàng)建view的MeasureSpec的。

第一步: 根據(jù)參數(shù)一,即傳入的父view的MeasureSpec獲得父view的ModeSize杀赢。這里的第三行代碼:

int size = Math.max(0, specSize - padding);  

這個size表示取0與父容器中可占用的位置的最大值烘跺,可以直接理解為父view的大小。

第二步:根據(jù)父view的Mode分情況處理脂崔, 到這一步我們應(yīng)該就清楚為什么說view的大小是由父view的MeasureSpec與本身LayoutParmas大小共同決定的吧滤淳。

這里我們依然只看AT_MOSTEXACTLY兩種情況,

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;  

這里有個比較難以理解的值就是childDimension > 0 砌左,這個其實就表示view的大小是一個具體的值比如100dp , 因為view的match_parentwrap_content在系統(tǒng)內(nèi)部定義的都是負數(shù)脖咐,一個是-1. 一個是-2 ,所以判斷childDimension > 0即绊困,view的大小為一個具體的值文搂。
接著就比較好理解了, 我們來稍微總結(jié)下:

無論父view是match_parent還是 wrap_content 秤朗,只要view是一個具體的值煤蹭,view的Mode永遠都是EXACTLY, 大小均是view本身定義的大小。

父view模式如果是EXACTLY, ---> 子view如果是mathch_parent 取视,那么子view的大小是父view的大小硝皂,模式也跟父view一樣為EXACTLY. 子view如果是wrap_content, 大小還是父view的大小作谭, 模式為AT_MOST

父view模式如果是AT_MOST , --- > 子view如果是math_parent稽物, 那么子view大小為父view大小, 模式與父view一樣都是AT_MOST, 子view如果是wrap_content折欠, 子view大小為父view大小贝或, 模式為AT_MOST


上面說的有點繞,但其實我們只需要記住一點锐秦, 無論上面那種情況咪奖,子view在wrap_content下,大小都是父view的大小酱床, 到這里我們是不是就能理解為什么在自定義view的過程中如果不重寫onMeasure羊赵, wrap_content是和match_parent是一個效果了吧。

以上過程是viewGroup中創(chuàng)建子view的MeasureSpec的過程扇谣,
有了這個MeasureSpec昧捷,測量子view大小就很簡單了,我們可以看到在ViewGroup獲取到子view的MeasureSpec之后罐寨,傳入到子view的measure方法中:

child.measure(childWidthMeasureSpec, childHeightMeasureSpec);  

進入到view的measure方法,

不知不覺我們已經(jīng)從viewgroup進入到了view的測量過程靡挥,

這里是不是突然意識到,ViewGroup根本沒有測繪自己本身啊衩茸, 只是獲取到子view的MeasureSpec然后傳入子view的measure方法里去芹血,這是因為ViewGroup是個抽象類贮泞,本身并沒有定義測量的過程, ViewGrouponMeasure需要各個子類去實現(xiàn)幔烛,比如LinearLayout 啃擦、 RelativeLayout等等,并且每個子類的測量過程都不一樣饿悬,這個我們后面會講令蛉, 現(xiàn)在我們還是接著看view的Measure過程。

上面說到viewgroup將創(chuàng)建的子view的MeasureSpec傳入到了view的Measure方法中狡恬, 那么我們就來看看View的Measure方法:

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {  
       boolean optical = isLayoutModeOptical(this);  
       if (optical != isLayoutModeOptical(mParent)) {  
           Insets insets = getOpticalInsets();  
           int oWidth  = insets.left + insets.right;  
           int oHeight = insets.top  + insets.bottom;  
           widthMeasureSpec  = MeasureSpec.adjust(widthMeasureSpec,  optical ? -oWidth  : oWidth);  
           heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);  
       }  
  
       // Suppress sign extension for the low bytes  
       long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;  
       if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);  
  
       final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;  
  
       // Optimize layout by avoiding an extra EXACTLY pass when the view is  
       // already measured as the correct size. In API 23 and below, this  
       // extra pass is required to make LinearLayout re-distribute weight.  
       final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec  
               || heightMeasureSpec != mOldHeightMeasureSpec;  
       final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY  
               && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;  
       final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)  
               && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);  
       final boolean needsLayout = specChanged  
               && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);  
  
       if (forceLayout || needsLayout) {  
           // first clears the measured dimension flag  
           mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;  
  
           resolveRtlPropertiesIfNeeded();  
  
           int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);  
           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;  
           }  
  
           // flag not set, setMeasuredDimension() was not invoked, we raise  
           // an exception to warn the developer  
           if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {  
               throw new IllegalStateException("View with id " + getId() + ": "  
                       + getClass().getName() + "#onMeasure() did not set the"  
                       + " measured dimension by calling"  
                       + " setMeasuredDimension()");  
           }  
  
           mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;  
       }  
  
       mOldWidthMeasureSpec = widthMeasureSpec;  
       mOldHeightMeasureSpec = heightMeasureSpec;  
  
       mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |  
               (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension  
   }  

這個方法真是又臭又長珠叔。。弟劲。講道理的話其實我也看不懂祷安, 但是我們只需要注意到一點, 就是這個方法調(diào)用了OnMeasure方法兔乞!
也就是說measure --> OnMeasure

OnMeasure就簡單了 :

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
      setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),  
              getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));  
  } 

很簡潔對不汇鞭,但是簡潔并不代表簡單, 這里套了好幾層庸追。霍骄。。 不要被迷惑 淡溯, 我們看最外層其實就是setMeasureDimension().
設(shè)置寬和高读整, 這個寬和高是在 getDefaultSize方法里返回的, 所以我們來看看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;  
    }  

如果我們忽略掉UNSPECIFIED情況的話咱娶, 我們會發(fā)現(xiàn)第一個參數(shù)size根本用不到米间。。膘侮。
也就是說view的大小其實就是父view給他創(chuàng)建的MeasureSpec中的size大小车伞。

這也進一步說明,view在wrap_content情況下 喻喳,大小還是會跟父view大小一樣, 所以我們需要在自定義view的時候重寫OnMeasure困曙。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末表伦,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子慷丽,更是在濱河造成了極大的恐慌蹦哼,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件要糊,死亡現(xiàn)場離奇詭異纲熏,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門局劲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來勺拣,“玉大人,你說我怎么就攤上這事鱼填∫┯校” “怎么了?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵苹丸,是天一觀的道長愤惰。 經(jīng)常有香客問我,道長赘理,這世上最難降的妖魔是什么宦言? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮商模,結(jié)果婚禮上奠旺,老公的妹妹穿的比我還像新娘。我一直安慰自己阻桅,他們只是感情好凉倚,可當我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著嫂沉,像睡著了一般稽寒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上趟章,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天杏糙,我揣著相機與錄音,去河邊找鬼蚓土。 笑死宏侍,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的蜀漆。 我是一名探鬼主播谅河,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼确丢!你這毒婦竟也來了绷耍?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤鲜侥,失蹤者是張志新(化名)和其女友劉穎褂始,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體描函,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡崎苗,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年狐粱,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片胆数。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡肌蜻,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出幅慌,到底是詐尸還是另有隱情宋欺,我是刑警寧澤,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布胰伍,位于F島的核電站齿诞,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏骂租。R本人自食惡果不足惜祷杈,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望渗饮。 院中可真熱鬧但汞,春花似錦、人聲如沸互站。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽胡桃。三九已至踩叭,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間翠胰,已是汗流浹背容贝。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留之景,地道東北人斤富。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像锻狗,于是被迫代替她去往敵國和親满力。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,592評論 2 353

推薦閱讀更多精彩內(nèi)容