Android 自定義View 之測量過程(onMeasure)

       本文探究測量的全過程,不是簡單的介紹onMeasure()函數!

一、測量的目的

      

        如果安卓的佈局體系中全部都是精確的值,那就沒有必要關心測量過程了。事實上,在佈局文件中寫的佈局參數都是match_parent,wrap_parent或者精確值。

       測量的目的就是爲了將match_parent,wrap_parent這些相對大小,轉換爲具體的值。


二、測量的依據

        應用開發者有三種方式表示長寬:match_parent,wrap_parent,精確值

         測量體系內部有三種模式:EXACTILY,AT_MOST,UNSPECIFIED

       其轉換所對應的表格如下:


paretMeasureSpec

Lp.height

childMeasureSpec

EXACTLY+SIZE

具體值

EXACTLY+具體值

同上

WRAP_CONTENT

AT_MOST+size

同上

MATCH_PARENT

EXACTLY+size

AT_MOST+SIZE

具體值

EXACTLY+size

同上

WRAP_CONTENT

AT_MOST+size

同上

MATCH_PARENT

AT_MOST+size

UNSPECIFIED+SIZE

childDimension

EXACTLY+childDimension

同上

WRAP_CONTENT

UNSPECIFIED+0

同上

MATCH_PARENT

UNSPECIFIED+0



    測量規格即表中的MeasureSpec,其組成爲:測量模式+大小。

    測量模式有三種:
    EXACTILIY:父視圖期待子視圖的最理想大小

    AT_MOST:父視圖允許子視圖的最大值

    UNSEPECFIED:好吧,我不知道這個做啥用的,基本沒遇到過


    上述表格意義解釋:

    paretMeasureSpec:這是測量體系傳遞下來的,由系統轉換完成

    Lp.height:對應XML文件中latyou_height = "XXXX",這裏是由程序猿指定。

    ChildMeasureSpec :經過表的轉換後得出自己的測量模式和測量中值,如果當前測量控件是一個ViewGroup,這個值還會傳遞給子控件。

    

三、測量過程

      整個的測量過程,根據個人的理解做了一個圖,將就着看:

      

   將視圖分爲兩類:View和ViewGroup

   1->      ViewGroup有兩個動作:

    動作一:measure(父控件給定的寬規格,父控件給定的高規格)  函數接受來自父控件指定的測量規格,然後結合自己的佈局參數           (layout_width,layou_height)來測量自身的寬和高 ,測量模式。這個時候就用到了最上面的表格,這一步的時候,measure中會回調onMeasure();

    這就是View中的onMeasure()函數中的參數是怎麼來的了。

   動作二:

    根據padding等屬性,綜合進行考慮,給每個子控件分配測量規格

    

    2->   View相對簡單,只有一個操作:

     只有動作一:根據父控件傳遞下來的寬高測量規格,得出自己的寬高。同樣的也會回調onMeasure()方法。

    

     在實際的使用過程,一遍的控件都有父控件,有父控件提供測量規格進行參考,那根視圖呢?

      根視圖有單獨的計算方法,其大小需要結合窗口的佈局參數。一般情況,根視圖的measure方法所獲得測量規格爲Exactly+窗口大小!

注意:

    以上測量的過程都是系統完成,只有onMeasure()這一步應用程序可以參與。而且各種規格都只是建議,應用程序的代碼可以通過     setMeasureDimension()來一票否決,直接指定當前控件的大小。

    同時,不建議直接繼承ViewGroup,通過上述的過程,應該知道,如果直接繼承ViewGroup你就需要處理給子空間分配大小的過程,這個過程肯定麻煩。

大多數的開源控件中也很有直接繼承ViewGroup,要麼是繼承的View,要麼就是繼承已有的VIewGroup子類,像LinearLayout之類的。


四、實際的運用過程。

        一個窗口承載一個視圖,視圖最終會表現爲一個View構成的樹。每個節點(也就是View)必須擁有LayoutParam的屬性。

來看看View的獲得方式:

方式一:new 一個View

方式二:通過 LayoutInflater.from(this).inflate(R.layout.XXX,null);的方式來構造出一個View

        如果你調用getLayoutParams(),會發現它們的佈局參數都是空的。

方式一不奇怪,你本來就沒有設置佈局參數,自然沒有

方式二奇怪的很,不是佈局XML裏面一般都有寫麼!其實是構造的過程最外層的ViewGroup的寬高屬性是沒有生效的,如果需要使得它生效,需要調用LayoutInfalter.from(this).infate的另外一種用法。


既然沒有佈局參數,它們最後都是怎麼測量的?

其實,以上的view都是孤立的View,如果需要讓它可見,一定要把通過addView的方法,加入主幹的視圖樹中。查看addView這個方法會發現有個特點,如果你加入的View沒有佈局參數,它會生成一個默認的參數。如果有就使用子控件的佈局參數。


這裏有一點需要注意到 是,如果給一個LinearLayout通過addView的方式添加一個View,而這個View你通過setLayoutParams的方式指定了一個佈局參數,這個指定的佈局參數必須是LinearLayout.LayoutParams ,不然就會報類型錯誤。

如下圖所示,每個VIew所對應的LayoutParams類型是是父母控件的類型的佈局參數!



    實際的運用就是,需要對控件本身定製大小,不受父母控件的干擾,就手動的設置精確佈局參數,需要注意其類型,類型一旦確實就只能添加到某個指定的控件下了。

  



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