您的位置:首页 > 其它

onMeasure()源码分析及自定义View对于wrap_content的支持

2016-03-25 11:18 417 查看
/**
*
* 文档描述:
* (1)onMeasure()源码分析
* (2)自定义View时重写onMeasure()实现对于wrap_content的支持
*
* 原文地址:
* http://blog.csdn.net/lfdfhl *
* 参考资料:
* http://blog.csdn.net/lfdfhl/article/details/50880382 *
*
* onMeasure()源码流程如下:
* 在onMeasure调用setMeasuredDimension()设置View的宽和高.
* 在setMeasuredDimension()方法中会调用getDefaultSize()获取View的宽和高.
* 在getDefaultSize()方法中又会调用到getSuggestedMinimumWidth()或者
* getSuggestedMinimumHeight()获取到View宽和高的最小值.
*
* 即这一系列的方法调用顺序为:
* onMeasure()--->setMeasuredDimension()--->getDefaultSize()--->
* getSuggestedMinimumWidth()或getSuggestedMinimumHeight()
*
* 为了理清这几个方法之间的调用及其作用,在此按照倒序分析每个方法的源码.
*
*/
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

/**
* Returns the suggested minimum width that the view should use
* 返回View的宽度的最小值MinimumWidth.
*
* 在此需要注意该View是否有背景.
* (1)若该View没有背景.那么该MinimumWidth为:View本身的最小宽度即mMinWidth.
*    有两种方法可以设置该mMinWidth值
*    1.1 XML布局文件中定义的minWidth
*    1.2 调用View的setMinimumWidth()方法为该值赋值
* (2)若该View有背景.那么该MinimumWidth为:
*    View本身最小宽度mMinWidth和View背景的最小宽度的最大值
*
*  getSuggestedMinimumHeight()方法与此处分析很类似,故不再赘述.
*
*/
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}

/**
* Utility to return a default size. Uses the supplied size if the
* MeasureSpec imposed no constraints. Will get larger if allowed
* by the MeasureSpec.
*
* @param size         Default size for this view
* @param measureSpec  Constraints imposed by the parent
* @return             The size this view should be.
*
* 获取View的宽或者高的大小
*
* 注意方法:getDefaultSize(int size, int measureSpec)第一个参数
* size是调用getSuggestedMinimumWidth()方法获得的View的宽或高的最小值
*
* 该方法的返回值有两种情况:
* (1)measureSpec的specMode为MeasureSpec.UNSPECIFIED
*    在此情况下该方法的返回值就是View的宽或者高最小值.
*    该情况很少见,基本上可以忽略
* (2)measureSpec的specMode为MeasureSpec.AT_MOST或MeasureSpec.EXACTLY:
*    在此情况下该方法的返回值就是measureSpec中的specSize.
*    这个值就是系统测量View得到的值.
*
*
*  除去第一种情况不考虑以外,可知:
*  View的宽和高由measureSpec中的specSize决定!!!!!!!!
*
*
*  所以在自定义View重写onMeasure()方法时必须设置wrap_content时自身的大小.
*
*
*  这是为什么呢?
*
*  因为如果子View在XML的布局文件中对于大小的设置采用wrap_content,
*  不管父View的specMode是 MeasureSpec.AT_MOST还是MeasureSpec.EXACTLY
*  对于子View而言:它的specMode都是MeasureSpec.AT_MOST,并且其大小都是
*  parentLeftSize即父View目前剩余的可用空间.
*  这时wrap_content就失去了原本的意义,变成了match_parent一样了.
*
*  所以自定义View对于onMeasure()的重写请参考该文最后的代码.
*
*
*  对于MeasureSpec请参考:
*  http://blog.csdn.net/lfdfhl/article/details/50880382 *
*/
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);

switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}

/**
* This mehod must be called by onMeasure(int,int)to store the
* measured width and measured height.
*
* 设置View宽和高的测量值
*/
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= MEASURED_DIMENSION_SET;
}

/**
*
* 在自定义View重写onMeasure()方法时必须设置wrap_content时自身的大小.
* 请参见示图中的绿色标记部分.
*
* (1)如果在xml布局中View的宽和高均用wrap_content.那么需要设置
*     View的宽和高为mWidth和mHeight.这两者为该View默认的宽和高.
* (2)如果在xml布局中View的宽或高其中一个采用wrap_content,那么
*     就将该值设置为默认的宽或高,另外的一个值采用系统测量的specSize即可.
*
*  请注意一个问题:
*  在这段代码中用的判断条件:
*  widthSpecMode==MeasureSpec.AT_MOST或者heightSpecMode==MeasureSpec.AT_MOST
*  或者兼而有之;总之,这里的着眼点是模式MeasureSpec.AT_MOST
*
*  看到这里再联想到下图就有一个疑问了:
*  如果在布局文件中采用match_parent,并且父容器的SpecMode为MeasureSpec.AT_MOST,
*  那么此时该子View的SpecMode也为MeasureSpec.AT_MOST且其View的大小为parentLeftSize.
*  既然此时该子VIew的SpecMode也为MeasureSpec.AT_MOST那么按照这里的判断逻辑,它的宽或者
*  高至少有一个会被设置成默认值(mWidth和mHeight).
*  请参见示图中的红色标记部分.
*
*
*  如果按照这样的理解就说不通了:
*  子View在布局文件中利用match_parent设置自己的大小,但是由于父容器的SpecMode为MeasureSpec.AT_MOST
*  于是在系统测量的时候自己的SpecMode也被设置为MeasureSpec.AT_MOST.于是在执行onMeasure()方法的时候,
*  利用match_parent设置自己的就无效了,会被对应的设置为默认值(mWidth和mHeight).
*
*  看到这里好像觉得也没啥不对,但是不符合常理!!!!!到底是哪里错了????
*
*  我们这么想:
*  在什么情况下父容器的SpecMode为MeasureSpec.AT_MOST?????
*  这个问题不难回答:
*  当父容器的大小为wrap_content和match_parent时系统给父容器的SpecMode为MeasureSpec.AT_MOST.
*  回答了这个问题就可以分情况讨论了.
*
*  情况1:
*  子View大小为match_parent,父容器大小为match_parent且父容器SpecMode为MeasureSpec.AT_MOST
*  这种情况下系统给子View的SpecMode为MeasureSpec.EXACTLY,根本就不是我们想的MeasureSpec.AT_MOST!!
*  所以不会被这里的if()else if() else if()处理到.
*
*
*  仔细想在这又有一个新的疑问:
*  当子View大小为match_parent,父容器大小为match_parent且父容器SpecMode为MeasureSpec.AT_MOST,
*  那为什么系统给子View的SpecMode为MeasureSpec.EXACTLY而不是其他SpecMode???
*
*  因为父容器的父容器应该是match_parent或者精确值不可能是wrap_content,至于为什么可以看情况2.
*  既然父容器的父容器应该是match_parent或者精确值不可能是wrap_content,那我们可以以此类推:
*  不可能出现根View的大小为wrap_content但它的一个子View大小为match_parent.
*
*  那会不会出现这样的情况从根到这个子View的父容器都是wrap_content,而子View的大小为match_parent?
*  这个极端情况也是不会的,可见情况2的分析.
*
*  那会不会出现这样的情况从根到这个子View的父容器都是wrap_content,而子View大小也为wrap_content?
*  这是个正常情况.也就是这里利用的if()else if() else if()来专门处理的子View大小为wrap_content的情况.
*
*
*  情况2:
*  子View大小为match_parent,父容器大小为wrap_content且父容器SpecMode为MeasureSpec.AT_MOST.
*  这根本就是一种不合理甚至错误的情况:
*  子View想和父容器一样大,但父容器的大小又设定为包裹内容大小即wrap_content.这两者就这么耗上了.
*/
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//第一步:调用super.onMeasure()
super.onMeasure(widthMeasureSpec , heightMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpceSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode=MeasureSpec.getMode(heightMeasureSpec);
int heightSpceSize=MeasureSpec.getSize(heightMeasureSpec);

//第二步:处理子View的大小为wrap_content的情况
if(widthSpecMode==MeasureSpec.AT_MOST&&heightSpecMode==MeasureSpec.AT_MOST){
setMeasuredDimension(mWidth, mHeight);
}else if(widthSpecMode==MeasureSpec.AT_MOST){
setMeasuredDimension(mWidth, heightSpceSize);
}else if(heightSpecMode==MeasureSpec.AT_MOST){
setMeasuredDimension(widthSpceSize, mHeight);
}

}


内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: