天天看點

自定義控件View之onMeasure調用時機源碼分析

先上測試代碼:

MainActivity.java

MyView.java

activity_main.xml

正常運作後,檢視對應的Log:

自定義控件View之onMeasure調用時機源碼分析

從Log輸出可以看出在一個View的繪制過程中,onMeasure是被多次調用了的。下面通過源碼來一步步分析 onMeasure(int widthMeasureSpec, int heightMeasureSpec)函數,尤其是傳過來的兩個參數到底是從哪裡來的。

首先看下MainActivity裡面的setContentView,進入該函數後,其對應的代碼如下:

Activity.java

即調用了getWindow()的setContentView方法,檢視getWindow方法,其返還的是類Window的一個執行個體mWindow,該類是一個抽象類,其具體實作類是PhoneWindow,即調用的是PhoneWindow的setContentView方法,檢視相應的代碼如下:

PhoneWindow.java

該方法首先判斷mContentParent是否為空,不為空則調用installDecor()方法來初始化mContentParent,檢視具體的代碼:

其中generateDecor方法就直接傳回一個DecorView,代碼如下:

而generateLayout(mDecor)方法會根據程式Activity設定的style來布局顯示的界面,其代碼如下:

通過一張圖來分析下一個視窗的布局具體是怎樣的。

自定義控件View之onMeasure調用時機源碼分析

圖上标的很詳細,在最外層是一個FramLayout,其實也就是DecorView,是所有視窗的根布局,在該根布局下有一個(0)LinearLayout和一個(1)View,這個(1)View就是狀态欄,(0)LinearLayout裡面有個FrameLayout,在裡面的多個View有固定的id,在圖中已經标明,所有在一個Activity通過findViewById擷取的ID_ANDROID_CONTENT就是

(0)FrameLayout->(0)LinearLayout->(0)FrameLayout->(1)FrameLayout對應的View。

要知道onMeasure兩個參數到底是從哪裡來的,還得再找下View是如何繪制的,上一篇文章有分析。View的繪制從ViewRootImpl的performTraversals()函數開始,下面進入該方法中具體分析下。

ViewRootImpl.java

看performTraversals方法中調用的performMeasure的地方,performMeasure即調用了View的measure方法,而measure方法會去調用onMeasure方法。

看下如下兩行代碼

在這兩行代碼中擷取了child的寬高,使用的方法是getRootMeasureSpec,其中參數lp.width是傳入的MATCH_PARENT或者WRAP_CONTENT,mWidth是視窗期望的大小,getRootMeasureSpec代碼如下:

這裡應該很好了解了,其中調用了MeasureSpec類中的方法,關于MeasureSpec類網上資料很多,該類中用一個int值的兩部分分别表示Mode和具體的尺寸。其中最高兩位表示

Mode,而最低的30位表示具體的尺寸值,這裡計算完之後就進入了View的measure函數中,代碼如下:

View.java

該方法是final的,因而不能被繼承,但是裡面提供了onMeasure回調,這樣子類就可以直接繼承onMeasure函數來實作相應的操作。這個View類型的,但是還有一種是ViewGroup類型,也就是容器類型的控件,在具體容器類型的控件裡面可以通過重寫onMeasure來實作,比如FrameLayout中的onMeasure函數如下:

FrameLayout.java

 大概也就是回調本容器裡面的子View的measure函數實作尺寸計算。這裡通過方法ViewGroup類中的getChildMeasureSpec()來擷取子類期望自己擷取的寬高大小。其代碼是

ViewGroup.java

  在重寫onMeasure方法時一定要調用setMeasuredDimension,該方法會将mPrivateFlags經過或使得View知道已經經過了measure這個步驟了。代碼如下:

至此分析結束,是以說一個View的大小是由自己和父類兩者共同決定的。

繼續閱讀