1.問題描述
在使用自定義View時(shí),View寬/高的wrap_content屬性不起自身應(yīng)用的作用身冬,而且是起到了與match_parent相同作用衅胀?
2.問題分析
問題出現(xiàn)在View的寬/高 設(shè)置,那我們直接來看自定義View繪制中第一步對View寬/高設(shè)置的過程:measure過程中的onMeasure()方法:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//參數(shù)說明:父view提供的測量規(guī)格
//setMeasuredDimension()用于獲取view寬高的測量值酥筝,這兩個(gè)
//參數(shù)是通過getDefaultSize()來獲取的
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
繼續(xù)往下看getDefaultSize(),其作用是根據(jù)父View提供的寬/高測量規(guī)格計(jì)算View自身的寬/高值滚躯。源碼分析如下:
public static int getDefaultSize(int size, int measureSpec) {
//參數(shù)說明:
//第一個(gè)參數(shù)size:提供默認(rèn)的大小
//第二個(gè)參數(shù):父view提供的測量規(guī)格
//設(shè)置默認(rèn)大小
int result = size;
//獲取寬/高測量規(guī)格的模式和大小
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
//模式為UNSPECIFIED時(shí),使用提供的默認(rèn)大小
//即第一個(gè)參數(shù) size
case MeasureSpec.UNSPECIFIED:
result = size;
break;
//模式為AT_MOST,EXACTLY時(shí),使用view測量后的寬/高值
// 即measureSpec中的specSize
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
//返回view的寬高值
return result;
}
1.getDefaultSize()的默認(rèn)實(shí)現(xiàn)中掸掏,當(dāng)View的測量模式是AT_MOST或EXACTLY時(shí)茁影,View的大小都會被設(shè)置成父View的specSize。
2.因?yàn)锳T_MOST對應(yīng)wrap_content,EXACTLY對應(yīng)match_parent丧凤,所以默認(rèn)情況下募闲,wrap_content和match_parent是具有相同的效果。
這里就解決了wrap_content起到了與match_parent相同的作用愿待。
那么有人會問浩螺,View的MeasureSpec是怎么賦值的?
我們知道仍侥,View的MeasureSpec的值是根據(jù)View的布局參數(shù)(LayoutParams)和父容器的MeasureSpec值計(jì)算得來的年扩,具體計(jì)算邏輯封裝在getChildMeasureSpec()里。
我們來分析下getChildMeasureSpec的源碼:
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);
}
//根據(jù)父視圖的MeasureSpec和布局參數(shù)LayoutParams访圃,計(jì)算單個(gè)子View的MeasureSpec
//即子View的確切大小由兩方面共同決定:父view的measureSpec和子view的LayoutParams屬性
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
//參數(shù)說明
//spec:父view的詳細(xì)測量值
//padding:view當(dāng)前尺寸的內(nèi)邊距和外邊距(padding,margin)
//childDimension:子視圖的布局參數(shù)(寬/高)
//父view的測量模式
int specMode = MeasureSpec.getMode(spec);
//父view的大小
int specSize = MeasureSpec.getSize(spec);
//通過父view計(jì)算出子view=父大小-邊距
int size = Math.max(0, specSize - padding);
//子view想要的實(shí)際大小和模式(需要計(jì)算)
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
//當(dāng)父view的模式為EXACTLY時(shí)厨幻,父view強(qiáng)加給了子view確切的值
//一般是父view設(shè)置為match_parent后者固定值的ViewGroup
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
//當(dāng)子view的LayoutParams>0,即有確切的值
if (childDimension >= 0) {
//子View大小為子自身所賦的值 模式大小為EXACTLY
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
//當(dāng)子View的LayoutParams為MATCH_PARENT(-1)
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
//子view大小為父view腿时,模式為EXACTLY
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
//當(dāng)子view的LayoutParams為WRAP_CONTENT(-2)
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
//子view決定自己的大小况脆,但是最大不能超過父View,模式為AT_MOST
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
//當(dāng)父view的模式為AT_MOST 時(shí)批糟,父view強(qiáng)加給了view一個(gè)最大值(一般是父View設(shè)置為了wrap_content)
// 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;
//當(dāng)父view的模式為UNSPECIFIED時(shí)格了,父容器不對view有
//任何限制,要多大給多大 多見于ListView,GridView等
// 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
//子view大小為子自身所賦的值
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);
}
從上面可以看出徽鼎,當(dāng)子view的布局參數(shù)使用match_parent或wrap_content時(shí)盛末,子view的specSize總是等于父容器當(dāng)前剩余空間大小
3.總結(jié)
- 在onMeasure()中的getDefaultSize()的默認(rèn)實(shí)現(xiàn)中,當(dāng)View的測量模式是AT_MOST或EXACTLY時(shí)否淤,View的大小都會被設(shè)置成View MeasureSpec的specSize悄但。
- 在計(jì)算子View MeasureSpec的getChildMeasureSpec()中,子View MeasureSpec在屬性被設(shè)置為wrap_content或match_parent情況下石抡,子View MeasureSpec的specSize被設(shè)置成parentSize=父容器當(dāng)前剩余空間大小檐嚣,所以:wrap_content起到了和match_parent相同的作用。
4.解決方案
當(dāng)自定義View的布局參數(shù)設(shè)置成wrap_content時(shí)啰扛,指定一個(gè)默認(rèn)大小(寬/高)嚎京。具體是在復(fù)寫onMeasure()里進(jìn)行設(shè)置。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 獲取寬-測量規(guī)則的模式和大小
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
// 獲取高-測量規(guī)則的模式和大小
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
// 設(shè)置wrap_content的默認(rèn)寬 / 高值
// 默認(rèn)寬/高的設(shè)定并無固定依據(jù),根據(jù)需要靈活設(shè)置
// 類似TextView,ImageView等針對wrap_content均在onMeasure()對設(shè)置默認(rèn)寬 / 高值有特殊處理,具體讀者可以自行查看
int mWidth = 400;
int mHeight = 400;
// 當(dāng)布局參數(shù)設(shè)置為wrap_content時(shí)隐解,設(shè)置默認(rèn)值
if (getLayoutParams().width == ViewGroup.LayoutParams.WRAP_CONTENT && getLayoutParams().height == ViewGroup.LayoutParams.WRAP_CONTENT) {
setMeasuredDimension(mWidth, mHeight);
// 寬 / 高任意一個(gè)布局參數(shù)為= wrap_content時(shí)鞍帝,都設(shè)置默認(rèn)值
} else if (getLayoutParams().width == ViewGroup.LayoutParams.WRAP_CONTENT) {
setMeasuredDimension(mWidth, heightSize);
} else if (getLayoutParams().height == ViewGroup.LayoutParams.WRAP_CONTENT) {
setMeasuredDimension(widthSize, mHeight);
}
這樣,當(dāng)你的自定義View的寬/高設(shè)置成wrap_content屬性時(shí)就會生效了煞茫。