UI繪製流程之測量------onMeasure()方法之探討實現不可滑動的原理

在自定義view的時候,只需要知道以下三個步驟:
1.測量——onMeasure():決定View的大小
2.佈局——onLayout():決定View在ViewGroup中的位置
3.繪製——onDraw():如何繪製這個View。(非必須)

今天來總理下onMeasure()方法的理解:
首先onMeasure()方法到目前爲止我最熟悉的是用途是用來讓可以滑動的View不能滑動.
不知道你經常那他來做什麼,現在就來看看一個很簡單的不可滑動的ListView的寫法,以此來淺談一下onMeasure()的寫法 :

public class NoScrollListview extends ListView{  

        public NoScrollListview(Context context, AttributeSet attrs) {  
                super(context, attrs);  
        }  

   /**
     * 測量寬高來設置不可滑動
     * @param widthMeasureSpec 寬的詳細測量值
     * @param heightMeasureSpec 高的詳細測量值 
     */
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
    int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
    super.onMeasure(widthMeasureSpec, expandSpec);

}

可以看到onMeasure()方法接收兩個參數寬高的詳細測量值 ,至於什麼叫詳細測量值 , 這裏不多解釋 , 反正就是預計的寬高 ,可以理解爲視圖View想要的大小(寬高)說明(想要的未必就是最終大小)。

然後在方法內部調用了MeasureSpec類的makeMeasureSpec()方法 , 還好這個類不是很大 ,還好搜到一篇詳細註釋的這個類 (附錄在最後), 現在一起來學習一下 這個方法:

  /** 
         * 根據提供的size和mode得到一個詳細的測量結果 
         */  
        // measureSpec = size + mode;   (注意:二進制的加法,不是十進制的加法!)  
        // 這裏設計的目的就是使用一個32位的二進制數,32和31位代表了mode的值,後30位代表size的值  
        // 例如size=100(4),mode=AT_MOST,則measureSpec=100+10000...00=10000..00100  
        public static int makeMeasureSpec(int size, int mode) {  
            return size + mode;  
        }  

這裏詳細介紹了makeMeausreSpec()方法 , 可以很清楚的明白我們改寫ListView測量的最大高度爲屏幕的高度,所以ListView就不能滑動了.

附錄MeasureSpec類詳解

/** 
 * MeasureSpec封裝了父佈局傳遞給子佈局的佈局要求,每個MeasureSpec代表了一組寬度和高度的要求 
 * MeasureSpec由size和mode組成。 
 * 三種Mode: 
 * 1.UNSPECIFIED 
 * 父不沒有對子施加任何約束,子可以是任意大小(也就是未指定) 
 * (UNSPECIFIED在源碼中的處理和EXACTLY一樣。當View的寬高值設置爲0的時候或者沒有設置寬高時,模式爲UNSPECIFIED 
 * 2.EXACTLY 
 * 父決定子的確切大小,子被限定在給定的邊界裏,忽略本身想要的大小。 
 * (當設置width或height爲match_parent時,模式爲EXACTLY,因爲子view會佔據剩餘容器的空間,所以它大小是確定的) 
 * 3.AT_MOST 
 * 子最大可以達到的指定大小 
 * (當設置爲wrap_content時,模式爲AT_MOST, 表示子view的大小最多是多少,這樣子view會根據這個上限來設置自己的尺寸) 
 *  
 * MeasureSpecs使用了二進制去減少對象的分配。 
 */  
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與任何數做與運算都得任何數,0與任何數做與運算都得0)  
        private static final int MODE_MASK  = 0x3 << MODE_SHIFT;  

        // 0向左進位30,就是00 00000000000(00後跟30個0)  
        public static final int UNSPECIFIED = 0 << MODE_SHIFT;  
        // 1向左進位30,就是01 00000000000(01後跟30個0)  
        public static final int EXACTLY     = 1 << MODE_SHIFT;  
        // 2向左進位30,就是10 00000000000(10後跟30個0)  
        public static final int AT_MOST     = 2 << MODE_SHIFT;  

        /** 
         * 根據提供的size和mode得到一個詳細的測量結果 
         */  
        // measureSpec = size + mode;   (注意:二進制的加法,不是十進制的加法!)  
        // 這裏設計的目的就是使用一個32位的二進制數,32和31位代表了mode的值,後30位代表size的值  
        // 例如size=100(4),mode=AT_MOST,則measureSpec=100+10000...00=10000..00100  
        public static int makeMeasureSpec(int size, int mode) {  
            return size + mode;  
        }  

        /** 
         * 通過詳細測量結果獲得mode 
         */  
        // mode = measureSpec & MODE_MASK;  
        // MODE_MASK = 11 00000000000(11後跟30個0),原理是用MODE_MASK後30位的0替換掉measureSpec後30位中的1,再保留32和31位的mode值。  
        // 例如10 00..00100 & 11 00..00(11後跟30個0) = 10 00..00(AT_MOST),這樣就得到了mode的值  
        public static int getMode(int measureSpec) {  
            return (measureSpec & MODE_MASK);  
        }  

        /** 
         * 通過詳細測量結果獲得size 
         */  
        // size = measureSpec & ~MODE_MASK;  
        // 原理同上,不過這次是將MODE_MASK取反,也就是變成了00 111111(00後跟30個1),將32,31替換成0也就是去掉mode,保留後30位的size  
        public static int getSize(int measureSpec) {  
            return (measureSpec & ~MODE_MASK);  
        }  

        /** 
         * 重寫的toString方法,打印mode和size的信息,這裏省略 
         */  
        public static String toString(int measureSpec) {  
            return null;  
        }  
}  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章