Android自定義View:測量規(guī)格(MeasureSpec)到底是什么拨扶?


前言

  • 在了解自定義View三大流程的Measure過程前,我們需要了解一個重要基礎(chǔ):MeasureSpec
  • 今天茁肠,我將全面解析 MeasureSpec類的相關(guān)知識患民,希望你們會喜歡

Carson帶你學(xué)Android自定義View文章系列:
Carson帶你學(xué)Android:自定義View基礎(chǔ)
Carson帶你學(xué)Android:一文梳理自定義View工作流程
Carson帶你學(xué)Android:自定義View繪制準備-DecorView創(chuàng)建
Carson帶你學(xué)Android:自定義View Measure過程
Carson帶你學(xué)Android:自定義View Layout過程
Carson帶你學(xué)Android:自定義View Draw過程
Carson帶你學(xué)Android:手把手教你寫一個完整的自定義View
Carson帶你學(xué)Android:Canvas類全面解析
Carson帶你學(xué)Android:Path類全面解析


目錄

示意圖

1. 簡介


2. 組成

測量規(guī)格(MeasureSpec)是由測量模式(mode)和測量大小(size)組成,共32位(int類型)垦梆,其中:

  • 測量模式(mode):占測量規(guī)格(MeasureSpec)的高2位匹颤;
  • 測量大小(size):占測量規(guī)格(MeasureSpec)的低30位。

其中托猩,測量模式(Mode)的類型有三種

image.png

3. 具體使用

  • 測量規(guī)格(MeasureSpec)的封裝類是:MeasureSpec類
  • MeasureSpec類用一個變量封裝了測量模式(mode)和測量大小(size):通過使用二進制印蓖,將測量模式(mode)和測量大小(size)打包成一個int值,并提供了打包和解包的方法京腥,這樣的做法是為了減少對象內(nèi)存分配和提高存取效率赦肃。具體使用如下所示:
// 1. 獲取測量模式(Mode)
int specMode = MeasureSpec.getMode(measureSpec)

// 2. 獲取測量大小(Size)
int specSize = MeasureSpec.getSize(measureSpec)

// 3. 通過Mode 和 Size 生成新的SpecMode
int measureSpec=MeasureSpec.makeMeasureSpec(size, mode);

4. 源碼分析

public class MeasureSpec {

  // 進位大小 = 2的30次方
  // int的大小為32位公浪,所以進位30位 = 使用int的32和31位做標志位
  private static final int MODE_SHIFT = 30;  
    
  // 運算遮罩:0x3為16進制他宛,10進制為3,二進制為11
  // 3向左進位30 = 11 00000000000(11后跟30個0)  
  // 作用:用1標注需要的值欠气,0標注不要的值厅各。因1與任何數(shù)做與運算都得任何數(shù)、0與任何數(shù)做與運算都得0
  private static final int MODE_MASK  = 0x3 << MODE_SHIFT;  

  // UNSPECIFIED的模式設(shè)置:0向左進位30 = 00后跟30個0预柒,即00 00000000000
  // 通過高2位
  public static final int UNSPECIFIED = 0 << MODE_SHIFT;  
  
  // EXACTLY的模式設(shè)置:1向左進位30 = 01后跟30個0 队塘,即01 00000000000
  public static final int EXACTLY = 1 << MODE_SHIFT;  

  // AT_MOST的模式設(shè)置:2向左進位30 = 10后跟30個0,即10 00000000000
  public static final int AT_MOST = 2 << MODE_SHIFT;  

  /**
    * makeMeasureSpec()方法
    * 作用:根據(jù)提供的size和mode得到一個詳細的測量結(jié)果嗎宜鸯,即measureSpec
    **/ 
      public static int makeMeasureSpec(int size, int mode) {  
          return size + mode;  
      // measureSpec = size + mode人灼;此為二進制的加法 而不是十進制
      // 設(shè)計目的:使用一個32位的二進制數(shù),其中:32和31位代表測量模式(mode)顾翼、后30位代表測量大型斗拧(size)
      // 例如size=100(4),mode=AT_MOST适贸,則measureSpec=100+10000...00=10000..00100  

      }  

  /**
    * getMode()方法
    * 作用:通過measureSpec獲得測量模式(mode)
    **/    
      public static int getMode(int measureSpec) {  
       
          return (measureSpec & MODE_MASK);  
          // 即:測量模式(mode) = measureSpec & MODE_MASK;  
          // MODE_MASK = 運算遮罩 = 11 00000000000(11后跟30個0)
          //原理:保留measureSpec的高2位(即測量模式)灸芳、使用0替換后30位
          // 例如10 00..00100 & 11 00..00(11后跟30個0) = 10 00..00(AT_MOST),這樣就得到了mode的值

      }  
  /**
    * getSize方法
    * 作用:通過measureSpec獲得測量大小size
    **/       
      public static int getSize(int measureSpec) {  
       
          return (measureSpec & ~MODE_MASK);  
          // size = measureSpec & ~MODE_MASK;  
         // 原理類似上面拜姿,即 將MODE_MASK取反烙样,也就是變成了00 111111(00后跟30個1),將32,31替換成0也就是去掉mode蕊肥,保留后30位的size  
      } 
} 

5. 計算邏輯

View的MeasureSpec值計算取決于兩個因素:

  • View自身的布局參數(shù)(LayoutParams)
  • 父容器的測量規(guī)格(MeasureSpec)

即View的大小是由自身布局參數(shù)(LayoutParams)和父容器的測量規(guī)格(MeasureSpec)共同決定的谒获。

MeasureSpec值的具體計算邏輯封裝在getChildMeasureSpec()里蛤肌,具體計算邏輯如下源碼所示。

/**
  * 源碼分析:getChildMeasureSpec()
  * 作用:根據(jù)父視圖的MeasureSpec & 布局參數(shù)LayoutParams批狱,計算單個子View的MeasureSpec
  * 注:子view的大小由父view的MeasureSpec值 和 子view的LayoutParams屬性 共同決定
  **/
    public static int getChildMeasureSpec(int spec, int padding, int childDimension) {  
    // 參數(shù)說明
    // * @param spec 父view的詳細測量值(MeasureSpec) 
    // * @param padding view當前尺寸的的內(nèi)邊距和外邊距(padding,margin) 
    // * @param childDimension 子視圖的布局參數(shù)(寬/高)

    //父view的測量模式
    int specMode = MeasureSpec.getMode(spec);     

    //父view的大小
    int specSize = MeasureSpec.getSize(spec);     
  
    //通過父view計算出的子view = 父大小-邊距(父要求的大小裸准,但子view不一定用這個值)   
    int size = Math.max(0, specSize - padding);  
  
    //子view想要的實際大小和模式(需要計算)  
    int resultSize = 0;  
    int resultMode = 0;  
  
    //通過父view的MeasureSpec和子view的LayoutParams確定子view的大小  

    // 當父view的模式為EXACITY時,父view強加給子view確切的值
    //一般是父view設(shè)置為match_parent或者固定值的ViewGroup 
    switch (specMode) {  
    case MeasureSpec.EXACTLY:  
        // 當子view的LayoutParams>0赔硫,即有確切的值  
        if (childDimension >= 0) {  
            //子view大小為子自身所賦的值炒俱,模式大小為EXACTLY  
            resultSize = childDimension;  
            resultMode = MeasureSpec.EXACTLY;  

        // 當子view的LayoutParams為MATCH_PARENT時(-1)  
        } else if (childDimension == LayoutParams.MATCH_PARENT) {  
            //子view大小為父view大小,模式為EXACTLY  
            resultSize = size;  
            resultMode = MeasureSpec.EXACTLY;  

        // 當子view的LayoutParams為WRAP_CONTENT時(-2)      
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {  
            //子view決定自己的大小爪膊,但最大不能超過父view权悟,模式為AT_MOST  
            resultSize = size;  
            resultMode = MeasureSpec.AT_MOST;  
        }  
        break;  
  
    // 當父view的模式為AT_MOST時,父view強加給子view一個最大的值推盛。(一般是父view設(shè)置為wrap_content)  
    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;  
  
    // 當父view的模式為UNSPECIFIED時峦阁,父容器不對view有任何限制,要多大給多大
    // 多見于ListView耘成、GridView  
    case MeasureSpec.UNSPECIFIED:  
        if (childDimension >= 0) {  
            // 子view大小為子自身所賦的值  
            resultSize = childDimension;  
            resultMode = MeasureSpec.EXACTLY;  
        } else if (childDimension == LayoutParams.MATCH_PARENT) {  
            // 因為父view為UNSPECIFIED拇派,所以MATCH_PARENT的話子類大小為0  
            resultSize = 0;  
            resultMode = MeasureSpec.UNSPECIFIED;  
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {  
            // 因為父view為UNSPECIFIED,所以WRAP_CONTENT的話子類大小為0  
            resultSize = 0;  
            resultMode = MeasureSpec.UNSPECIFIED;  
        }  
        break;  
    }  
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);  
}  

總結(jié)如下:

示意圖

其中的規(guī)律總結(jié):(以子View為標準凿跳,橫向觀察)

示意圖

由于UNSPECIFIED模式適用于系統(tǒng)內(nèi)部多次measure情況件豌,很少用到,故此處不討論


  • 區(qū)別于頂級View(即DecorView)的測量規(guī)格MeasureSpec計算邏輯:取決于 自身布局參數(shù) & 窗口尺寸
示意圖

6. 總結(jié)


歡迎關(guān)注Carson_Ho的簡書

不定期分享關(guān)于安卓開發(fā)的干貨茧彤,追求短、平疆栏、快曾掂,但卻不缺深度


請點贊壁顶!因為你的鼓勵是我寫作的最大動力珠洗!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市若专,隨后出現(xiàn)的幾起案子许蓖,更是在濱河造成了極大的恐慌,老刑警劉巖调衰,帶你破解...
    沈念sama閱讀 210,914評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件膊爪,死亡現(xiàn)場離奇詭異,居然都是意外死亡嚎莉,警方通過查閱死者的電腦和手機米酬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評論 2 383
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來趋箩,“玉大人赃额,你說我怎么就攤上這事加派。” “怎么了跳芳?”我有些...
    開封第一講書人閱讀 156,531評論 0 345
  • 文/不壞的土叔 我叫張陵芍锦,是天一觀的道長。 經(jīng)常有香客問我筛严,道長醉旦,這世上最難降的妖魔是什么饶米? 我笑而不...
    開封第一講書人閱讀 56,309評論 1 282
  • 正文 為了忘掉前任桨啃,我火速辦了婚禮,結(jié)果婚禮上檬输,老公的妹妹穿的比我還像新娘照瘾。我一直安慰自己,他們只是感情好丧慈,可當我...
    茶點故事閱讀 65,381評論 5 384
  • 文/花漫 我一把揭開白布析命。 她就那樣靜靜地躺著,像睡著了一般逃默。 火紅的嫁衣襯著肌膚如雪鹃愤。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,730評論 1 289
  • 那天完域,我揣著相機與錄音软吐,去河邊找鬼。 笑死吟税,一個胖子當著我的面吹牛凹耙,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播肠仪,決...
    沈念sama閱讀 38,882評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼肖抱,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了异旧?” 一聲冷哼從身側(cè)響起意述,我...
    開封第一講書人閱讀 37,643評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎吮蛹,沒想到半個月后欲险,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,095評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡匹涮,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,448評論 2 325
  • 正文 我和宋清朗相戀三年天试,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片然低。...
    茶點故事閱讀 38,566評論 1 339
  • 序言:一個原本活蹦亂跳的男人離奇死亡喜每,死狀恐怖务唐,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情带兜,我是刑警寧澤枫笛,帶...
    沈念sama閱讀 34,253評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站刚照,受9級特大地震影響刑巧,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜无畔,卻給世界環(huán)境...
    茶點故事閱讀 39,829評論 3 312
  • 文/蒙蒙 一啊楚、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧浑彰,春花似錦恭理、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至诉濒,卻和暖如春周伦,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背未荒。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評論 1 264
  • 我被黑心中介騙來泰國打工专挪, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人茄猫。 一個月前我還...
    沈念sama閱讀 46,248評論 2 360
  • 正文 我出身青樓狈蚤,卻偏偏與公主長得像,于是被迫代替她去往敵國和親划纽。 傳聞我的和親對象是個殘疾皇子脆侮,可洞房花燭夜當晚...
    茶點故事閱讀 43,440評論 2 348

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