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);
}
- 遍历子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的创建规则表。
- 合并所有子View的尺寸大小
ViewGroup子类根据其不同的布局特性,根据子元素的测量情况最终确定自身的大小,具体实现见具体子类的情况。
- 存储测量后View宽/高
调用setMeasuredDimension()方法存储测量后的View的宽/高。
以上便是View的三大流程的measure过程,关于剩下的layout过程与draw过程,读者可以参照:Android进阶知识(十四):View的工作流程之Layout过程和Draw过程。
参考资料:《Android开发艺术探索》
自定义View Measure过程 - 最易懂的自定义View原理系列(2)