在自定义view中多半都会去重写onMeasure方法,进行view的测量,测量出大小后,再在onDraw方法中进行绘制,下面是一段简易的自定义view的代码:
public class MyTextView extends View {
//在new一个MyTextView对象的时候会调用
public MyTextView(Context context) {
this(context,null);
}
//在xml布局文件中使用MyTextView会调用
public MyTextView(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
//在xml布局文件中使用MyTextView并给MyTextView设置style样式会调用
public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//获取宽高模式
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode=MeasureSpec.getMode(heightMeasureSpec);
//获取宽高大小
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
}
}
通过MeasureSpec.getMode()可以获取到宽高模式,系统提供了下面三个模式的常量值:
//在xml布局中设置为wrap_content
MeasureSpec.AT_MOST;
//在xml布局中设置为具体的值比如100dp或者match_parent或者fill_parent
MeasureSpec.EXACTLY;
//在实际开发中很少用到,ScrollView、ListView等源码中有使用到
MeasureSpec.UNSPECIFIED;
在项目开发中有时候会用到ScrollView和ListView的嵌套(当然现在很少用到了),就会碰到ListView显示条目不全的问题,其实就是ScrollView在测量时将ListView的测量模式设置为MeasureSpec.UNSPECIFIED,ListView在测量时的判断所导致的;ScrollView是一个布局容器,肯定是继承自ViewGoup的,在ViewGroup中会发现measureChild()方法,该方法是用来测量布局容器中子view的方法,在ViewGoup中的measureChild()方法中并没有指定子view的测量模式,
//这个是ViewGroup中的measureChild方法源码
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);
//调用view中的measure方法去测量子view
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
ViewGroup中没有做任何动作,但是ScrollView中重写了ViewGroup中的measureChild()方法,
//这是ScrollView中measureChild的源码,
@Override
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
ViewGroup.LayoutParams lp = child.getLayoutParams();
int childWidthMeasureSpec;
int childHeightMeasureSpec;
//在这里指定了子view的height mode 为MeasureSpec.UNSPECIFIED
childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft
+ mPaddingRight, lp.width);
final int verticalPadding = mPaddingTop + mPaddingBottom;
childHeightMeasureSpec = MeasureSpec.makeSafeMeasureSpec(
Math.max(0, MeasureSpec.getSize(parentHeightMeasureSpec) - verticalPadding),
MeasureSpec.UNSPECIFIED);
//进行子view的测量
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
在这里首先要明白的是childMeasure(childeWidthMeasureSpec,childHeightMeasureSpec);中的两个参数childeWidthMeasureSpec和childHeightMeasureSpec;childeWidthMeasureSpec和childHeightMeasureSpec它是包含两部分的,前两位是mode,后30为是值(size);接下来就会调用view中的measure方法及onMeasure()方法,并把宽高值和宽高模式传入;但是ListView的话将View中的onMeasure方法进行了重写;
//这里是ListView中onMeasure方法源码
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Sets up mListPadding
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//获取宽高模式
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
//获取宽高大小
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int childWidth = 0;
int childHeight = 0;
int childState = 0;
mItemCount = mAdapter == null ? 0 : mAdapter.getCount();
if (mItemCount > 0 && (widthMode == MeasureSpec.UNSPECIFIED
|| heightMode == MeasureSpec.UNSPECIFIED)) {
final View child = obtainView(0, mIsScrap);
// Lay out child directly against the parent measure spec so that
// we can obtain exected minimum width and height.
measureScrapChild(child, 0, widthMeasureSpec, heightSize);
childWidth = child.getMeasuredWidth();
childHeight = child.getMeasuredHeight();
childState = combineMeasuredStates(childState, child.getMeasuredState());
if (recycleOnMeasure() && mRecycler.shouldRecycleViewType(
((LayoutParams) child.getLayoutParams()).viewType)) {
mRecycler.addScrapView(child, 0);
}
}
//在这里对宽度模式进行了判断
if (widthMode == MeasureSpec.UNSPECIFIED) {
widthSize = mListPadding.left + mListPadding.right + childWidth +
getVerticalScrollbarWidth();
} else {
widthSize |= (childState & MEASURED_STATE_MASK);
}
//在这里对高度模式进行了判断
if (heightMode == MeasureSpec.UNSPECIFIED) {
//如果高度的模式是MeasureSpec.UNSPECIFIED 计算的heightSize大小就是top+bottom+单个item条目高度(childHeight)+分割线的高度
heightSize = mListPadding.top + mListPadding.bottom + childHeight +
getVerticalFadingEdgeLength() * 2;
}
if (heightMode == MeasureSpec.AT_MOST) {
// TODO: after first layout we should maybe start at the first visible position, not 0
//如果高度模式是MeasureSpec.AT_MOST就会去计算所有item条目的高度,并赋值个heightSize
heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);
}
//设置计算好的宽高
setMeasuredDimension(widthSize, heightSize);
mWidthMeasureSpec = widthMeasureSpec;
}
在ScrollView和ListView嵌套的时候,ScrollView给ListView高度模式设置的是MeasureSpec.UNSPECIFIED,同时ListView又对onMeasure方法进行了重写,所以就出现了ScrollView嵌套ListView条目显示不全的问题,其实只需将ListView的高度模式设置为MeasureSpec.AT_MOST就可以去测量计算所有item的高度了;
public class MyListView extends ListView {
public MyListView(Context context) {
this(context,null);
}
public MyListView(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public MyListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//指定ListView的高度模式为MeasureSpecAT_MOST 并指定大小为Integer最大值右移两位
heightMeasureSpec=MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE>>2,MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
这样子问题就解决了,宽高模式设置为MeasureSpec.AT_MOST容易理解,大小设置为Integer的最大值右移两位;
//这里是ListView中测量所有item高度的源码 maxHeight就是heightSize
final int measureHeightOfChildren(int widthMeasureSpec, int startPosition, int endPosition,
int maxHeight, int disallowPartialChildPosition) {
final ListAdapter adapter = mAdapter;
if (adapter == null) {
return mListPadding.top + mListPadding.bottom;
}
// Include the padding of the list
//定义的返回height变量
int returnedHeight = mListPadding.top + mListPadding.bottom;
final int dividerHeight = mDividerHeight;
// The previous height value that was less than maxHeight and contained
// no partial children
int prevHeightWithoutPartialChild = 0;
int i;
View child;
// mItemCount - 1 since endPosition parameter is inclusive
endPosition = (endPosition == NO_POSITION) ? adapter.getCount() - 1 : endPosition;
final AbsListView.RecycleBin recycleBin = mRecycler;
final boolean recyle = recycleOnMeasure();
final boolean[] isScrap = mIsScrap;
for (i = startPosition; i <= endPosition; ++i) {
child = obtainView(i, isScrap);
measureScrapChild(child, i, widthMeasureSpec, maxHeight);
if (i > 0) {
// Count the divider for all but one child
returnedHeight += dividerHeight;
}
// Recycle the view before we possibly return from the method
if (recyle && recycleBin.shouldRecycleViewType(
((LayoutParams) child.getLayoutParams()).viewType)) {
recycleBin.addScrapView(child, -1);
}
returnedHeight += child.getMeasuredHeight();
//maxHeight值是Integer.MAX_VALUE>>2为,所有returnedHeight的值是永远小于maxHeight的,这个if条件是永远不成立的,这样就可以返回returnedHeight计算出来的值,也就是设置height大小为Integer.MAX_VALUE>>2的原因
if (returnedHeight >= maxHeight) {
// We went over, figure out which height to return. If returnedHeight > maxHeight,
// then the i'th position did not fit completely.
return (disallowPartialChildPosition >= 0) // Disallowing is enabled (> -1)
&& (i > disallowPartialChildPosition) // We've past the min pos
&& (prevHeightWithoutPartialChild > 0) // We have a prev height
&& (returnedHeight != maxHeight) // i'th child did not fit completely
? prevHeightWithoutPartialChild
: maxHeight;
}
if ((disallowPartialChildPosition >= 0) && (i >= disallowPartialChildPosition)) {
prevHeightWithoutPartialChild = returnedHeight;
}
}
// At this point, we went through the range of children, and they each
// completely fit, so return the returnedHeight
return returnedHeight;
}
这里涉及到java中的左移,右移运算符;
<< : 左移运算符,num << 1,相当于num乘以2
>> : 右移运算符,num >> 1,相当于num除以2
>>> : 无符号右移,忽略符号位,空位都以0补齐