在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
三个步骤,总体来说还是比较复杂的,本文只是简要概括,更详细的分析见文末参考。