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

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

	 }

时间: 2024-11-01 23:30:52

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

asp.net mvc源码分析-DefaultModelBinder 自定义的普通数据类型的绑定和验证

原文:asp.net mvc源码分析-DefaultModelBinder 自定义的普通数据类型的绑定和验证 在前面的文章中我们曾经涉及到ControllerActionInvoker类GetParameterValue方法中有这么一句代码:    ModelBindingContext bindingContext = new ModelBindingContext() {                 FallbackToEmptyPrefix = (parameterDescriptor

JVM源码分析之自定义类加载器如何拉长YGC

概述 本文重点讲述毕玄大师在其公众号上发的一个GC问题一个jstack/jmap等不能用的case,对于毕大师那篇文章,题目上没有提到GC的那个问题,不过进入到文章里可以看到,既然文章提到了jstack/jmap的问题,这里也简单回答下jstack/jmap无法使用的问题,其实最常见的场景是使用jstack/jmap的用户和目标进程不是同一个用户,哪怕你执行jstack/jmap的动作是root用户也无济于事,不过毕大师这里主要提到的是jmap -heap/histo这两个参数带来的问题,如果使

自定义View系列教程04--Draw源码分析及其实践

探索Android软键盘的疑难杂症 深入探讨Android异步精髓Handler 详解Android主流框架不可或缺的基石 站在源码的肩膀上全解Scroller工作机制 Android多分辨率适配框架(1)- 核心基础 Android多分辨率适配框架(2)- 原理剖析 Android多分辨率适配框架(3)- 使用指南 自定义View系列教程00–推翻自己和过往,重学自定义View 自定义View系列教程01–常用工具介绍 自定义View系列教程02–onMeasure源码详尽分析 自定义View

详解SpringMVC中Controller的方法中参数的工作原理[附带源码分析] good

目录 前言 现象 源码分析 HandlerMethodArgumentResolver与HandlerMethodReturnValueHandler接口介绍 HandlerMethodArgumentResolver与HandlerMethodReturnValueHandler接口的具体应用 常用HandlerMethodArgumentResolver介绍 常用HandlerMethodReturnValueHandler介绍 本文开头现象解释以及解决方案 编写自定义的HandlerMet

Backbone.js 0.9.2 源码分析收藏

Backbone 为复杂Javascript应用程序提供模型(models).集合(collections).视图(views)的结构.其中模型用于绑定键值数据和自定义事件:集合附有可枚举函数的丰富API: 视图可以声明事件处理函数,并通过RESRful JSON接口连接到应用程序. 源码分析转之网上它人的备注,特收藏一下,以免方便阅读. // Backbone.js 0.9.2 // (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc. // Ba

从源码分析Android的Glide库的图片加载流程及特点_Android

0.基础知识Glide中有一部分单词,我不知道用什么中文可以确切的表达出含义,用英文单词可能在行文中更加合适,还有一些词在Glide中有特别的含义,我理解的可能也不深入,这里先记录一下. (1)View: 一般情况下,指Android中的View及其子类控件(包括自定义的),尤其指ImageView.这些控件可在上面绘制Drawable (2)Target: Glide中重要的概念,目标.它即可以指封装了一个View的Target(ViewTarget),也可以不包含View(SimpleTar

[cocos2dx]TestCpp框架源码分析

Cocos2d-x2.0 TestCpp框架源码分析                     [本版教程使用的Cocos2d-x版本为cocos2d-2.0-x-2.0.2]          好的引擎,会提供一系列完整的功能示例,Cocos2d-x之所以能得到很多人的喜爱,其重要的原因是它提供了丰富而易学的示例.在cocos2d-2.0-x-2.0.2中这些示例被放在一个名叫TestCpp的工程中,为了更好的学习Cocos2d-x的功能示例,我们今天来学习一下这个工程的框架结构.      

MeasureSpec的理解和详尽源码分析

package cc.ww; import android.view.View; import android.view.View.MeasureSpec; import android.view.ViewGroup.LayoutParams; import android.view.ViewGroup.MarginLayoutParams; import android.widget.LinearLayout; /** * @author http://blog.csdn.net/lfdfhl

Android透明化和沉浸式状态栏实践及源码分析

本文所提到的透明状态栏其实指的是将顶部的导航栏延伸到状态栏,使之浑然一体(Google官方建议状态栏颜色比导航栏的颜色略深一点),并不代表一定不设置背景色,比如导航栏是白色,则可设置状态栏为白色,视情况而定. 相比于iOS系统,Android系统对于状态栏的设置就显得稍微复杂了一点.Android系统提供了API 19以上对状态栏的设置接口,而直到API 23以上才提供对于icon颜色的设置,还有就是各家厂商(如魅族,小米等)对于状态栏的有自己的定制,对于需要使用浅色背景状态栏的应用,没处理好的