1. 程式人生 > >Android程式碼中如何獲取控制元件寬高

Android程式碼中如何獲取控制元件寬高

源地址http://blog.csdn.net/nailsoul/article/details/25909313

總結

1. 介面的原點(0, 0)是除去status bar和title bar之後剩下的區域。如果使用了全屏,不顯示狀態列,不顯示標題欄這樣的主題後,區域的原點位置會相應改變。

2. FrameLayout的widget中使用類似android:layout_marginLeft="65px"這樣的屬性,一定要加上android:layout_gravity,否則margin無效。還要注意FrameLayout的android:layout_width和android:layout_height對layout_gravity的影響。

3. 使用佈局屬性一定要分清誰是parent,parent用的是什麼layout,layout_width和layout_height的值。

4. 不同的佈局屬性也可以實現相同的功能。例如layout_gravity="center"和android:layout_centerInParent ="true"。

5. Eclipse的Android開發工具外掛ADT裡面有一個所見即所得的開發UI的功能。利用Graphical Layout可以預覽的效果。但是,有時會遇到以下問題:


測量控制元件尺寸(寬度、高度)是開發自定義控制元件的第一步,只有確定尺寸後才能開始畫(利用canvas在畫布上畫,我們所使用的控制元件實際上都是這樣畫上去的)。當然,這個尺寸是需要根據控制元件的各個部分計算出來的,比如:padding、文字大小,間距等。


非容器控制元件的onMeasure

下面我們就來看看如何給非容器控制元件(即直接extends View)這隻尺寸的:

1.@Override 2.protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 3.setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec)); 4.}

通過重寫onMeasure()方法來設定尺寸。這個方法實際是由容器控制元件(LinearLayout、RelativeLayout)來呼叫的,以便容器控制元件知道我需要分配給你多大空間或尺寸。

計算完尺寸後,最終呼叫setMeasuredDimension()來設定。比如,我要建立個 寬度是200px,高度是100px的控制元件,就可以setMeasuredDimension(200, 100)。

注意setMeasuredDimension()接收的值是px值,而不是dp值,所以我們不可能像上面那樣將尺寸寫固定的。如何做那:

引數 int widthMeasureSpec, int heightMeasureSpec 是指明控制元件可獲得的空間以及關於這個空間描述的元資料,是與佈局檔案中Android:layout_width、android:layout_height相聯絡的。如何使用:

01.   /** 02.* 計算元件寬度 03.*/ 04.private int measureWidth(int widthMeasureSpec) { 05.int result; 06.int specMode = MeasureSpec.getMode(widthMeasureSpec); 07.int specSize = MeasureSpec.getSize(widthMeasureSpec); 08. 09.if (specMode == MeasureSpec.EXACTLY) {//精確模式 10.result = specSize; 11.else { 12.result = getDefaultWidth();//最大尺寸模式,getDefaultWidth方法需要我們根據控制元件實際需要自己實現 13.if (specMode == MeasureSpec.AT_MOST) { 14.result = Math.min(result, specSize); 15.} 16.} 17.return result; 18.}

呼叫MeasureSpec.getMode(measureSpec),可以獲得設定尺寸的mode(模式)。

mode共有三種情況,取值分別為:

 MeasureSpec.UNSPECIFIED,MeasureSpec.EXACTLY,MeasureSpec.AT_MOST。

MeasureSpec.EXACTLY是精確尺寸,當我們將控制元件的layout_width或layout_height指定為具體數值時如andorid:layout_width="50dip",或者為FILL_PARENT是,都是控制元件大小已經確定的情況,都是精確尺寸。

MeasureSpec.AT_MOST是最大尺寸,當控制元件的layout_width或layout_height指定為WRAP_CONTENT時,控制元件大小一般隨著控制元件的子空間或內容進行變化,此時控制元件尺寸只要不超過父控制元件允許的最大尺寸即可。因此,此時的mode是AT_MOST,size給出了父控制元件允許的最大尺寸。

MeasureSpec.UNSPECIFIED是未指定尺寸,這種情況不多,一般都是父控制元件是AdapterView,通過measure方法傳入的模式。

measureWidth() 方法是個固定實現,幾乎不用改, 唯一需要我們實現的就是getDefaultWidth()。即,當mode處於最大模式下,此時父容器會將他所能給你的或者說目前剩餘的最大空間給你。你只能在這個空間內設定控制元件尺寸。一個簡單的類似TextView的getDefaultWidth()的實現:

1.private void getDefaultWidth(){ 2.int txtWidth = (int)this.paint.measureText(this.text); 3.return txtWidth + this.paddingLeft + this.paddingRight; 4.}

上面說了寬度的測量,高度同理:

01.   /** 02.* 計算元件高度 03.*/ 04.private int measureHeight(int measureSpec) { 05.int result; 06.int specMode = MeasureSpec.getMode(measureSpec); 07.int specSize = MeasureSpec.getSize(measureSpec); 08. 09.if (specMode == MeasureSpec.EXACTLY) { 10.result = specSize; 11.else { 12.result = getDefaultHeight(); 13.if (specMode == MeasureSpec.AT_MOST) { 14.result = Math.min(result, specSize); 15.} 16.} 17.return result; 18.}

有了上面的實現,我們就可以在佈局檔案中使用控制元件時,通過android:layout_width、android:layout_height來自定義控制元件的寬度、高度。

容器控制元件的onMeasure

容器控制元件一般是繼承ViewGroup,ViewGroup是個抽象類,本身沒有實現onMeasure,但是他的子類都有各自的實現,通常他們都是通過measureChildWithMargins函式或者其他類似於measureChild的函式來遍歷測量子View,被GONE的子View將不參與測量,當所有的子View都測量完畢後,才根據父View傳遞過來的模式和大小來最終決定自身的大小.

在測量子View時,會先獲取子View的LayoutParams,從中取出寬高,如果是大於0,將會以精確的模式加上其值組合成MeasureSpec傳遞子View,如果是小於0,將會把自身的大小或者剩餘的大小傳遞給子View,其模式判定在前面表中有對應關係.

ViewGroup一般都在測量完所有子View後才會呼叫setMeasuredDimension()設定自身大小。


在activity中可以呼叫View.getWidth、View.getHeight()、View.getMeasuredWidth()、View.getgetMeasuredHeight()來獲得某個view的寬度或高度,但是在onCreate()、onStrart()、onResume()方法中會返回0,這是應為當前activity所代表的介面還沒顯示出來沒有新增到WindowPhone的DecorView上或要獲取的view沒有被新增到DecorView上或者該View的visibility屬性為gone 或者該view的width或height真的為0 所以只有上述條件都不成立時才能得到非0的width和height  

所以要想獲取到的width和height為真實有效的 則有以下方法

1.在該View的事件回撥裡使用  這時候該view已經被顯示即被新增到DecorView上 如點選事件  觸控事件  焦點事件等

  1. View view=findViewById(R.id.tv);  
  2.     view.setOnClickListener(new OnClickListener() {  
  3.         @Override  
  4.         public void onClick(View v) {  
  5.             int width = v.getWidth();  
  6.         }  
  7.     });  

2.在activity被顯示出來時即新增到了DecorView上時獲取寬和高如onWindowFocusChanged() 回撥方法

  1. @Override  
  2.   public void onWindowFocusChanged(boolean hasFocus) {  
  3.     View iv1 = findViewById(R.id.iv1);  
  4. View iv2=findViewById(R.id.iv2);  
  5. String msg1="iv1' width:"+iv1.getWidth()+" height:"+iv1.getHeight()+"  measuredWidth:"+iv1.getMeasuredWidth()+"measuredHeight:"+iv1.getMeasuredHeight();  
  6. String msg2="iv2' width:"+iv2.getWidth()+" height:"+iv2.getHeight()+"  measuredWidth:"+iv2.getMeasuredWidth()+"measuredHeight:"+iv2.getMeasuredHeight();  
  7. i("onWindowFocusChanged() "+msg1);  
  8. i("onWindowFocusChanged() "+msg2);  
  9.     super.onWindowFocusChanged(hasFocus);  
  10.   }  

3.或在onResume方法最後開執行緒300毫秒左右後獲取寬和高  因為onResume執行完後300毫秒後 介面就顯示出來了  

當然地2種和地3種方法要保證獲取寬高的view是在setContentView時設進去的View或它的子View

  1. view.postDelayed(new Runnable() {  
  2.             @Override  
  3.             public void run() {  
  4.                 View iv1 = findViewById(R.id.iv1);  
  5.                 View iv2=findViewById(R.id.iv2);  
  6.                 String msg1="iv1' width:"+iv1.getWidth()+" height:"+iv1.getHeight()+"  measuredWidth:"+iv1.getMeasuredWidth()+"measuredHeight:"+iv1.getMeasuredHeight();  
  7.                 String msg2="iv2' width:"+iv2.getWidth()+" height:"+iv2.getHeight()+"  measuredWidth:"+iv2.getMeasuredWidth()+"measuredHeight:"+iv2.getMeasuredHeight();  
  8.                 i("onWindowFocusChanged() "+msg1);  
  9.                 i("onWindowFocusChanged() "+msg2);  
  10.             }  
  11.         }, 300);  


4.在onCreate()或onResume()等方法中需要獲取寬高時使用getViewTreeObserver().addOnGlobalLayoutListener()來添為view加回調在回撥裡獲得寬度或者高度獲取完後讓view刪除該回調

  1. view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {  
  2.         @Override  
  3.         public void onGlobalLayout() {  
  4.             view.postDelayed(new Runnable() {  
  5.                 @Override  
  6.                 public void run() {  
  7.                     View iv1 = findViewById(R.id.iv1);  
  8.                     View iv2=findViewById(R.id.iv2);  
  9.                     String msg1="iv1' width:"+iv1.getWidth()+" height:"+iv1.getHeight()+"  measuredWidth:"+iv1.getMeasuredWidth()+"measuredHeight:"+iv1.getMeasuredHeight();  
  10.                     String msg2="iv2' width:"+iv2.getWidth()+" height:"+iv2.getHeight()+"  measuredWidth:"+iv2.getMeasuredWidth()+"measuredHeight:"+iv2.getMeasuredHeight();  
  11.                     i("onWindowFocusChanged() "+msg1);  
  12.                     i("onWindowFocusChanged() "+msg2);  
  13.                 }  
  14.             }, 300);  
  15.         }  
  16.     }); 

在Android中,有時需要對控制元件進行測量,得到的控制元件寬度和高度可以用來做一些計算。在需要自適應螢幕的情況下,這種計算就顯得特別重要。另一方便,由於需求的原因,希望一進入介面後,就能得到控制元件的寬度和高度。

可惜的是,根據我的驗證,利用網上轉載的那些方法在OnCreate函式中獲取到的仍然是0(希望搞技術的能自己驗證過再轉載),例如Measure方法之後呼叫getMeasuredWidth的值還是0。

原因是因為當OnCreate函式發生時,只是提供了資料初始化的機會,此時還沒有正式繪製圖形。而繪製圖形在OnDraw中進行,此時計算又顯得太晚。容易想到的辦法是:希望能在程式剛剛測量好某個指定控制元件後,拿到它的寬度和高度立刻進行計算或資料初始化。這就需要有一個方法來監聽到這個事件的發生,幸好Android提供了這樣的機制,利用View類中的getViewTreeObserver方法,可以獲取到指定View的觀察者,在繪製控制元件前的一剎那進行回撥,這樣速度上又不耽誤,得到的資料由是準確的,但此方法在之後可能會被反覆呼叫,因此需要加入限制,普通需求下,只計算一次就夠了,程式碼如下(此程式碼在OnCreate回撥函式中驗證通過,實時上,