zoukankan      html  css  js  c++  java
  • 自定义View Measure过程(2)


    目录


    目录

    1. 作用

    测量View的宽/高

    1. 在某些情况下,需要多次测量(measure)才能确定View最终的宽/高;
    2. 在这种情况下measure过程后得到的宽/高可能是不准确的;
    3. 建议在layout过程中onLayout()去获取最终的宽/高

    2. 准备的基础

    在了解measure 过程前,我们需要先了解measure过程中传递尺寸(宽 / 高测量值)的两个类:

    • ViewGroup.LayoutParams (View 自身的布局参数)
    • MeasureSpecs 类(父视图对子视图的测量要求)

    2.1 ViewGroup.LayoutParams

    • 这个类我们很常见,用来指定视图的高度(height)和宽度(width)等布局参数。可通过以下参数进行指定:
    参数解释
    具体值 dp / px
    fill_parent 强制性使子视图的大小扩展至与父视图大小相等(不含 padding )
    match_parent 与fill_parent相同,用于Android 2.3及之后版本
    wrap_content 自适应大小,强制性地使视图扩展以便显示其全部内容(含 padding )
    android:layout_weight="wrap_content"   //自适应大小  
    android:layout_weight="match_parent"   //与父视图等高  
    android:layout_weight="fill_parent"    //与父视图等高  
    android:layout_weight="100dip"         //精确设置高度值为 100dip
    • ViewGroup 的子类有其对应的 ViewGroup.LayoutParams 子类

      1. ViewGroup 的子类包括RelativeLayout、LinearLayout等;
      2. 如 RelativeLayout的 ViewGroup.LayoutParams 的子类是RelativeLayoutParams。
    • 构造函数
      构造函数是View的入口,可以用于初始化一些的内容,和获取自定义属性。

    // View的构造函数有四种重载
        public DIY_View(Context context){
            super(context);
        }
    
        public DIY_View(Context context,AttributeSet attrs){
            super(context, attrs);
        }
    
        public DIY_View(Context context,AttributeSet attrs,int defStyleAttr ){
            super(context, attrs,defStyleAttr);
    
    // 第三个参数:默认Style
    // 默认Style:指在当前Application或Activity所用的Theme中的默认Style
    // 且只有在明确调用的时候才会生效,
        }
    
        public DIY_View(Context context,AttributeSet attrs,int defStyleAttr ,int defStyleRes){
            super(context, attrs,defStyleAttr,defStyleRes);
        }
    
    // 最常用的是1和2
    }

    2.2 MeasureSpec

    2.2.1 定义

    测量规格

    可以理解为:测量View的依据

    2.2.2 类型

    MeasureSpec的类型分为两种:


    测量规格类型

    即每个MeasureSpec代表了一组宽度和高度的测量规格

    2.2.3 作用

    决定了一个View的大小(宽/高)

    即宽测量值(widthMeasureSpec)和高测量值(heightMeasureSpec)决定了View的大小

    2.2.4 组成

    如下图:


    MeasureSpec组成 - 1

    MeasureSpec组成 - 2

    其中,Mode模式共分为三类

    • UNSPECIFIED模式
    • EXACTLY模式
    • AT_MOST模式

    具体说明如下图:


    Mode模式说明

    2.2.5 MeasureSpec类的使用

    • MeasureSpec 、Mode 和Size都封装在View类中的一个内部类里 - MeasureSpec类。
    • MeasureSpec类通过使用二进制,将mode和size打包成一个int值来减少对象内存分配,用一个变量携带两个数据(size,mode),并提供了打包和解包的方法。具体源代码解析如下:
    public class MeasureSpec {  
            //进位大小为2的30次方
            //int的大小为32位,所以进位30位就是要使用int的32和31位做标志位) 
            private static final int MODE_SHIFT = 30;  
    
            // 运算遮罩,0x3为16进制,10进制为3,二进制为11。3向左进位30,就是11 00000000000(11后跟30个0)  
            // 遮罩的作用是用1标注需要的值,0标注不要的值。因为1与任何数做与运算都得任何数,0与任何数做与运算都得0
            private static final int MODE_MASK  = 0x3 << MODE_SHIFT;  
    
            // 0向左进位30 = 00后跟30个0,即00 00000000000
            public static final int UNSPECIFIED = 0 << MODE_SHIFT;  
    
            // 1向左进位30 = 01后跟30个0 ,即01 00000000000
            public static final int EXACTLY     = 1 << MODE_SHIFT;  
    
            // 2向左进位30 = 10后跟30个0,即10 00000000000
            public static final int AT_MOST     = 2 << MODE_SHIFT;  
    
            /* 根据提供的size和mode得到一个详细的测量结果 */  
            public static int makeMeasureSpec(int size, int mode) {  
            // measureSpec = size + mode
            //注:二进制的加法,不是十进制的加法!
                return size + mode;  
            //设计的目的就是使用一个32位的二进制数,32和31位代表了mode的值,后30位代表size的值  
            // 例如size=100(4),mode=AT_MOST,则measureSpec=100+10000...00=10000..00100  
            }  
    
    
            /* 通过详细测量结果获得mode */   
            public static int getMode(int measureSpec) {  
             // mode = measureSpec & MODE_MASK;  
            // MODE_MASK = 11 00000000000(11后跟30个0)
            //原理:用MODE_MASK后30位的0替换掉measureSpec后30位中的1,再保留32和31位的mode值。  
            // 例如10 00..00100 & 11 00..00(11后跟30个0) = 10 00..00(AT_MOST),这样就得到了mode的值
                return (measureSpec & MODE_MASK);  
            }  
    
    
    
            /* 通过详细测量结果获得size */   
            public static int getSize(int measureSpec) {  
             // size = measureSpec & ~MODE_MASK;  
            // 原理同上,不过这次是将MODE_MASK取反,也就是变成了00 111111(00后跟30个1),将32,31替换成0也就是去掉mode,保留后30位的size  
                return (measureSpec & ~MODE_MASK);  
            } 
    
    }  
    
    // 可以通过下面方式获取specMode和SpecSize
    //获取specMode
    int specMode = MeasureSpec.getMode(measureSpec)
    
    //获取SpecSize
    int specSize = MeasureSpec.getSize(measureSpec)
    
    //也可以通过这两个值生成新的SpecMode
    int measureSpec=MeasureSpec.makeMeasureSpec(size, mode);

    2.2.6 MeasureSpec值的确定

    • 上面讲了那么久MeasureSpec,那么,MeasureSpec值到底是如何计算得来的呢?
    • 结论:子View的MeasureSpec值是根据子View的布局参数(LayoutParams)和父容器的MeasureSpec值计算得来的,具体计算逻辑封装在getChildMeasureSpec()里。

    如下图:


    对于普通View

    下面,我们来看getChildMeasureSpec()的源码分析:

    //作用:
    / 根据父视图的MeasureSpec & 布局参数LayoutParams,计算单个子View的MeasureSpec
    //即子view的确切大小由两方面共同决定:父view的MeasureSpec 和 子view的LayoutParams属性 
    
    
    public static int getChildMeasureSpec(int spec, int padding, int childDimension) {  
    
     //参数说明
     * @param spec 父view的详细测量值(MeasureSpec) 
     * @param padding view当前尺寸的的内边距和外边距(padding,margin) 
     * @param childDimension 子视图的布局参数(宽/高)
    
        //父view的测量模式
        int specMode = MeasureSpec.getMode(spec);     
    
        //父view的大小
        int specSize = MeasureSpec.getSize(spec);     
    
        //通过父view计算出的子view = 父大小-边距(父要求的大小,但子view不一定用这个值)   
        int size = Math.max(0, specSize - padding);  
    
        //子view想要的实际大小和模式(需要计算)  
        int resultSize = 0;  
        int resultMode = 0;  
    
        //通过父view的MeasureSpec和子view的LayoutParams确定子view的大小  
    
    
        // 当父view的模式为EXACITY时,父view强加给子view确切的值
       //一般是父view设置为match_parent或者固定值的ViewGroup 
        switch (specMode) {  
        case MeasureSpec.EXACTLY:  
            // 当子view的LayoutParams>0,即有确切的值  
            if (childDimension >= 0) {  
                //子view大小为子自身所赋的值,模式大小为EXACTLY  
                resultSize = childDimension;  
                resultMode = MeasureSpec.EXACTLY;  
    
            // 当子view的LayoutParams为MATCH_PARENT时(-1)  
            } else if (childDimension == LayoutParams.MATCH_PARENT) {  
                //子view大小为父view大小,模式为EXACTLY  
                resultSize = size;  
                resultMode = MeasureSpec.EXACTLY;  
    
            // 当子view的LayoutParams为WRAP_CONTENT时(-2)      
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {  
                //子view决定自己的大小,但最大不能超过父view,模式为AT_MOST  
                resultSize = size;  
                resultMode = MeasureSpec.AT_MOST;  
            }  
            break;  
    
        // 当父view的模式为AT_MOST时,父view强加给子view一个最大的值。(一般是父view设置为wrap_content)  
        case MeasureSpec.AT_MOST:  
            // 道理同上  
            if (childDimension >= 0) {  
                resultSize = childDimension;  
                resultMode = MeasureSpec.EXACTLY;  
            } else if (childDimension == LayoutParams.MATCH_PARENT) {  
                resultSize = size;  
                resultMode = MeasureSpec.AT_MOST;  
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {  
                resultSize = size;  
                resultMode = MeasureSpec.AT_MOST;  
            }  
            break;  
    
        // 当父view的模式为UNSPECIFIED时,父容器不对view有任何限制,要多大给多大
        // 多见于ListView、GridView  
        case MeasureSpec.UNSPECIFIED:  
            if (childDimension >= 0) {  
                // 子view大小为子自身所赋的值  
                resultSize = childDimension;  
                resultMode = MeasureSpec.EXACTLY;  
            } else if (childDimension == LayoutParams.MATCH_PARENT) {  
                // 因为父view为UNSPECIFIED,所以MATCH_PARENT的话子类大小为0  
                resultSize = 0;  
                resultMode = MeasureSpec.UNSPECIFIED;  
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {  
                // 因为父view为UNSPECIFIED,所以WRAP_CONTENT的话子类大小为0  
                resultSize = 0;  
                resultMode = MeasureSpec.UNSPECIFIED;  
            }  
            break;  
        }  
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);  
    }
    • 关于getChildMeasureSpec()里对于子View的测量模式和大小的判断逻辑有点复杂;
    • 别担心,我已经帮大家总结好。具体子View的测量模式和大小请看下表:

    Paste_Image.png

    规律总结:(以子View为标准,横向观察)

    • 当子View采用具体数值(dp / px)时
      无论父容器的测量模式是什么,子View的测量模式都是EXACTLY且大小等于设置的具体数值;
    • 当子View采用match_parent时
      • 子View的测量模式与父容器的测量模式一致
      • 若测量模式为EXACTLY,则子View的大小为父容器的剩余空间;若测量模式为AT_MOST,则子View的大小不超过父容器的剩余空间
    • 当子View采用wrap_parent时
      无论父容器的测量模式是什么,子View的测量模式都是AT_MOST且大小不超过父容器的剩余空间。

    UNSPECIFIED模式:由于适用于系统内部多次measure情况,很少用到,故此处不讨论

    注:区别于顶级View(即DecorView)的计算逻辑


    对于顶级View

    2.3 最基本的知识储备

    具体请看我写的另外一篇文章:自定义View基础 - 最易懂的自定义View原理系列


    3. measure过程详解

    measure过程根据View的类型分为两种情况:

    1. View类型 = 单一View时:只测量自身一个View;
    2. View类型 = ViewGroup时:对ViewGroup视图中所有的子View都进行测量

      即遍历去调用所有子元素的measure方法,然后各子元素再递归去执行这个流程。

    接下来,我将详细分析这两个measure过程。

    3.1 单一View的measure过程

    • 应用场景
      在没有现成的View,需要自己实现的时候,就使用自定义View,一般继承自View,SurfaceView或其他的View,不包含子View。
      1. 如:制作一个支持加载网络图片的ImageView
      2. 特别注意:自定义View在大多数情况下都有替代方案,利用图片或者组合动画来实现,但是使用后者可能会面临内存耗费过大,制作麻烦更诸多问题。
        单一View的measure过程如下图所示:

    单一View的measure过程

    下面我将一个个方法进行详细分析。

    3.1.1 measure()

    • 作用:基本测量逻辑的判断;调用onMeasure()

      属于View.java类 & final类型,即子类不能重写此方法

    • 源码分析如下:

    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    
    //参数说明:View的宽 / 高测量规格
        ...
        int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 :
                mMeasureCache.indexOfKey(key);
        if (cacheIndex < 0 || sIgnoreMeasureCache) {
            // 计算视图大小
            onMeasure(widthMeasureSpec, heightMeasureSpec);
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        } else {
            ...
    
    }

    measure()最终会调用onMeasure()方法。下面继续看onMeasure()的介绍

    3.1.2 onMeasure()

    • 作用:调用getDefaultSize()定义对View尺寸的测量逻辑;调用setMeasuredDimension()存储测量后的View宽 / 高
    • 源码分析如下:
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
    //参数说明:View的宽 / 高测量规格
    
    //setMeasuredDimension()  用于获得View宽/高的测量值
    //这两个参数是通过getDefaultSize()获得的
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),  
               getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));  
    }

    下面继续看setMeasuredDimension()的分析

    3.1.3 setMeasuredDimension()

    • 作用:存储测量后的View宽 / 高。

      该方法就是我们重写onMeasure()所要实现的最终目的

    • 源码分析如下:

    protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {  
    
    //参数说明:测量后子View的宽 / 高值
    
    //将测量后子View的宽 / 高值进行传递
        mMeasuredWidth = measuredWidth;  
        mMeasuredHeight = measuredHeight;  
    
        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;  
    }

    由于setMeasuredDimension(int measuredWidth, int measuredHeight)的参数是从getDefaultSize()获得的,下面我们继续看getDefaultSize()的介绍

    3.1.4 getDefaultSize()

    • 作用:根据View宽/高的测量规格计算View的宽/高值
    • 源码分析如下:
    public static int getDefaultSize(int size, int measureSpec) {  
    
    //参数说明:
    // 第一个参数size:提供的默认大小
    // 第二个参数:宽/高的测量规格(含模式 & 测量大小)
    
        //设置默认大小
        int result = size; 
    
        //获取宽/高测量规格的模式 & 测量大小
        int specMode = MeasureSpec.getMode(measureSpec);  
        int specSize = MeasureSpec.getSize(measureSpec);  
    
        switch (specMode) {  
            // 模式为UNSPECIFIED时,使用提供的默认大小
            // 即第一个参数:size 
            case MeasureSpec.UNSPECIFIED:  
                result = size;  
                break;  
            // 模式为AT_MOST,EXACTLY时,使用View测量后的宽/高值
            // 即measureSpec中的specSize
            case MeasureSpec.AT_MOST:  
            case MeasureSpec.EXACTLY:  
                result = specSize;  
                break;  
        }  
    
     //返回View的宽/高值
        return result;  
    }
    • 上面提到,当模式是UNSPECIFIED时,使用的是提供的默认大小(即第一个参数size)。
      那么,提供的默认大小具体是多少呢?
    • 答:在onMeasure()方法中,getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec)中传入的默认大小是getSuggestedMinimumWidth()

    接下来我们继续看getSuggestedMinimumWidth()的源码分析

    由于getSuggestedMinimumHeight()类似,所以此处仅分析getSuggestedMinimumWidth()

    • 源码分析如下:
    protected int getSuggestedMinimumWidth() {
        return (mBackground == null) ? mMinWidth : max(mMinWidth,mBackground.getMinimumWidth());
    }
    
    //getSuggestedMinimumHeight()同理

    从代码可以看出:

    • 如果View没有设置背景,View的宽度为mMinWidth

      1. mMinWidth = android:minWidth属性所指定的值;
      2. 若android:minWidth没指定,则默认为0
    • 如果View设置了背景,View的宽度为mMinWidth和mBackground.getMinimumWidth()中的最大值

    那么,mBackground.getMinimumWidth()的大小具体是指多少呢?接下来继续看getMinimumWidth()的源码分析:

    public int getMinimumWidth() {
        final int intrinsicWidth = getIntrinsicWidth();
        //返回背景图Drawable的原始宽度
        return intrinsicWidth > 0 ? intrinsicWidth :0 ;
    }

    由源码可知:mBackground.getMinimumWidth()的大小具体是指背景图Drawable的原始宽度。

    1. 若无原始宽度,则为0;
    2. 那么Drawable什么情况下有原始宽度?如:ShapeDrawable没有,但BitmapDrawable有。

    总结:
    对于getDefaultSize()计算View的宽/高值的逻辑如下:


    计算View的宽/高值的逻辑

    至此,单一View的宽/高值已经测量完成,即对于单一View的measure过程已经完成。

    3.1.6 总结

    • 对于单一View的measure过程,如下:

    单一View的measure过程
    • 对于每个方法的总结如下:

    方法总结

    3.2 ViewGroup的measure过程

    • 应用场景
      自定义ViewGroup一般是利用现有的组件根据特定的布局方式来组成新的组件,大多继承自ViewGroup或各种Layout(含有子View)。

      如:底部导航条中的条目,一般都是上图标(ImageView)、下文字(TextView),那么这两个就可以用自定义ViewGroup组合成为一个Veiw,提供两个属性分别用来设置文字和图片,使用起来会更加方便。


      Paste_Image.png
    • 原理
      通过遍历所有的子View进行子View的测量,然后将所有子View的尺寸进行合并,最终得到ViewGroup父视图的测量值。

    Paste_Image.png

    这样自上而下、一层层地传递下去,直到完成整个View树的measure()过程

    • ViewGroup的measure过程
      如下图所示:

      ViewGroup的Measure过程

    下面我将一个个方法进行详细分析。

    3.2.1 measureChildren()

    • 和单一View的measure过程是从measure()开始不同,ViewGroup的measure过程是从measureChildren()开始的。

      1. ViewGroup是一个抽象类,自身没有重写View的onMeasure();
      2. 若需要进行自定义View,则需要对onMeasure()进行重写,下文会提到
    • 作用:遍历子View并调用measureChild()进行下一步测量

    • 源码分析如下:
    protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
    //参数说明:父视图的测量规格(MeasureSpec)
    
            final int size = mChildrenCount;
            final View[] children = mChildren;
    
            //遍历所有的子view
            for (int i = 0; i < size; ++i) {
                final View child = children[i];
            //如果View的状态不是GONE就调用measureChild()去进行下一步的测量
                if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
                    measureChild(child, widthMeasureSpec, heightMeasureSpec);
                }
            }
        }

    下面,我们继续看measureChild()的分析。

    3.2.2 measureChild()

    • 作用:计算单个子View的MeasureSpec;调用子View的measure()进行每个子View最后的宽 / 高测量

    • 源码分析如下:

    protected void measureChild(View child, int parentWidthMeasureSpec,
                int parentHeightMeasureSpec) {
    
            // 获取子视图的布局参数
            final LayoutParams lp = child.getLayoutParams();
    
            // 调用getChildMeasureSpec(),根据父视图的MeasureSpec & 布局参数LayoutParams,计算单个子View的MeasureSpec
             // getChildMeasureSpec()请回看上面的解析
            final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,// 获取 ChildView 的 widthMeasureSpec
                    mPaddingLeft + mPaddingRight, lp.width);
            final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,// 获取 ChildView 的 heightMeasureSpec
                    mPaddingTop + mPaddingBottom, lp.height);
    
            // 将计算好的子View的MeasureSpec值传入measure(),进行最后的测量
            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        }

    下面,我们继续看measure()的分析。

    3.2.3 measure()

    • 作用:基本测量逻辑的判断;调用onMeasure()

      与单一View measure过程中讲的measure()是一致的。

    • 源码分析如下:
    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        ...
        int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 :
                mMeasureCache.indexOfKey(key);
        if (cacheIndex < 0 || sIgnoreMeasureCache) {
    
            // 调用onMeasure()计算视图大小
            onMeasure(widthMeasureSpec, heightMeasureSpec);
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        } else {
            ...
    
    }

    下面,我们继续看onMeasure()的分析。

    3.2.4 onMeasure()

    • 首先明确:ViewGroup是一个抽象类,自身没有重写View的onMeasure();
    • 问:为什么ViewGroup的measure过程不像单一View的measure过程那样对onMeasure()做统一的实现?(如下代码)
    //单一View中的onMeasure统一实现
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
    
    //setMeasuredDimension()  用于获得View宽/高的测量值
    //这两个参数是通过getDefaultSize()获得的
    //下面继续看setMeasuredDimension()  源码
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),  
               getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));  
    }
    • 答:因为不同的ViewGroup子类(LinearLayout、RelativeLayout或自定义ViewGroup子类等)具备不同的布局特性,这导致他们子View的测量方法各有不同;而onMeasure()的作用在于测量View的宽/高值。
      因此,ViewGroup无法对onMeasure()作统一实现。

    在自定义View中,关键在于根据你的自定义View去复写onMeasure()从而实现你的子View测量逻辑。复写onMeasure()的模板如下:

    //根据自身的测量逻辑复写onMeasure()
    
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
    
          //定义存放测量后的View宽/高的变量
          int widthMeasure ;
          int heightMeasure ;
    
    
          //定义测量方法
          void measureCarson{
    
           //定义测量的具体逻辑
    
                    }
    
    //记得!最后使用setMeasuredDimension()  存储测量后View宽/高的值
    setMeasuredDimension(widthMeasure,  heightMeasure);  
    }
    
    //最终setMeasuredDimension()会像上面单一View的measure过程中提到的,存储好测量后View宽/高的值并进行传递。

    上面说的便是单一View的measure过程与ViewGroup过程最大的不同:单一View measure过程的onMeasure()具有统一实现,而ViewGroup则没有。

    注:其实,在单一View measure过程中,getDefaultSize()只是简单的测量了宽高值,在实际使用时有时需要进行更精细的测量。所以有时候也需要重写onMeasure()。

    • 至此,ViewGroup的measure过程已经分析完毕。

    3.2.5 总结

    • 对于ViewGroup的measure过程,如下:


      ViewGroup的measure过程
    • 对于每个方法的总结如下:


    方法总结

    为了让大家更好地理解ViewGroup的measure过程(特别是复写onMeasure()),所以接下来,我将用ViewGroup的子类LinearLayout来分析下ViewGroup的measure过程。

    3.2.6 实例解析(LinearLayout)


    LinearLayout的measure流程

    在上述流程中,前4个方法的实现与上面所说是一样的,这里不作过多阐述,直接进入LinearLayout复写的onMeasure()代码分析:

    // 详细分析请看代码注释
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    
        //根据不同的布局属性进行不同的计算
        if (mOrientation == VERTICAL) {
            measureVertical(widthMeasureSpec, heightMeasureSpec);
        } else {
            measureHorizontal(widthMeasureSpec, heightMeasureSpec);
        }
    }
    
        // 此处只选垂直方向的测量过程,即measureVertical()
        // 该方法代码非常多,此处仅分析重要的逻辑
    
    void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
    
        // 获取垂直方向上的子View个数
        final int count = getVirtualChildCount();
    
        // 遍历子View获取其高度,并记录下子View中最高的高度数值
        for (int i = 0; i < count; ++i) {
            final View child = getVirtualChildAt(i);
    
            // 子View不可见,直接跳过该View的measure过程,getChildrenSkipCount()返回值恒为0
            // 注:若view的可见属性设置为VIEW.INVISIBLE,还是会计算该view大小
            if (child.getVisibility() == View.GONE) {
               i += getChildrenSkipCount(child, i);
               continue;
            }
    
            // 记录子View是否有weight属性设置,用于后面判断是否需要二次measure
            totalWeight += lp.weight;
    
            if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {
                // 如果LinearLayout的specMode为EXACTLY且子View设置了weight属性,在这里会跳过子View的measure过程
                // 同时标记skippedMeasure属性为true,后面会根据该属性决定是否进行第二次measure
              // 若LinearLayout的子View设置了weight,会进行两次measure计算,比较耗时
                // 这就是为什么LinearLayout的子View需要使用weight属性时候,最好替换成RelativeLayout布局
    
                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
                skippedMeasure = true;
            } else {
                int oldHeight = Integer.MIN_VALUE;
    
                // 在该方法内部,最终会调用到子View的measure方法,计算出子View的大小
               //  即遍历子View并调用measure(),形成递归
                measureChildBeforeLayout(
                       child, i, widthMeasureSpec, 0, heightMeasureSpec,
                       totalWeight == 0 ? mTotalLength : 0);
    
                if (oldHeight != Integer.MIN_VALUE) {
                   lp.height = oldHeight;
                }
    
    
                final int childHeight = child.getMeasuredHeight();
                // mTotalLength用于存储LinearLayout在竖直方向的高度
                final int totalLength = mTotalLength;
                //每测量一个子View的高度, mTotalLength就会增加
                mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
                       lp.bottomMargin + getNextLocationOffset(child));
        }
    
        // 记录LinearLayout占用的总高度
        // 即除了子View的高度,还有本身的padding属性值
        mTotalLength += mPaddingTop + mPaddingBottom;
        int heightSize = mTotalLength;
    
    
    // 最终调用setMeasuredDimension()  设置测量后View宽/高的值
    setMeasureDimension(resolveSizeAndState(maxWidth,width))
    
    
      ...
    }
  • 相关阅读:
    Codeforces 787D. Legacy 线段树优化建图+最短路
    Codeforces 1051E. Vasya and Big Integers
    BZOJ3261 最大异或和
    BZOJ3531 SDOI2014 旅行
    洛谷P2468 SDOI 2010 粟粟的书架
    2018 ICPC 焦作网络赛 E.Jiu Yuan Wants to Eat
    HDU6280 From Tree to Graph
    HDU5985 Lucky Coins 概率dp
    (HDU)1334 -- Perfect Cubes (完美立方)
    (HDU)1330 -- Deck (覆盖物)
  • 原文地址:https://www.cnblogs.com/xinmengwuheng/p/7070081.html
Copyright © 2011-2022 走看看