/** * * 文档描述: * (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); } }