Android的View体系(五):Measure过程

measure过程 根据View的类型分为2种情况:单一Viewmeasure过程(只测量自身一个view)和ViewGroupmeasure过程(对ViewGroup视图中所有的子view都进行测量,即遍历调用所有view的measure())。

一. 单一View的measure过程

  • 具体的流程如下:
    在这里插入图片描述
    下面我将一个个方法进行详细分析:入口 = measure()
/**
  * 源码分析:measure()
  * 定义:Measure过程的入口;属于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);
            // 计算视图大小 ->>分析1

        } else {
            ...
      
    }
/**
  * 分析1:onMeasure()
  * 作用:a. 根据View宽/高的测量规格计算View的宽/高值:getDefaultSize()
  *      b. 存储测量后的View宽 / 高:setMeasuredDimension()
  **/ 
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
    // 参数说明:View的宽 / 高测量规格

    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),  
                         getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));  
    // setMeasuredDimension() :获得View宽/高的测量值 ->>分析2
    // 传入的参数通过getDefaultSize()获得 ->>分析3
}
/**
  * 分析2:setMeasuredDimension()
  * 作用:存储测量后的View宽 / 高
  * 注:该方法即为我们重写onMeasure()所要实现的最终目的
  **/
    protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {  

    //参数说明:测量后子View的宽 / 高值

        // 将测量后子View的宽 / 高值进行传递
            mMeasuredWidth = measuredWidth;  
            mMeasuredHeight = measuredHeight;  
          
            mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;  
        } 
    // 由于setMeasuredDimension()的参数是从getDefaultSize()获得的
    // 下面我们继续看getDefaultSize()的介绍
/**
  * 分析3:getDefaultSize()
  * 作用:根据View宽/高的测量规格计算View的宽/高值
  **/
  public static int getDefaultSize(int size, int measureSpec) {  

        // 参数说明:
        // size:提供的默认大小
        // measureSpec:宽/高的测量规格(含模式 & 测量大小)

            // 设置默认大小
            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中的Size
                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

mMinWidth· = android:minWidth属性所指定的值;
2.若android:minWidth没指定,则默认为0

  • View设置了背景,View的宽度为mMinWidthmBackground.getMinimumWidth()中的最大值

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

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

// 由源码可知:mBackground.getMinimumWidth()的大小 = 背景图Drawable的原始宽度
// 若无原始宽度,则为0;
// 注:BitmapDrawable有原始宽度,而ShapeDrawable没有

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

在这里插入图片描述至此,单一View的宽/高值已经测量完成,即对于单一Viewmeasure过程已经完成。

总结

对于单一View的measure过程,如下:
在这里插入图片描述

实际作用的方法:getDefaultSize() = 计算View的宽/高值、setMeasuredDimension() =
存储测量后的View宽 / 高

二. ViewGroup的measure过程原理

  • 原理
    1.遍历测量所有子View的尺寸
    2.合并将所有子View的尺寸进行,最终得到ViewGroup父视图的测量值

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

  • 流程
    在这里插入图片描述
    下面我将一个个方法进行详细分析:入口 = measure()
/**
  * 源码分析: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 {
        ...
}

/**
  * 分析1:onMeasure()
  * 作用:遍历子View & 测量
  * 注:ViewGroup = 一个抽象类 = 无重写View的onMeasure(),需自身复写
  **/ 

为什么ViewGroupmeasure过程不像单一Viewmeasure过程那样对onMeasure()做统一的实现?(如下代码)

/**
 * 分析:子View的onMeasure()
 * 作用:a. 根据View宽/高的测量规格计算View的宽/高值:getDefaultSize()
 *      b. 存储测量后的View宽 / 高:setMeasuredDimension()
 * 注:与单一View measure过程中讲的onMeasure()一致
  **/ 
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
    // 参数说明:View的宽 / 高测量规格

    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),  
                         getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));  
    // setMeasuredDimension() :获得View宽/高的测量值 
    // 传入的参数通过getDefaultSize()获得
}
  • 答:因为不同的ViewGroup子类(LinearLayout、RelativeLayout / 自定义ViewGroup子类等)具备不同的布局特性,这导致他们子View的测量方法各有不同

onMeasure()的作用 = 测量View的宽/高值

因此,ViewGroup无法对onMeasure()作统一实现。这个也是单一Viewmeasure过程与ViewGroup过程最大的不同。

  1. 即 单一View measure过程的onMeasure()具有统一实现,而ViewGroup则没有
  2. 注:其实,在单一View measure过程中,getDefaultSize()只是简单的测量了宽高值,在实际使用时有时需更精细的测量。所以有时候也需重写onMeasure()

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

/**
  * 根据自身的测量逻辑复写onMeasure(),分为3步
  * 1. 遍历所有子View & 测量:measureChildren()
  * 2. 合并所有子View的尺寸大小,最终得到ViewGroup父视图的测量值(自身实现)
  * 3. 存储测量后View宽/高的值:调用setMeasuredDimension()  
  **/ 

  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  

        // 定义存放测量后的View宽/高的变量
        int widthMeasure ;
        int heightMeasure ;

        // 1. 遍历所有子View & 测量(measureChildren())
        // ->> 分析1
        measureChildren(widthMeasureSpec, heightMeasureSpec)// 2. 合并所有子View的尺寸大小,最终得到ViewGroup父视图的测量值
         void measureCarson{
             ... // 自身实现
         }

        // 3. 存储测量后View宽/高的值:调用setMeasuredDimension()  
        // 类似单一View的过程,此处不作过多描述
        setMeasuredDimension(widthMeasure,  heightMeasure);  
  }
  // 从上可看出:
  // 复写onMeasure()有三步,其中2步直接调用系统方法
  // 需自身实现的功能实际仅为步骤2:合并所有子View的尺寸大小

/**
  * 分析1:measureChildren()
  * 作用:遍历子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];
                     // 调用measureChild()进行下一步的测量 ->>分析1
                    if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
                        measureChild(child, widthMeasureSpec, heightMeasureSpec);
                    }
                }
            }

/**
  * 分析2:measureChild()
  * 作用:a. 计算单个子View的MeasureSpec
  *      b. 测量每个子View最后的宽 / 高:调用子View的measure()
  **/ 
  protected void measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {

        // 1. 获取子视图的布局参数
        final LayoutParams lp = child.getLayoutParams();

        // 2. 根据父视图的MeasureSpec & 布局参数LayoutParams,计算单个子View的MeasureSpec
        // getChildMeasureSpec() 请看上面第2节储备知识处
        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,// 获取 ChildView 的 widthMeasureSpec
                mPaddingLeft + mPaddingRight, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,// 获取 ChildView 的 heightMeasureSpec
                mPaddingTop + mPaddingBottom, lp.height);

        // 3. 将计算好的子View的MeasureSpec值传入measure(),进行最后的测量
        // 下面的流程即类似单一View的过程,此处不作过多描述
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }
    // 回到调用原处

至此,ViewGroupmeasure过程分析完毕。

总结

ViewGroup的measure过程如下:
在这里插入图片描述

发布了82 篇原创文章 · 获赞 38 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/gaolh89/article/details/104077473