一、measure 過程
對 ViewGroup 來說椅亚,除了完成自己的 measure 過程以外葡秒,還要遍歷所有子 View 的 measure 方法,各個子元素再去遞歸執(zhí)行這個過程砂碉。
ViewGroup 的 measure 過程代碼調(diào)用順序如下:
1. measure()
Android 源碼中,View 類的聲明是
public class View implements Drawable.Callback, KeyEvent.Callback,
AccessibilityEventSource{}
ViewGroup 類的聲明是
public abstract class ViewGroup extends View implements ViewParent, ViewManager {}
ViewGroup 類是 View 類的子類刻两,ViewGroup 的 measure 過程入口是 View 類中的measure()
方法增蹭,與單一 View 的測量過程中介紹的measure()
方法一致。
/**
* 源碼分析:measure()
* 作用:基本測量邏輯的判斷闹伪;調(diào)用onMeasure()
* 注:與單一View measure過程中講的measure()一致
**/
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 : mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// 調(diào)用onMeasure()計算視圖大小
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
...
}
}
2. onMeasure()
ViewGroup 是一個抽象類沪铭,它沒有重寫 View 的 onMeasure()
方法壮池,該方法作用是父容器測量子 View 的尺寸偏瓤,其測量過程的onMeasure()
方法需要各個子類去具體實現(xiàn)杀怠。
Q:為什么 ViewGroup 的 measure 過程不像單一 View 的 measure 過程那樣對 onMeasure() 做統(tǒng)一的實現(xiàn)?
A:因為不同的 ViewGroup 子類(LinearLayout厅克、RelativeLayout 及自定義的 ViewGroup 子類等)具備不同的布局特性赔退,這導(dǎo)致他們的測量細節(jié)各有不同,ViewGroup 無法對
onMeasure()
做統(tǒng)一實現(xiàn)证舟。
在自定義 ViewGroup 中硕旗,關(guān)鍵在于:根據(jù)需求復(fù)寫onMeasure()
從而實現(xiàn)你的子View測量邏輯。復(fù)寫onMeasure()的套路如下:
/**
* 根據(jù)自身測量邏輯復(fù)寫onMeasure()女责,分3步:
* (1)遍歷所有子view漆枚,依次進行測量
* (2)合并所有子View的尺寸大小,最終得到ViewGroup父視圖的測量值(自身實現(xiàn))
* (3)存儲測量后View寬/高值抵知,調(diào)用setMeasuredDimension()
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 定義存放測量后view寬/高的變量
int widthMeasure;
int heightMeasure;
// 1,遍歷所有子View墙基,依次進行測量
measureChildren(widthMeasureSpec, heightMeasureSpec);
// 2,合并所有子View的尺寸大小,最終得到父視圖的測量值
void measureCarson {
// 自己實現(xiàn)
}
// 3,存儲測量后view的寬/高
setMeasuredDimension(widthMeasure, heightMeasure);
}
下面看下 LinearLayout 類中實現(xiàn)的
onMeasure()
方法刷喜。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
measureVertical()
和measureHorizontal()
原理類似残制,我們以前者為例,代碼主要分三個步驟:
① 遍歷所有子View 掖疮,依次進行測量
系統(tǒng)首先遍歷子元素初茶,并對每個子元素執(zhí)行measureChildBeforeLayout()
方法,這個方法內(nèi)部會調(diào)用子元素的measure()
方法浊闪,這樣各個元素就開始進入 measure 過程
② 合并所有子View的尺寸大小恼布,得到 ViewGroup 的測量值
系統(tǒng)通過mTotalLength
存儲 LinearLayout 在豎直方向上的高度
③ 存儲 ViewGroup 的測量值
使用setMeasuredDimension()
方法
/**
* 測量 LinearLayout 垂直方向的測量尺寸
*
* @param widthMeasureSpec 父容器的寬測量規(guī)格
* @param heightMeasureSpec 父容器的高測量規(guī)格
*/
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
final int count = getVirtualChildCount();
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
// 對于每一個垂直方向上的子View
/**
* 步驟1:遍歷所有子View ,依次進行測量
* 注:該方法內(nèi)部搁宾,最終會調(diào)用measureChildren()桥氏,從而遍歷所有子View & 測量
**/
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
if (child == null) {
mTotalLength += measureNullChild(i);
continue;
}
// 若子View不可見,則直接跳過該View的測量過程
// 注:若可見屬性設(shè)置為VIEW.INVISIBLE猛铅,則仍需測量
if (child.getVisibility() == View.GONE) {
i += getChildrenSkipCount(child, i);
continue;
}
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
totalWeight += lp.weight;
final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;
if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {
// 如果LinearLayout的specMode為EXACTLY且子View設(shè)置了weight屬性字支,在這里會跳過子View的measure過程
// 同時標記skippedMeasure屬性為true,后面會根據(jù)該屬性決定是否進行第二次measure
// 若LinearLayout的子View設(shè)置了weight奸忽,會進行兩次measure計算堕伪,比較耗時
// 這就是為什么LinearLayout的子View需要使用weight屬性時候,最好替換成RelativeLayout布局
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
skippedMeasure = true;
} else {
if (useExcessSpace) {
// The heightMode is either UNSPECIFIED or AT_MOST, and
// this child is only laid out using excess space. Measure
// using WRAP_CONTENT so that we can find out the view's
// optimal height. We'll restore the original height of 0
// after measurement.
lp.height = LayoutParams.WRAP_CONTENT;
}
/**
* 調(diào)用measureChildBeforeLayout()測量子View
*/
final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
heightMeasureSpec, usedHeight);
/**
* 通過 getMeasuredHeight() 獲取View的測量寬/高
* 注:在某些極端情況下栗菜,系統(tǒng)可能需要多次 measure 才能確定最終的寬/高欠雌,在這種情形下,
* 在onMeasure()中通過 getMeasuredHeight() 獲取測量寬高是不準確的疙筹。
* 一個好的習(xí)慣是富俄,在 onLayout() 方法中獲取 View 的最終寬/高
*/
final int childHeight = child.getMeasuredHeight();
if (useExcessSpace) {
// Restore the original height and record how much space
// we've allocated to excess-only children so that we can
// match the behavior of EXACTLY measurement.
lp.height = 0;
consumedExcessSpace += childHeight;
}
// totalLength
/**
* 系統(tǒng)通過 mTotalLength 這個變量來存儲 LinearLayout 在豎直方向的初步高度禁炒。
* 每測量一個元素, mTotalLength就會增加霍比,增加的部分主要包括了子元素的高度以及子元素在豎直方向上的 margin 等幕袱。
*/
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
lp.bottomMargin + getNextLocationOffset(child));
}
}
/**
* 步驟2:合并所有子View的尺寸大小,最終得到ViewGroup的測量值
* 子元素測量完畢后,LinearLayout 根據(jù)子元素的情況來測量自己的大小悠瞬。
**/
// 記錄 LinearLayout 占用的總高度们豌,除了子View占用的高度,還有本身的 padding 屬性
// Add in our padding
mTotalLength += mPaddingTop + mPaddingBottom;
int heightSize = mTotalLength;
// Check against our minimum height
heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
// Reconcile our calculated size with the heightMeasureSpec
int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
heightSize = heightSizeAndState & MEASURED_SIZE_MASK;
/**
* 步驟3:存儲測量后View寬/高的值:調(diào)用setMeasuredDimension()
**/
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
heightSizeAndState);
}
3. measureChildren()
ViewGroup 類中的方法浅妆。在該方法中遍歷父容器中的所有子元素望迎,逐個進行 measure。
/**
* 遍歷凌外,測量父容器中的所有子View
*
* @param widthMeasureSpec 父容器的寬測量規(guī)格
* @param heightMeasureSpec 父容器的高測量規(guī)格
*/
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < size; ++i) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
// 調(diào)用measureChild()方法對子View進行進一步測量
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
4. measureChild()
ViewGroup 類中的方法辩尊。該方法測量某一具體的子元素,測量過程參見自定義View——單一View的measure過程康辑。
/**
* 計算子View的MeasureSpec摄欲,并且測量子View最終的寬/高
*
* @param child The child to measure
* @param parentWidthMeasureSpec The width requirements for this view
* @param parentHeightMeasureSpec The height requirements for this view
*/
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
// 子View自身的布局參數(shù)
final LayoutParams lp = child.getLayoutParams();
// 計算子View的測量規(guī)格
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
// 根據(jù)上述參數(shù)測量子View
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
二、重點說明
參考文獻
自定義View Measure過程 - 最易懂的自定義View原理系列(2)
任玉剛_Android開發(fā)藝術(shù)探索