这篇博客主要从源代码的角度和大家深入探讨一下Android下View的绘制流程中的Measure过程,
Android下View绘制流程整体上分为三个步骤,Measure(测量),Layout(布局),Draw(绘制),具体来说,当Application启动之后,首先启动一个默认的Activity,接着,在对这个Activity进行绘制时,会从根视图(ViewRoot)的performTraversals()方法开始,从上到下遍历整个视图树,每个View 负责绘制自己,而ViewGroup还要负责通知自己的子View进行绘制操作,performTraversals()方法核心代码如下:
private void performTravelsals(){
'''
int childWidthMeasureSpec = getRootMeasureSpec(mWidth,lp.width);
int childWidthMeasureSpec = getRootMeasureSpec(mWidth,lp.width);
...
//进行测量
performMeasure(childWidthMeasureSpec,childHeightMeasureSpec);
...
//进行布局
performLayout(lp,desireWindowWidth,desireWindowHeight);
...
//进行绘制
performDraw();
...
}
下面具体来看
- 测量,从上面performTravelsals()可以看出,测量方法第一步是去调用performMeasure()方法,这个方法的代码如下
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec){
...
mView.measure(childWidthMeasureSpec,childHeightMeasureSpec);
...
}
具体的测量时分发给ViewGroup(这里的mView就是一个ViewGroup)进行的,这里ViewGroup先调用measure方法对其自身进行测量,接着会调用measureChildren()方法对其子View进行遍历并对每个子View调用measure()方法实现子View的测量,以下为measureChildren()方法的核心代码:
private void measureChildren(int widthMeasureSpec, int heightMeasureSpec){
final int size = mChildrenCount;
final View[] children = mChildren;
for(int i = 0; i < size; ++i){
final View child = children[i];
//当View的可见性处于GONE状态时,不对其进行测量
if((child.mViewFlags & VISIBILITY_MASK) != GONE){
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
接下来是measureChild()方法的核心代码:
private void measureChild(View child, int parentWidthMeasureSpec, int parentHeigthMeasureSpec){
final LayoutParams lp = child.getLayoutParams();
//根据父容器的MeasureSpec(即传过来的第二个和第三个参数)和子View 的LayoutParams 等信息计算子 View的MeasureSpec
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
measureChild()方法通过ViewGroup的宽高和View自身的Padding属性获取到View实际大小,接下来调用View的measure()方法,下面是measure()方法的核心代码:
public final void measure(int widthMeasureSpec, int heightMeasureSpec){
...
onMeasure(widthMeasureSpec, heightMeasureSpec);
...
}
可以看到,其实measure方法执行了对onMeasure()方法的调用,下面我们来看onMeasure()方法代码:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
//setMeasureDimension方法用于设置View的宽高
setMeasureDimension(getDefaultSize(getSuggestedMinimunWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimunHeight(), heightMeasureSpec));
}
由于measure方法是final修饰的,无法进行修改,所以在实际开发中,需要自定义控件时,我们通常会重写onMeasure()方法,如果不重写onMeasure()方法,onMeasure()方法通过调用getDefaultSize()方法获取到View最终宽高,下面我们看下getDefaultSize()方法的核心代码:
public static int getDefaultSize(int size, int measureSpec){
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measuerSpec);
switch (specMode){
case MeasureSpec.UNSOECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
这里涉及到对测量模式的选择,简单介绍一下,测量模式分为三种:
UNSPECIFIED:不指定测量模式,父视图没有限制子视图的大小,子视图可以是想要的任何尺寸,通常用于系统内部,应用开发中很少使用到。
EXACTLY:精确测量模式,当该View的layout_width和layout_height为具体数值或者为match_parent时生效,表示父视图已经决定子视图的精确大小,这种情况下View的测量值就是SpecSize的值。
AT_MOST:最大值模式,当该View的layout_width和layout_height为wrap_content时生效,此时子视图的尺寸可以是不超过父视图允许的最大尺寸的任意尺寸。
最后我们通过对整个Measure过程中参数传递流程来整体了解一下View的Measure过程
Activity启动----ViewRoot-----perTravelsals(A参数)----performMeasure(A参数)----ViewGroup----(对子View)measureChildren(A参数)----measureChild(B参数)----measure(B参数)----onMeasure(B参数)
其中A参数表示初始参数,B参数表示在A参数的基础上通过与Padding属性算出的参数;