場(chǎng)景一:自定義View宾茂,使用父類的 super.onMeasure
這種場(chǎng)景實(shí)際上是使用了 super.onMeasure 先測(cè)量一遍瓷马,讓系統(tǒng)自己先填充 mMeasuredWidth,mMeasuredHeight 成員變量跨晴,之后就可以通過
getMeasuredWidth(); getMeasuredHeight(); 直接獲取測(cè)量之后的寬高值欧聘。最后再調(diào)用 setMeasuredDimension 重新將計(jì)算出來的新的寬高填充 mMeasuredWidth,mMeasuredHeight 成員變量
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int measureWidth = getMeasuredWidth();
int measureHeight = getMeasuredHeight();
if (measureWidth > measureHeight) {
measureWidth = measureHeight;
} else {
measureHeight = measureWidth;
}
setMeasuredDimension(measureWidth, measureHeight);
}
場(chǎng)景二:自定義View端盆,【不】使用父類的 super.onMeasure
這種場(chǎng)景需要自行根據(jù)View的測(cè)量類型怀骤,算出真實(shí)的寬高费封,并將結(jié)果填充至 mMeasuredWidth,mMeasuredHeight 成員變量蒋伦。
特別說明:widthMeasureSpec弓摘、heightMeasureSpec 是一個(gè)32位的數(shù)值,前2位指代測(cè)量模式痕届,后30位指代大小
xml 中 layout_xxx 的屬性就是父布局對(duì)子Veiw的屬性聲明
MeasureSpec.UNSPECIFIED:父布局對(duì)子View的大小沒有限制韧献,子View想多大都可以
MeasureSpec. AT_MOST:父布局限制了子View的大小上限,子View最大不得超過父布局的上限
MeasureSpec. EXACTLY:父布局指定了子View的大小爷抓,子View只能使用這個(gè)固定值
實(shí)際返回的測(cè)量結(jié)果势决,需要根據(jù)具體業(yè)務(wù)進(jìn)行計(jì)算。
最后依然要調(diào)用 setMeasuredDimension 把測(cè)量出來的結(jié)果填充至 mMeasuredWidth蓝撇,mMeasuredHeight 成員變量果复。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int userSize = 200;
int measureWidth = customResolveSize(userSize, widthMeasureSpec);
int measureHeight = customResolveSize(userSize, heightMeasureSpec);
setMeasuredDimension(measureWidth, measureHeight);
}
private static int customResolveSize(int size, int measureSpec) {
int measureMode = MeasureSpec.getMode(measureSpec);
int measureSize = MeasureSpec.getSize(measureSpec);
int realSize = 0;
switch (measureMode) {
case MeasureSpec.UNSPECIFIED: // 父view對(duì)子view的大小沒有限制,直接返回子view的size
realSize = size;
break;
case MeasureSpec.AT_MOST: // 父view限制了子view的大小上限渤昌,子view的大小不得超過父view指定的值
if (size >= measureSize) {
realSize = measureSize;
} else {
realSize = size;
}
break;
case MeasureSpec.EXACTLY: // 父view指定了子view的大小虽抄,直接返回父view指定的值
realSize = measureSize;
break;
default:
realSize = size;
break;
}
return realSize;
}
場(chǎng)景三:自定義ViewGroup
自定義ViewGroup比較復(fù)雜,難點(diǎn)在測(cè)量過程独柑,例子代碼迈窟,具體返回的測(cè)量寬高值,根據(jù)實(shí)際業(yè)務(wù)計(jì)算忌栅。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//觸發(fā)所有子View的onMeasure函數(shù)去測(cè)量寬高
measureChildren(widthMeasureSpec, heightMeasureSpec);
//MeasureSpec封裝了父View傳遞給子View的布局要求
int wMode = MeasureSpec.getMode(widthMeasureSpec);
int wSize = MeasureSpec.getSize(widthMeasureSpec);
int hMode = MeasureSpec.getMode(heightMeasureSpec);
int hSize = MeasureSpec.getSize(heightMeasureSpec);
switch (wMode) {
case MeasureSpec.EXACTLY: // 說明這個(gè)ViewGroup在父布局中的寬度是一個(gè)定值
mWidth = wSize;
break;
case MeasureSpec.AT_MOST: // 說明這個(gè)ViewGroup會(huì)盡量填滿父布局的寬度车酣,但不能超過父布局的寬度
mWidth = wSize;
break;
case MeasureSpec.UNSPECIFIED: // 說明這個(gè)ViewGroup的寬度不受父布局的寬度約束,有可能會(huì)超過父布局的寬度
break;
}
switch (hMode) {
case MeasureSpec.EXACTLY: // 說明這個(gè)ViewGroup在父布局中的高度是一個(gè)定值
mHeight = hSize;
break;
case MeasureSpec.AT_MOST: // 說明這個(gè)ViewGroup會(huì)盡量填滿父布局的高度索绪,但不能超過父布局的高度
mHeight = hSize;
break;
case MeasureSpec.UNSPECIFIED: // 說明這個(gè)ViewGroup的寬度不受父布局的高度約束湖员,有可能會(huì)超過父布局的高度
break;
}
// setMeasuredDimension 的作用是將測(cè)量出來的最新寬高值設(shè)置到成員變量 mMeasuredWidth,mMeasuredHeight 中瑞驱,下一階段
// onLayout 可以獲取到經(jīng)過測(cè)量之后的準(zhǔn)確寬高值
setMeasuredDimension(mWidth, mHeight);
}
【重點(diǎn)來了】measureChildren娘摔,做了什么事情?
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) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
遍歷子view唤反,過濾掉Gone的子View凳寺,并再次調(diào)用 measureChild 方法。通過 getChildMeasureSpec 方法算出子View的 MeasureSpec 值彤侍,并調(diào)用子View的measure方法肠缨,進(jìn)行子View的測(cè)量
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
蛋疼的來了,getChildMeasureSpec 做了什么拥刻?
/**
* Does the hard part of measureChildren: figuring out the MeasureSpec to
* pass to a particular child. This method figures out the right MeasureSpec
* for one dimension (height or width) of one child view.
*
* The goal is to combine information from our MeasureSpec with the
* LayoutParams of the child to get the best possible results. For example,
* if the this view knows its size (because its MeasureSpec has a mode of
* EXACTLY), and the child has indicated in its LayoutParams that it wants
* to be the same size as the parent, the parent should ask the child to
* layout given an exact size.
*
* @param spec The requirements for this view
* @param padding The padding of this view for the current dimension and
* margins, if applicable
* @param childDimension How big the child wants to be in the current
* dimension
* @return a MeasureSpec integer for the child
*/
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent asked to see how big we want to be
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
實(shí)際就是根據(jù)不同的測(cè)量模式怜瞒,算出真實(shí)的 mode、size,并調(diào)用 MeasureSpec.makeMeasureSpec 生成 MeasureSpec吴汪,并返回惠窄。
過程很繞,看英文原著吧漾橙。實(shí)際使用其實(shí)用 measureChildren 讓系統(tǒng)自己測(cè)量就好了杆融,ViewGroup的實(shí)際寬高值根據(jù)具體情況計(jì)算測(cè)量值即可。
下一個(gè)階段是 onLayout霜运,根據(jù)左脾歇、上、右淘捡、下的原則藕各,計(jì)算子View在ViewGroup的內(nèi)部位置,之后使用 child. layout(int l, int t, int r, int b) 方法焦除,將子View進(jìn)行重新定位激况。
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// 對(duì)子View進(jìn)行位置布局
int count = getChildCount();
for (int i = 0; i < count; i++) {
View childView = getChildAt(i);
childView.layout(xx,xx,xx,xx);
}
}
場(chǎng)景四:讓ViewGroup支持margin
要讓自定義ViewGroup支持 layout_margin 屬性,需要重寫 generateLayoutParams膘魄,generateDefaultLayoutParams
@Override
protected LayoutParams generateLayoutParams(LayoutParams p) {
return new MarginLayoutParams(p);
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
}
@Override
protected LayoutParams generateDefaultLayoutParams() {
return new MarginLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int count = getChildCount();
for (int i = 0; i < count; i++) {
View childView = getChildAt(i);
measureChild(childView, widthMeasureSpec, heightMeasureSpec);
MarginLayoutParams marginLayoutParams = (MarginLayoutParams)childView.getLayoutParams();
int childWidth =
childView.getMeasuredWidth() + marginLayoutParams.leftMargin + marginLayoutParams.rightMargin;
int childHeight =
childView.getMeasuredHeight() + marginLayoutParams.topMargin + marginLayoutParams.bottomMargin;
// ........
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int count = getChildCount();
for (int i = 0; i < count; i++) {
View childView = getChildAt(i);
MarginLayoutParams marginLayoutParams = (MarginLayoutParams)childView.getLayoutParams();
int childWidth =
childView.getMeasuredWidth() + marginLayoutParams.leftMargin + marginLayoutParams.rightMargin;
int childHeight =
childView.getMeasuredHeight() + marginLayoutParams.topMargin + marginLayoutParams.bottomMargin;
// .......
}
}
為什么要重寫 generateLayoutParams乌逐,generateDefaultLayoutParams ?
/**
* Returns a new set of layout parameters based on the supplied attributes set.
*
* @param attrs the attributes to build the layout parameters from
*
* @return an instance of {@link android.view.ViewGroup.LayoutParams} or one
* of its descendants
*/
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(getContext(), attrs);
}
/**
* Returns a safe set of layout parameters based on the supplied layout params.
* When a ViewGroup is passed a View whose layout params do not pass the test of
* {@link #checkLayoutParams(android.view.ViewGroup.LayoutParams)}, this method
* is invoked. This method should return a new set of layout params suitable for
* this ViewGroup, possibly by copying the appropriate attributes from the
* specified set of layout params.
*
* @param p The layout parameters to convert into a suitable set of layout parameters
* for this ViewGroup.
*
* @return an instance of {@link android.view.ViewGroup.LayoutParams} or one
* of its descendants
*/
protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
return p;
}
/**
* Returns a set of default layout parameters. These parameters are requested
* when the View passed to {@link #addView(View)} has no layout parameters
* already set. If null is returned, an exception is thrown from addView.
*
* @return a set of default layout parameters or null
*/
protected LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
}
系統(tǒng)默認(rèn)的ViewGroup只返回了LayoutParams對(duì)象,只能獲取到 layout_width创葡,layout_height 屬性浙踢,獲取不到 margin 的屬性
public LayoutParams(Context c, AttributeSet attrs) {
TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_Layout);
setBaseAttributes(a,
R.styleable.ViewGroup_Layout_layout_width,
R.styleable.ViewGroup_Layout_layout_height);
a.recycle();
}
如果想獲取 margin 的屬性,則需要返回 MarginLayoutParams
public MarginLayoutParams(Context c, AttributeSet attrs) {
super();
TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_MarginLayout);
setBaseAttributes(a,
R.styleable.ViewGroup_MarginLayout_layout_width,
R.styleable.ViewGroup_MarginLayout_layout_height);
int margin = a.getDimensionPixelSize(
com.android.internal.R.styleable.ViewGroup_MarginLayout_layout_margin, -1);
.......
}
由于 MarginLayoutParams 是 LayoutParams 的派生類灿渴,所以 (MarginLayoutParams) 強(qiáng)轉(zhuǎn)是合法的洛波,不會(huì)報(bào)錯(cuò)
自定義View、ViewGroup講完骚露。