在Android开发中, View 扮演了很重要的角色。在官方的API中,对View的描述是这样的:
A View occupies a rectangular area on the screen and is responsible for drawing and event handling
意思是 View 在屏幕上占用一块矩形区域,并且负责绘制和事件处理。 View 的绘制和事件处理是两个重要的主题。本文简单分析一下 View 的绘制流程。
View 的绘制流程是从 ViewRootImpl 的 performTraversals 方法开始,它经过 measure 、 layout 和 draw 三个过程才能最终将一个 View 绘制出来。
为了明白 View 的绘制原理,首先要知道 MeasureSpec 的概念。 MeasureSpec 相当于是 View 的测量说明,其中封装了测量模式(SpecMode)和测量规格(SpecSize)。看一下 MeasureSpec 的代码:
private static final int MODE_SHIFT = 30; private static final int MODE_MASK = 0x3 << MODE_SHIFT; public static final int UNSPECIFIED = 0 << MODE_SHIFT; public static final int EXACTLY = 1 << MODE_SHIFT; public static final int AT_MOST = 2 << MODE_SHIFT; public static int makeMeasureSpec(int size, int mode) { if (sUseBrokenMakeMeasureSpec) { return size + mode; } else { return (size & ~MODE_MASK) | (mode & MODE_MASK); } } public static int getMode(int measureSpec) { return (measureSpec & MODE_MASK); } public static int getSize(int measureSpec) { return (measureSpec & ~MODE_MASK); } MeasureSpec 将SpecMode和SpecSize封装成一个 int 值,通过 getMode 方法可以提取出SpecMode, getSize 方法可以提取出SpecSize。
测量模式(SpecMode)有三种类型:
EXACTLY父容器已经检测出View所需要的精确大小,这个时候View的最终大小就是SpecSize所指定的值。它对应于LayoutParams中的match_parent和具体的数值这两种模式。
AT_MOST父容器指定了一个可用大小即SpecSize,View的大小不能大于这个值,具体是什么值要看不同View的具体实现。它对应于LayoutParams中的wrap_content。
UNSPECIFIED父容器不对View有任何限制,要多大给多大,这种情况一般用于系统内部,表示一种测量状态。
一个 View 的 MeasureSpec 由父布局 MeasureSpec 和自身的 LayoutParams 共同产生。父布局的 MeasureSpec 从何而来?从父布局的父布局而来。最顶层的布局是 DecorView ,常用的 setContent(view) 便是设置 DecorView 。 DecorView 的 MeasureSpec 是通过 ViewRootImpl 中的 getRootMeasureSpec 方法得到的。
下面主要分析普通 View 的 MeasureSpec 的产生,看一下 ViewGroup 的 measureChild 方法:
protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) { final LayoutParams lp = child.getLayoutParams(); final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight, lp.width); final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom, lp.height); child.measure(childWidthMeasureSpec, childHeightMeasureSpec); } 从上面的代码可以看出调用 getChildMeasureSpec 方法得到子 View 的 MeasureSpec ,传进去的参数是父容器的 MeasureSpec 和子 View 的 LayoutParams ,由此可见 MeasurecSpec 是由父容器和 View 本身共同决定的。 getChildMeasureSpec 方法获取子 View 的 MeasureSpec 的逻辑如下图所示:
其中 UNSPECIFID 不需要考虑。
注意当子 View 的 LayoutParams 为 wrap_content 时,最终的SpecMode都是 AT_MOST ,SpecSize为父容器剩余空间大小。
得到子 View 的 MeasureSpec 后,调用子 View 的 measure 方法,传入相应的参数,开始下一层的 measure 过程。
View 的 measure 的过程由其 measure 方法来完成,这是一个 final 类型的方法,这意味着子类不能重写此方法,在 View 的方法中去调用 View 的 onMeasure 方法,它的实现如下:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); } public static int getDefaultSize(int size, int measureSpec) { int result = size; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); switch (specMode) { case MeasureSpec.UNSPECIFIED: result = size; break; case MeasureSpec.AT_MOST: case MeasureSpec.EXACTLY: result = specSize; break; } return result; } 可以看出,最终是调用 getDefaultSize 方法得到实际的测量值。上一节提到,当子 View 的 LayoutParams 为 wrap_content 时,最终的SpecMode都是 AT_MOST ,SpecSize为父容器剩余空间大小。在 getDefaultSize 方法中,对于 AT_MOST 和 EXACTLY 均是直接使用父容器传进来的值,这可能不是我们想要的值,所以自定义 View 时要重写 onMeasure 方法处理 AT_MOST ,否则使用 wrap_content 相当于使用 match_parent 。
对于 ViewGroup ,测量完自己还要调用子 View 的 measure 方法,各个子元素再递归去执行这个过程。
Layout的作用是 ViewGroup 用来确定子元素的位置,当 ViewGroup 的位置被确定以后,它在 onLayout 中会遍历所有的子元素并调用其 layout 方法,在 layout 方法中 onLayout 方法又会被调用。 onlayout 方法是抽象方法,所以自定义 ViewGroup 时需要实现这个方法确定子元素的布局。常用的 LinearLayout 以及 RelativeLayout 方法均重写了这个方法。
Draw过程就是将 View 绘制到屏幕上,有如下几步:
绘制背景
绘制自己
绘制children
绘制装饰
View 中有一个特殊的方法 setWillNotDraw ,源码如下:
public void setWillNotDraw(boolean willNotDraw) { setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK); } 这个方法的意义是,如果一个 View 不需要绘制任何内容,设置这个标记位为 true 后,系统会进行相应的优化。默认情况下, View 没有启用这个标记位,但是 ViewGroup 默认启用。
View 的绘制要经过 measure 、 layout 以及 draw 三个步骤,总体来说还是比较复杂的,本文只是简要概括,更详细的分析见文末参考。