iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 7
1
Mobile Development

Android 開發經驗三十天系列 第 7

[Android經驗三十天]Day7一自定義View觀念 (中)

永遠等待 那一日 咱可以出頭天 人生不怕風浪 只怕自己沒志氣 - 五月天 /images/emoticon/emoticon02.gif

雖然知道講觀念很無聊,但只是筆記的話。。 還是紀錄一下性質吧 :D

自定義View 要override的話有主要幾個function,先講前幾個,下的話會介紹完~~~
之後會再有一篇實作篇 :D

自定義View總共有三個方法比較常用需要override,那就一一介紹吧!

OnMeasure

1.作用 : 用來測量View的寬跟高,將View放入父容器的時候需要知道view的寬跟高
2.有可能踩到的雷:

  •  1.需要多次測量與測量不精確,可以在onLayout在精準定位一次
    
  •  2.Wrap_Content無作用
    
  •  這些都會在之後的文章提出解決方式。
    

那就開始吧!

要測量一個View的寬跟高需要兩個東西,一個是 MeasureSpec 一個是 LayoutParams

MeasureSpec

Q:作用?
A:可以想成是一個測量的工具與方法,是由父類提供的,每個MeasureSpec代表一組寬/高的規格

Q:那有幾種模式呢?
A:主要有

  1. UNSPECIFIED
  2. EXACTLY : 父視圖指定一個確切的大小,子視圖必須在那個大小內
    常用到的地方有 *match_parent : 擴展至父視圖大小
    *XXdp : 有具體大小
  3. AT_MOST: 父視圖指定一個 最大的大小,子視圖必須確保自己跟所有子視圖都在那個大小內
    常用到的地方有: wrap_content
    但是,因為父視圖只給&要求一個不能超出的大小,所以不會知道子視圖尺寸,所以需要實現自身測量邏輯,否則可能會有問題

LayoutParams

LayoutParams是由View本身提供的,也是ViewGroup的子類,View基本上都是一層一層上去組成Group的

Q:使用的點 ? 聽起來還是好抽象喔 ?
A:常見的有 fill_parent match_parent wrap_content XXdp

所以結論是 MeasureSpec ->是由父視圖的MeasureSpec+子視圖的LayoutParams合成的,他能決定最後的寬跟高。

可以看下面原始碼也有說到這部分
Q:還是好複雜 ?
A:那就改成舉例子:
當父視圖的測量模式為 EXACTLY,子視圖是xxDp or MATCH_PARENT他的測量模式都為EXACTLY
因為父視圖有指定一個精確大小
若子視圖WRAP_CONTENT 他的測量模式為 AT_MOST,因為子視圖此時自己決定自己大小了。
當父視圖的測量模式為 AT_MOST,子視圖是xxDp,儘管父視圖指定的是最大但子視圖還是自己精確指定了
子視圖MATCH_PARENT,因為子視圖也是給範圍,父視圖給的也是最大大小,可能需要再往上推一層才能知道大小,所以子視圖測量模式是AT_MOST
若子視圖WRAP_CONTENT 他的測量模式為 AT_MOST,因為子視圖此時自己決定自己大小了

下面是原始碼的一部份可以參考。。
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;
}
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) {  
            case MeasureSpec.EXACTLY:  
              
                if (childDimension >= 0) {  
                  
                    resultSize = childDimension;  
                    resultMode = MeasureSpec.EXACTLY;  

                
                } else if (childDimension == LayoutParams.MATCH_PARENT) {  
             
                    resultSize = size;  
                    resultMode = MeasureSpec.EXACTLY;  

              
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {  
                
                    resultSize = size;  
                    resultMode = MeasureSpec.AT_MOST;  
                }  
                break;  
          
    
            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;  
          
            case MeasureSpec.UNSPECIFIED:  
                if (childDimension >= 0) {  
                
                    resultSize = childDimension;  
                    resultMode = MeasureSpec.EXACTLY;  
                } else if (childDimension == LayoutParams.MATCH_PARENT) {  
                     
                    resultSize = 0;  
                    resultMode = MeasureSpec.UNSPECIFIED;  
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {  
                    
                    resultSize = 0;  
                    resultMode = MeasureSpec.UNSPECIFIED;  
                }  
                break;  
            }  
            return MeasureSpec.makeMeasureSpec(resultSize, resultMode);  
        }  

原始碼流程 : measure() -> onMeasure ()
-> getDefaultSize() 根據測量規格計算寬高
-> setMeasuredDimension() 根據存起來

  • [ x ] 注意:setMeasuredDimension是很重要的,如果你放的是固定的而不是測量的
  • ex setMeasuredDimension(50,50),那永遠在View上顯示的都是 50 * 50
  • 還有在 setMeasuredDimension 才能調用 getMeasuredWidth() getMeasuredHeight() 獲取值

Q:ViewGroup ?
一層一層合併計算

measure() -> onMeasure ()
-> 因為有很多子視圖所以 ... measureChildren() ..measureChild() 計算單一寬高
-> setMeasuredDimension() 根據組合存起來
參考


上一篇
[Android 開發經驗三十天]D6一自定義View筆記(上)
下一篇
[Android 開發經驗三十天]D8一自定義View筆記(下)
系列文
Android 開發經驗三十天30

尚未有邦友留言

立即登入留言