在Android开发中,View
扮演了很重要的角色。在官方的API中,对View的描述是这样的:布局
- 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
绘制出来。spa
为了明白View
的绘制原理,首先要知道MeasureSpec
的概念。MeasureSpec
至关因而View
的测量说明,其中封装了测量模式(SpecMode)和测量规格(SpecSize)。看一下MeasureSpec
的代码:.net
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。code
测量模式(SpecMode)有三种类型:orm
父容器已经检测出View所须要的精确大小,这个时候View的最终大小就是SpecSize所指定的值。它对应于LayoutParams中的match_parent和具体的数值这两种模式。blog
父容器指定了一个可用大小即SpecSize,View的大小不能大于这个值,具体是什么值要看不一样View的具体实现。它对应于LayoutParams中的wrap_content。递归
一个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
绘制到屏幕上,有以下几步:
View
中有一个特殊的方法setWillNotDraw
,源码以下:
public void setWillNotDraw(boolean willNotDraw) { setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK); }
这个方法的意义是,若是一个View
不须要绘制任何内容,设置这个标记位为true
后,系统会进行相应的优化。默认状况下,View
没有启用这个标记位,可是ViewGroup
默认启用。
View
的绘制要通过measure
、layout
以及draw
三个步骤,整体来讲仍是比较复杂的,本文只是简要归纳,更详细的分析见文末参考。
参考