View的layout()
下面的方法解釋均翻譯於官方文檔
/**
* layout機制有兩個階段
* 第一階段:測量,在這個階段,每個父view都會調用layout來爲子view指定位置,最經典的做法就是使用存於measure pass()的子view測量值
* 第二階段:分配一個大小和位置給此view和它所有的子view
* View的衍生類不應該重寫這個方法,若該類有子view應該重寫onLayout(),並在這個方法調用每個子view的layout()
* 上下左右的位置都是相對於該view的父控件來說的。
*/
public void layout(int l, int t, int r, int b) {
if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
/**
* onMesure()
* 測量視圖和它的內容來確定寬高,注意這個方法是由measure()喚醒,並且在子類重寫來爲它的內容提供更精準有效的測量值。
* 基類測量的實現默認爲背景的大小,除非MeasureSpec提供更大的size。子類應該重寫onMeasure()爲自己的內容提供更好的測量值
* 規定:當年重寫這個方法時必須調用setMeasuredDimension()來保存這個view的寬高值。若是沒保存成功就會觸發measure()拋出IllegalStateException異常。調用父類的onMeasure()是個比較好的辦法。
* 如果這個方法在子類被重寫,那它應該確保測量的寬高至少是視圖的最低寬高。
* @param widthMeasureSpec
* @param heightMeasureSpec
* 兩個參數代表這個控件父view的寬高,並與MeasureSpec有緊密聯繫
*/
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
}
/**
* setFrame()
* 爲這個view分配大小和位置。此方法在layout()裏調用
* 上下左右的位置都是相對於父view的
* 大小與位置與前一次有改變就會返回true
*/
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
/**
* onLayout()
* 當這個view需要爲子view分配大小和位置時會在layout()裏調用它。
* 所以有子view的衍生類應該重寫這個方法並調用它的每個子view的layout()。
* 5個參數的解釋跟setFrame()一樣。
*/
onLayout(changed, l, t, r, b);
}
}
總結:大致的layout過程是先調用setFrame()爲這個view分配大小和位置,然後回調onLayout函數,對於View來說onLayout只是一個空實現,一般只有當它是ViewGroup需要爲子view分配大小和位置時才重寫它。然後它會參考measure過程中計算得到的mMeasuredWidth和mMeasuredHeight來安排子視圖在父視圖中顯示的位置,但這不是必須的,我們可以自己傳入4個參數調用layout()來安排子view具體位置。
其實具體measure()是什麼時候執行的還是不清楚,找了好久也沒找着,不過目前來說這個不重要就先放放吧。
MeasureSpec
上面說onMeasure(widthMeasureSpec,heightMeasureSpec)時就說到這兩個參數跟MeasureSpec有密切聯繫,我們先就大致知道怎麼用他們就行。
1. MeasureSpec.getMode(widthMeasureSpec)有三個值
- EXACTLY:測量的佈局大小是精確的,比如match_parent或具體dp值。
- AT_MOST:子佈局可以根據自己的大小選擇任意大小。比如wrap_content,但它最大不能超出父控件大小 。
- UNSPECIFIED:父佈局沒有給子佈局任何限制,子佈局可以任意大小。這種情況大多出現與ListView 。
2. MeasureSpec.getSize(widthMeasureSpec)
得到測量大小,以像素爲單位。