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);
	    }
	    
	 }



相关文章
|
开发工具 git
git基于tag创建分支
git基于tag创建分支
|
Android开发
Android 自定义View 测量控件宽高、自定义viewgroup测量
Android 自定义View 测量控件宽高、自定义viewgroup测量
540 0
|
11月前
|
存储 搜索推荐 Java
打造个性化安卓应用:从设计到实现
【10月更文挑战第30天】在数字化时代,拥有一个个性化的安卓应用不仅能够提升用户体验,还能加强品牌识别度。本文将引导您了解如何从零开始设计和实现一个安卓应用,涵盖用户界面设计、功能开发和性能优化等关键环节。我们将以一个简单的记事本应用为例,展示如何通过Android Studio工具和Java语言实现基本功能,同时确保应用流畅运行。无论您是初学者还是希望提升现有技能的开发者,这篇文章都将为您提供宝贵的见解和实用的技巧。
|
存储 设计模式 Java
阿里官方代码规范
这篇文章详细介绍了阿里巴巴官方的代码规范,包括命名规则、常量使用、方法覆写、并发处理、注释规范、数据库设计等多个方面,旨在提高代码的可读性、维护性和扩展性。
|
XML Java 定位技术
【Android App】定位导航GPS中开启手机定位功能讲解及实战(附源码和演示 超详细)
【Android App】定位导航GPS中开启手机定位功能讲解及实战(附源码和演示 超详细)
799 0
|
Android开发 数据格式 XML
Android若干条并排RecyclerView滑动实时联动
Android若干条并排RecyclerView滑动实时联动 以水平方向并排排列的两条RecyclerView为例,实现一个简单的功能:这两个RecyclerView要实时联合滚动,即其中任意一个RecyclerView,将触发其余所有RecyclerView同时滚动相同。
2345 0
|
Java Go Android开发
Kotlin 学习笔记(五)—— 协程的基础知识,面试官的最爱了~(上)
Kotlin 学习笔记(五)—— 协程的基础知识,面试官的最爱了~(上)
170 0
|
JavaScript 前端开发 测试技术
百度JavaScript项目tangram开源 促进国内前端开发水平提升
日前,百度JavaScript开发框架tangram宣布对第三方开发者开源,并推出网站tangram.baidu.com。据悉,这是百度前端第一次对外开放源代码。业内人士认为,此举将推动国内JavaScript技术的研究和交流,为广大前端开发人员提供更符合本土的选择和代码参考,对国内前端开发整体水平的提升带来重大意义。
1328 0
|
Java 安全
Java并发编程 -- AQS入门&实现可重入锁
Java并发编程 -- AQS可能会看的一脸懵逼,今天实战一个项目练手AQS MyAQSLock.java /** * Created by Fant.
1321 0