Android进阶知识(十三):View的工作流程之measure过程

Android进阶知识(十三):View的工作流程之measure过程

  View的工作流程主要是指measure、layout、draw这三大流程,即测量、布局和绘制,其中measure确定View的测量宽/高,layout确定View的最终宽/高和四个顶点的位置,而draw则将View绘制在屏幕上
在这里插入图片描述

  measure过程要分情况来看,如果只是一个原始的View,那么通过measure方法就完成了其测量过程,如果是一个ViewGroup,除了完成自己的测量过程之外,还会遍历去调用所有子元素的measure方法,各个子元素再递归去执行这个流程。

View类型 measure过程
View 使用measure(),只测量自身
ViewGroup 对ViewGroup视图中所有的子View都测量(即 遍历调用所有子元素的measure() & 各子元素再递归去执行该流程

一、View的measure过程

  View的measure过程由measure方法来完成,measure方法是一个final类型方法,这意外着子类不能重写此方法,在View的measure方法中会去调用View的onMeasure方法,看下onMeasure方法的源码。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),  
                         getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

  上述几个方法的作用:

方法 具体描述
measure() 基本测量逻辑的判断,调用onMeasure()进行下一步测量。final类型,子类无法重写改方法
onMeasure() 根据View的MeasureSpec计算View的宽/高值,存储测量后的View的宽/高。
setMeasureDimesion() 计算View的宽/高值。
getDefaultSize() 存储测量后的View的宽/高值。

  getDefaultSize()方法的逻辑很简单,对于AT_MOST和EXACTLY两种情况,返回的大小为MeasureSpec中的specSize,而这个specSize也是View的测量大小。至于View在UNSPECIFIED情况下,其测量的宽/高由getSuggestedMinimunWidth和getSuggestedMinimunHeight的返回值决定。根据getSuggestedMinimunWidth的源码逻辑可以知道:如果View没有设置背景,那么返回android:minWidth这个属性所指定的值,可以为0;如果View设置了背景,则返回android:minWidth和背景的最小宽度之间的最大值。
在这里插入图片描述

  View的measure过程流程如图所示。
在这里插入图片描述

  从getDefaultSize方法的实现来看,View的宽/高由specSize决定,那么:直接继承View的自定义控件需要重写onMeasure方法并设置wrap_content时的自身大小,否则在布局中使用wrap_content就相当于match_parent。具体原因参照:Android进阶知识(十二):View的工作原理之基本概念
中普通View的MeasureSpec的创建规则表。

二、ViewGroup的measure过程

  对于ViewGroup来说,除了完成自己的measure过程之外,还会去遍历去调用所有子元素的measure方法,各个子元素再递归去执行这个过程。和View不同的是,ViewGroup是一个抽象类,**由于不同的子类由不同的布局特性,这导致测量细节各不相同,因此没有重写onMeasure方法,**但提供了一个measureChildren方法。具体流程如下。
在这里插入图片描述

  即使ViewGroup子类的布局特性不同,但是其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()))
        measureChildren(widthMeasureSpec, heightMeasureSpec)//TODO 2. 合并所有子View的尺寸大小,最终得到ViewGroup父视图的测量值

        // 3. 存储测量后View宽/高的值:调用setMeasuredDimension()  
        setMeasuredDimension(widthMeasure,  heightMeasure);  
  }
  1. 遍历子View&测量

  ViewGroup中提供了measureChildren()方法,用以遍历并测量子View,具体代码如下。

protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
    final int size = mChildrenCount;
    final View[] children = mChildren;
    // 遍历所有子view
    for (int i = 0; i < size; ++i) {
        final View child = children[i];
        // 调用measureChild()进行下一步的测量
        if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
        }
    }
}

  可以看到ViewGroup在measure时,会对每个子元素进行measure,measureChild()方法的具体实现如下。

protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) {
    // 1. 获取子视图的布局参数
    final LayoutParams lp = child.getLayoutParams();
    // 2. 根据父视图的MeasureSpec & 布局参数LayoutParams,计算单个子View的MeasureSpec
    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(),进行最后的测量
   child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

  measureChild的思想是取出子元素的LayoutParams,然后通过getChildMeasureSpec来创建子元素的MeasureSpec,接着将MeasureSpec直接传递给View的measure方法进行测量。getChildMeasureSpec的工作过程可以参照Android进阶知识(十二):View的工作原理之基本概念
中普通View的MeasureSpec的创建规则表。

  1. 合并所有子View的尺寸大小

  ViewGroup子类根据其不同的布局特性,根据子元素的测量情况最终确定自身的大小,具体实现见具体子类的情况。

  1. 存储测量后View宽/高

  调用setMeasuredDimension()方法存储测量后的View的宽/高。
  以上便是View的三大流程的measure过程,关于剩下的layout过程与draw过程,读者可以参照:Android进阶知识(十四):View的工作流程之Layout过程和Draw过程

参考资料:《Android开发艺术探索》
     自定义View Measure过程 - 最易懂的自定义View原理系列(2)

原创文章 78 获赞 25 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_38196407/article/details/105862216