MeasureSpec理解

簡要

  今天來聊聊MeasureSpec,記得剛接觸的也感覺很難理解,知其然不知其所以然。MeasureSpec其實在面試中還經常會被問到,如果沒有真正去理解它,不論是後續的開發或者面試中,難免會成爲絆腳石。遂今天在此聊聊這個,可能以下文中理解有所偏頗。如果有什麼不到之處或者看完還有不太理解的地方,可以在文章後面寫下你的評論一起去探討。
  一說到MeasureSpec,大家本能反應是其是由高兩位的mode和低30位的size組成的32位的一個int值,但要是再深入問一些可能回答不出來,例如:MeasureSpec爲什麼由mode+size的方式組成?MeasureSpec的值跟什麼有關?或者父View與子View的MeasureSpec值的關係?自定義Viewd的onMeasure()方法要不要重寫?你還在爲這些面試中經常問的問題爲難到嗎?相信看完下文,心裏多多少少都會有個答案。

組成

  其實MeasureSpec是View的一個內部類,真正的身份就是幫助View完成測量功能。MeasureSpec類中最主要的部分由5個變量和3個方法組成,接下來根據原碼中的代碼慢慢一層層剖析。

變量

五個成員變量:

    private static final int MODE_SHIFT = 30;
    private static final int MODE_MASK  = 0x3 << MODE_SHIFT;
    public static final int UNSPECIFIED = 0 << MODE_SHIFT;
    public static final int EXACTLY     = 1 << MODE_SHIFT;
    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);
     }
}

@MeasureSpecMode
public static int getMode(int measureSpec) {
 //noinspection ResourceType
  return (measureSpec & MODE_MASK);
}

public static int getSize(int measureSpec) {
  return (measureSpec & ~MODE_MASK);
}

舉例:

位運算符號:
<< :左移位運算 符號左邊是值,符號右邊是左移的位數
& :與運算 相同位的值同爲1則爲,否爲0
~ :優先級比算數運算符、關係運算符、邏輯運算符和其他運算符都高

解釋
  • MeasureSpec由mode+size的形式組合而成32位int值的好處是減少變量的創建
  • MODE_MASK、UNSPECIFIED、EXACTLY 、AT_MOST 四個變量均左移30位,剛好高兩位組成2*2排列組合,由位運算特性mode=MODE_MASK&mode
  • makeMeasureSpec()方法目的就是通過mode和size組成MesureSpec的值,if條件判斷SDK版本是否低於17,但是結果是相同的,只是高版本採用位運算的方式獲取。
  • getMode()方法是獲取measureSpec的mode值,由於MODE_MASK低30位全是0,因此MODE_MASK&measureSpec計算的結果低30位爲0,又因爲measureSpec的高兩位爲mode值,又因爲MODE_MASK的高兩位全爲1,因此MODE_MASK&measureSpec高兩位的值爲mode高兩位的值,再加上低30位的0,因此MODE_MASK&measureSpec的結果就是mode值。
  • getSize()方法是獲取measureSpec的size值,由於~ 位運算優先級高於&運算,因此先~運算再&運算。~MODE_MASK運算之後高兩位爲0,低30位爲1。~MODE_MASK&measureSpec的高兩位只能爲0,又因爲measureSpec的低30位size值,因此~MODE_MASK&measureSpec的低30位爲size值。

來源和決定因素

  上面的組成部分講解了MeasureSpec內部結構,其中mode與size參數的意義和如何獲取。接下來說一說其如何幫助測量View的。我們都知道整個View體系是樹狀結構的,測量是由上至下的過程,View的測量功能由onMeasure()和measure()方法完成,其中onMeasure()屬於回調方法,其結果再一層層回溯給頂層View。其中開發中我們只需要關注onMeasure()方法。onMeasure()方法中最重要的參數就是MeasureSpec值,接下來看一下自定義View的onMeasure()方法。

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int specMode = MeasureSpec.getMode(widthMeasureSpec);
        switch (specMode) {
            case MeasureSpec.EXACTLY:
                Log.e("TAG", "ChildView:MeasureSpec.EXACTLY");
                break;
            case MeasureSpec.AT_MOST:
                Log.e("TAG", "ChildView:MeasureSpec.AT_MOST");
                break;
            case MeasureSpec.UNSPECIFIED:
                Log.e("TAG", "ChildView:MeasureSpec.UNSPECIFIED");
                break;
        }
    }

說明:
本文以下內容均只針對寬方向上來說

  • 源頭:widthMeasureSpec值是由其父類傳遞過來的
  • 值 :widthMeasureSpec(自身)=super.widthMeasureSpec(父)+this.LayoutParams(自身)結果得來的
  • LayoutParams是ViewGroup的靜態內部類,由widthparam+widthparam組成,LayoutParams的類型是其父類的類型,值是自身的,常用的ViewGroup都重寫,新增Gravity方位值。其值分爲三類,MATCH_PARENT(-1)、WRAP_CONTENT(-2)、具體的dp值,後面具體的dp值用dp代替

上圖說明:
1、上圖是自定義View沒重寫onMeasure()的情況,謹記!!!
2、childView括號中的三個值從左到右爲:LayoutParams的值、MeasureSpec的mode值(已經確定的)、測量過後的自身size值(此時稱爲measuredWidth)
3、正常情況下UNSPECIFIED模式用不到,所以自定義View的時候可以不做處理
4、getMeasuredWidth()≠getWidth(),getMeasuredWidth()在測量之後就有值了,但是getWidth()是沒有值的
5、只有自身mode爲EXACTLY時,測量的size值爲自己設置的,mode爲AT_MOST的時候size全是父類的值

總結

  這裏做一下總結,其中有兩部分組成,第一部分是內部的組成關係,組成關係中最主要的是幾個位運算和幾個參數的意義。第二部分是值的來源和決定因素,onMeasure()方法中值由父類傳遞過來,其值是根據父類的mode和自身的LayoutParams決定。如果還感覺很模糊,不妨自己去寫個例子去驗證一下,這樣可以加深印象,最重要的還是要理解。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章