android自定义控件(6)-详解在onMeasure()方法中如何测量一个控件尺寸
?
今天的任務就是詳細研究一下protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)方法。如果只是說要重寫什么方法有什么用的話,還是不太清楚。先去源碼中看看為什么要重寫onMeasure()方法,這個方法是在哪里調(diào)用的:
?
一、源碼中的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)方法中被調(diào)用的:
public final void measure(int widthMeasureSpec, int heightMeasureSpec) { ...onMeasure(widthMeasureSpec, heightMeasureSpec); ...}1、measure()
可以看到,measure()這個方法是一個由final來修飾的方法,意味著不能夠被子類重寫.measure()方法的作用是:測量出一個View的實際大小,而實際性的測量工作,Android系統(tǒng)卻并沒有幫我們完成,因為這個工作交給了onMeasure()來作,所以我們需要在自定義View的時候按照自己的需求,重寫onMeasure方法.而子控件又分為view和viewGroup兩種情況,那么測量的流程是怎樣的呢,看一下下面這個圖你就明白了:
2、onMeasure
onMeasure(int widthMeasureSpec, int heightMeasureSpec)中,兩個參數(shù)的作用: ? ? ? ?widthMeasureSpec和heightMeasureSpec這兩個int類型的參數(shù),看名字應該知道是跟寬和高有關(guān)系,但它們其實不是寬和高,而是由寬、高和各自方向上對應的模式來合成的一個值:其中,在int類型的32位二進制位中,31-30這兩位表示模式,0~29這三十位表示寬和高的實際值.其中模式一共有三種,被定義在Android中的View類的一個內(nèi)部類中:View.MeasureSpec:
?
①UNSPECIFIED:表示默認值,父控件沒有給子view任何限制。------二進制表示:00
②EXACTLY:表示父控件給子view一個具體的值,子view要設置成這些值的大小。------二進制表示:01
③AT_MOST:表示父控件個子view一個最大的特定值,而子view不能超過這個值的大小。------二進制表示:10
?
二、MeasureSpec
?
MeasureSpe描述了父View對子View大小的期望.里面包含了測量模式和大小.我們可以通過以下方式從MeasureSpec中提取模式和大小,該方法內(nèi)部是采用位移計算.
int specMode = MeasureSpec.getMode(measureSpec);//得到模式
int specSize = MeasureSpec.getSize(measureSpec);//得到大小
?
也可以通過MeasureSpec的靜態(tài)方法把大小和模式合成,該方法內(nèi)部只是簡單的相加.
MeasureSpec.makeMeasureSpec(specSize,specMode);
?
每個View都包含一個ViewGroup.LayoutParams類或者其派生類,LayoutParams中包含了View和它的父View之間的關(guān)系,而View大小正是View和它的父View共同決定的。
我們平常使用類似于RelativeLayout和LinearLayout的時候,在其內(nèi)部添加view的時候,不管是布局文件中加入還是在代碼中使用addView方法添加,實際上都會調(diào)用這個onMeasure方法,而measure和onMeasure中的兩個參數(shù),是由各級父控件往子控件/子view進行一層層傳遞的。我們可以在xml中定義Layout的寬和高的具體的值或?qū)捀叩奶畛浞绞?#xff1a;matchparent/wrapcontent,也可以在代碼中使用LayoutParams設置,而實際上這里設置的值就會對應到上面的measure和onMeasure方法中的兩個參數(shù)的模式,對應關(guān)系如下:
?
具體的值(如width=200dp)和matchparent/fillparent,對應模式中的MeasureSpec.EXACTLY
?
包裹內(nèi)容(width=wrapcontent)則對應模式中的MeasureSpec.AT_MOST
?
系統(tǒng)調(diào)用measure方法,從父控件到子控件的heightMeasureSpec的傳遞是有一套對應的判斷規(guī)則的,列表如下:
一個view的寬高尺寸,只有在測量之后才能得到,也就是measure方法被調(diào)用之后。大家都應該使用過View.getWidth()和View.getHeight()方法,這兩個方法可以返回view的寬和高,但是它們也不是在一開始就可以得到的,比如oncreate方法中,因為這時候measure方法還沒有被執(zhí)行,測量還沒有完成,我們可以來作一個簡單的實驗:自定義一個MyView,繼承View類,然后在OnCreate方法中,將其new出來,通過addview方法,添加到現(xiàn)在的布局中。然后調(diào)用MyView對象的getWidth()和getHeight()方法,會發(fā)現(xiàn)得到的都是0。
?
?
onMeasure通過父View傳遞過來的大小和模式,以及自身的背景圖片的大小得出自身最終的大小,然后通過setMeasuredDimension()方法設置給mMeasuredWidth和mMeasuredHeight.
?
普通View的onMeasure邏輯大同小異,基本都是測量自身內(nèi)容和背景,然后根據(jù)父View傳遞過來的MeasureSpec進行最終的大小判定,例如TextView會根據(jù)文字的長度,文字的大小,文字行高,文字的行寬,顯示方式,背景圖片,以及父View傳遞過來的模式和大小最終確定自身的大小.
?
三、ViewGroup的onMeasure
?
ViewGroup是個抽象類,本身沒有實現(xiàn)onMeasure,但是他的子類都有各自的實現(xiàn),通常他們都是通過measureChildWithMargins函數(shù)或者其他類似于measureChild的函數(shù)來遍歷測量子View,被GONE的子View將不參與測量,當所有的子View都測量完畢后,才根據(jù)父View傳遞過來的模式和大小來最終決定自身的大小.
在測量子View時,會先獲取子View的LayoutParams,從中取出寬高,如果是大于0,將會以精確的模式加上其值組合成MeasureSpec傳遞子View,如果是小于0,將會把自身的大小或者剩余的大小傳遞給子View,其模式判定在前面表中有對應關(guān)系.
ViewGroup一般都在測量完所有子View后才會調(diào)用setMeasuredDimension()設置自身大小,如第一張圖所示.
?
可能看到現(xiàn)在,還是沒搞清楚Android系統(tǒng)通過measure和onmeasure一層層傳遞參數(shù)的具體方法。在研究這個問題之前,先來看一下最簡單的helloworld的UI層級關(guān)系圖:
為了方便起見,這里我們使用requestWindowFeature(Window.FEATURE_NO_TITLE);去除標題欄的影響,只看層級關(guān)系。
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context="${relativePackage}.${activityClass}" ><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@string/hello_world" /></RelativeLayout> package com.example.hello;import android.app.Activity; import android.os.Bundle; import android.view.Window;public class MainActivity extends Activity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);requestWindowFeature(Window.FEATURE_NO_TITLE);setContentView(R.layout.activity_main);} }UI層級關(guān)系圖:
可以發(fā)現(xiàn)最簡單的helloworld的層級關(guān)系圖是這樣的,最開始是一個PhoneWindow的內(nèi)部類DecorView,這個DecorView實際上是系統(tǒng)最開始加載的最底層的一個viewGroup,它是FrameLayout的子類,然后加載了一個LinearLayout,然后在這個LinearLayout上加載了一個id為content的FrameLayout和一個ViewStub,這個實際上是原本為ActionBar的位置,由于我們使用了requestWindowFeature(Window.FEATURE_NO_TITLE),于是變成了空的ViewStub;然后在id為content的FrameLayout才加載了我們的布局XML文件中寫的RelativeLayout和TextView。
?
那么measure方法在系統(tǒng)中傳遞尺寸和模式,必定是從DecorView這一層開始的,我們假定手機屏幕是320*480,那么DecorView最開始是從硬件的配置文件中讀取手機的尺寸,然后設置measure的參數(shù)大小為320*480,而模式是EXCACTLY,傳遞關(guān)系可以由下圖示意:
?
總結(jié)
以上是生活随笔為你收集整理的android自定义控件(6)-详解在onMeasure()方法中如何测量一个控件尺寸的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 基于gulp编写的一个简单实用的前端开发
- 下一篇: [20170410]快速找回触发器内容.