1. 程式人生 > >Android自定義控制元件系列:詳解onMeasure()方法中如何測量一個控制元件尺寸(一)

Android自定義控制元件系列:詳解onMeasure()方法中如何測量一個控制元件尺寸(一)

轉載請註明出處:http://blog.csdn.net/cyp331203/article/details/45027641
今天的任務就是詳細研究一下protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)方法。

如果只是說要重寫什麼方法有什麼用的話,還是不太清楚。先去原始碼中看看為什麼要重寫onMeasure()方法,這個方法是在哪裡呼叫的:

一、原始碼中的measure/onMeasure方法:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),  
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));  
}  

實際上是在View這個類中的public final void measure(int widthMeasureSpec, int heightMeasureSpec)方法中被呼叫的:

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {  
...  

onMeasure(widthMeasureSpec, heightMeasureSpec);  
...  

}  

1、measure()
可以看到,measure()這個方法是一個由final來修飾的方法,意味著不能夠被子類重寫.measure()方法的作用是:測量出一個View的實際大小,而實際性的測量工作,Android系統卻並沒有幫我們完成,因為這個工作交給了onMeasure()來作,所以我們需要在自定義View的時候按照自己的需求,重寫onMeasure方法.而子控制元件又分為view和viewGroup兩種情況,那麼測量的流程是怎樣的呢,看一下下面這個圖你就明白了:
![這裡寫圖片描述
](https://img-blog.csdn.net/20170109122620061?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd29ycnlkb2c=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) 2、onMeasure onMeasure(int widthMeasureSpec, int heightMeasureSpec)中,兩個引數的作用: widthMeasureSpec和heightMeasureSpec這兩個int型別的引數,看名字應該知道是跟寬和高有關係,但它們其實不是寬和高,而是由寬、高和各自方向上對應的模式來合成的一個值:其中,在int型別的32位二進位制位中,31-30這兩位表示模式,0~29這三十位表示寬和高的實際值.其中模式一共有三種,被定義在Android中的View類的一個內部類中:View.MeasureSpec: ①UNSPECIFIED:表示預設值,父控制元件沒有給子view任何限制。------二進位制表示:00 ②EXACTLY:表示父控制元件給子view一個具體的值,子view要設定成這些值的大小。------二進位制表示:01 ③AT_MOST:表示父控制元件個子view一個最大的特定值,而子view不能超過這個值的大小。------二進位制表示:10 二、MeasureSpec MeasureSpe描述了父View對子View大小的期望.裡面包含了測量模式和大小.我們可以通過以下方式從MeasureSpec中提取模式和大小,該方法內部是採用位移計算. int specMode = MeasureSpec.getMode(measureSpec);//得到模式 int specSize = MeasureSpec.getSize(measureSpec);//得到大小 也可以通過MeasureSpec的靜態方法把大小和模式合成,該方法內部只是簡單的相加. MeasureSpec.makeMeasureSpec(specSize,specMode); 每個View都包含一個ViewGroup.LayoutParams類或者其派生類,LayoutParams中包含了View和它的父View之間的關係,而View大小正是View和它的父View共同決定的。 我們平常使用類似於RelativeLayout和LinearLayout的時候,在其內部新增view的時候,不管是佈局檔案中加入還是在程式碼中使用addView方法新增,實際上都會呼叫這個onMeasure方法,而measure和onMeasure中的兩個引數,是由各級父控制元件往子控制元件/子view進行一層層傳遞的。我們可以在xml中定義Layout的寬和高的具體的值或寬高的填充方式:matchparent/wrapcontent,也可以在程式碼中使用LayoutParams設定,而實際上這裡設定的值就會對應到上面的measure和onMeasure方法中的兩個引數的模式,對應關係如下: 具體的值(如width=200dp)和matchparent/fillparent,對應模式中的MeasureSpec.EXACTLY 包裹內容(width=wrapcontent)則對應模式中的MeasureSpec.AT_MOST 系統呼叫measure方法,從父控制元件到子控制元件的heightMeasureSpec的傳遞是有一套對應的判斷規則的,列表如下: ![這裡寫圖片描述
](https://img-blog.csdn.net/20170109122719822?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd29ycnlkb2c=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
package com.example.hello;  

import android.app.Activity;  
import android.os.Bundle;  
import android.view.Window;  

public class MainActivity extends Activity {  

    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        requestWindowFeature(Window.FEATURE_NO_TITLE);  
        setContentView(R.layout.activity_main);  
    }  
}  

這裡寫圖片描述
可以發現最簡單的helloworld的層級關係圖是這樣的,最開始是一個PhoneWindow的內部類DecorView,這個DecorView實際上是系統最開始載入的最底層的一個viewGroup,它是FrameLayout的子類,然後載入了一個LinearLayout,然後在這個LinearLayout上載入了一個id為content的FrameLayout和一個ViewStub,這個實際上是原本為ActionBar的位置,由於我們使用了requestWindowFeature(Window.FEATURE_NO_TITLE),於是變成了空的ViewStub;然後在id為content的FrameLayout才載入了我們的佈局XML檔案中寫的RelativeLayout和TextView。

那麼measure方法在系統中傳遞尺寸和模式,必定是從DecorView這一層開始的,我們假定手機螢幕是320*480,那麼DecorView最開始是從硬體的配置檔案中讀取手機的尺寸,然後設定measure的引數大小為320*480,而模式是EXCACTLY,傳遞關係可以由下圖示意:
這裡寫圖片描述
好了,原理將到這裡,下一篇將看到利用onMeasure來測量一個自定義一個ImageView,使其能夠自動填滿螢幕的寬度,且能通過measure測量高度,自適應的調整高度,永遠不出現拉伸/壓縮變形的情況,敬請關注,謝謝。