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

简介: /** * * 文档描述: * (1)onMeasure()源码分析 * (2)自定义View时重写onMeasure()实现对于wrap_content的支持 * * 原文地址: * http://blog.
	/**
	 * 
	 * 文档描述:
	 * (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);
	    }
	    
	 }



相关文章
|
4月前
|
消息中间件 前端开发 Android开发
Android面试题自定义View之Window、ViewRootImpl和View的三大流程
Android开发中,View的三大核心流程包括measure(测量)、layout(布局)和draw(绘制)。MeasureSpec类在测量过程中起到关键作用,它结合尺寸大小和模式(EXACTLY、AT_MOST、UNSPECIFIED)来指定View应如何测量。onMeasure方法用于自定义View的测量,布局阶段,ViewGroup调用onLayout确定子元素位置,而draw阶段按照特定顺序绘制背景、内容、子元素和装饰。整个流程始于ViewRootImpl的performTraversals,该方法触发测量、布局和绘制。
107 0
|
6月前
|
Android开发 容器
[Android]View的事件分发机制(源码解析)
[Android]View的事件分发机制(源码解析)
59 0
|
存储 Android开发
解读ImageView的wrap_content和adjustViewBounds的工作原理
ImageView是android开发过程中经常会使用的一种组件,由于android屏幕碎片化的问题,有时候我们无法设定一个具体的宽高。比如说width是match_parent的,这时候我们还想让图片在宽度完全填充并能正常显示,我们直接会想到将height设置为wrap_content。但是用过的同学都知道ImageView的实际区域要大于图片区域
339 0
|
前端开发 Android开发 图形学
Android自定义View工具:Paint&Canvas(一)
本文主要讲的是自定义View时我们经常用到的Canvas和Paint,像平时画画一样,我们需要画布和画笔,而Canvas就是画布,Paint就是画笔
|
前端开发 Android开发 Python
|
Android开发
Android 自定义View 之 RectF用法详解
Android 自定义View 之 RectF用法详解
669 0
Android 自定义View 之 RectF用法详解
|
XML Android开发 数据格式
Android控件之ScrollView探究
ScrollView滚动视图是指当拥有很多内容,屏幕显示不完时,需要通过滚动跳来显示的视图。 ScrollView只支持垂直滚动。
239 0