Android View 绘制流程
int size = Math.max(0, specSize - padding);
int resultSize = 0;int resultMode = 0;
switch (specMode) {// 父容器是精确模式的情况,设置了精确尺寸。case MeasureSpec.EXACTLY:if (childDimension >= 0) {//子元素本身是设置的精确尺寸,就是 EXACTLY 模式,尺寸就是设置的尺寸。result
Size = childDimension;resultMode = MeasureSpec.EXACTLY;} else if (childDimension == LayoutParams.MATCH_PARENT) {// 子元素设置的 match_content 充满入容器,就把尺寸设置为入容器的尺寸,模式设置为 EXACTLYresultSize = size;resultMode = MeasureSpec.EXACTLY;} else if (childDimension == LayoutParams.WRAP_CONTENT) {// 包裹模式下,子元素可以自己设置尺寸,但是不能超过夫容器的尺寸。模式为 AT_MOST,尺寸为父容器的尺寸。resultSize = size;resultMode = MeasureSpec.AT_MOST;}break;
//父容器是最大模式 case MeasureSpec.AT_MOST:if (childDimension >= 0) {// 设置为子元素的尺寸,为精确模式 resultSize = childDimension;resultMode = MeasureSpec.EXACTLY;} else if (childDimension == LayoutParams.MATCH_PARENT) {// 子元素想充满父容器,应该设置为父容器的尺寸,但是父容器是最大模式,没有精确尺寸。// 所以将子元素设置为最大模式,不能超过父容器目前的尺寸。resultSize = size;resultMode = MeasureSpec.AT_MOST;} else if (childDimension == LayoutParams.WRAP_CONTENT) {// 子元素没有精确尺寸,想包裹自身,这种模式下,设置为最大模式,不超过父容器尺寸就好。// bigger than us.resultSize = size;resultMode = MeasureSpec.AT_MOST;}break;
// 父容器没有限制,子元素自己发挥 case MeasureSpec.UNSPECIFIED:if (childDimension >= 0) {//子元素自己有设置的值,就好实用自己的值,设置为精确模式 resultSize = childDimension;resultMode = MeasureSpec.EXACTLY;} else if (childDimension == LayoutParams.MATCH_PARENT) {// 子元素想充满父容器,那就找到父容器的尺寸,但父容器的尺寸未知,还是要自己发挥 UNSPECIFIED。resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;resultMode = MeasureSpec.UNSPECIFIED;} else if (childDimension == LayoutParams.WRAP_CONTENT) {// 只元素是包裹自身,父容器无法给出参考,所以让子元素自己去随意发挥,仍然是 UNSPECIFIEDresultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;resultMode = MeasureSpec.UNSPECIFIED;}break;}//使用打包方法,将子元素的模式和尺寸打包并返回 return MeasureSpec.makeMeasureSpec(resultSize, resultMode);}
measure 流程是在 ViewRoot 的 performMeasure() 里开始的。
在这里会将 DecorView 的 layoutParams 在 window 的 measureSpec 影响下转换为自己的 measureSpec 。 然后调用 DecorView 的 measure() 将宽高的 measureSpec 传入,在 measure() 里,decorView 开始自己的测量。
从 DecorView 的 measure() 开始,整个 View 树的测量流程就开始了。
View 的测量都是在 measure() 里进行的,这是个 final 类型的方法,里面的实现比较简单会有一些判断调整,是否需要测量,会继续调用 onMeasure() 将 measureSpec 传进来,测量尺寸的确定最终是在 onMeasure() 里完成的。
通常我们自定义 View 都要重写这个方法实现自己的测量逻辑,包括我们常用的控件都是自己重写了这个方法实现自己的测量逻辑。
如果不重写 onMeasure(),会导致自定义 view 的 wrap_content 参数无效,具体可以看一下 getDefaultSize() 实现。
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://默认 精确模式和最大模式下都是使用后边的 specSize ,这会导致我们设置的 wrap_content 无效,始终是充满父容器。result = specSize;break;}return result;}
protected int getSuggestedMinimumHeight() {return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}
protected int getSuggestedMinimumWidth() {return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());}
View 和 ViewGroup 的测量过程是不同的。
单纯的 View 只需要在 onMeasure() 里完成自己的测量就可以了,ViewGroup 除了完成自己的测量外,还有子元素的测量。
ViewGroup 的 onMeasure() 是没有任何实现的,因为各个布局的特性不同,具体测量逻辑也是不同的,具体实现都在各个布局里。
但是 ViewGroup 里提供了 measureChildren() 方法,思路就是,遍历所有需要显示的子元素,取出他们的 LayoutParams 参数在自己 measureSpec 的影响下创建出子元素的 measureSpec ,然后将调用子元素的 measure() 将 measureSpec 传递进去。
这里就将测量传递到了子元素。如果子元素是单纯的 View 控件只需要完成自己就可以了,如果是 ViewGroup 会继续将测量递归下去,直至完成整个 View 树的测量。
protected 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];if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {//测量子元素,measureChild 见上面 MeasureSpec 里的代码。measureChild(child, widthMeasureSpec, heightMeasureSpec);}}}
在完成测量流程之后就会进入了 layout 流程了。
layout 布局流程
layout 这一流程会确定 View 的四个顶点位置,进而确定在父容器中的位置和最终宽高。
layout 流程也是在 ViewRoot 里开始,是在 performLayout() 里首先调用 DecorView 的 layout() 方法开始整个 View 树的布局流程。
View 的布局流程都是在 layout() 方法里完成的,会在这里通过 setFrame() 设置自己四个顶点的位置。
设置完自己的位置后,会继续调用 onLayout() 方法,如果是 ViewGroup 可以继续在 onLayout 里确定子元素的位置。
View 的 onLayout() 是没有任何实现的,因为它是没有子元素,ViewGroup 本身也是没有实现的,也都是具体的各个布局里自己实现的。
思路也是遍历所有需要布局的子元素,根据测量尺寸计算出他们的位置后调用子元素的 layout() 方法将位置参数穿进去,让子元素去完成自己的布局流程。
在这里也是将布局流程传递到了子元素,如果子元素是 ViewGroup 会继续将布局流程传递,直到完成整个 View 树的布局流程。
layout() 确定自身的位置
onLayout() 确定子元素的位置
在完成 layout 流程后,就是最后一个 draw 流程了。
draw 绘制流程
这个流程是将 View 绘制到屏幕上。
draw 流程也是在 ViewRoot 里开始的,具体是在 performDraw() 里开始,在这里会调用 DecorView 的 draw() 开始整个 View 树的绘制。
draw 的过程相对来说较为简单,在 draw() 里可以看到整个步骤
绘制背景 drawBackground(canvas);
绘制自己的内容 onDraw(canvas);
绘制子元素 dispatchDraw(canvas);
绘制装饰 onDrawForeground(canvas);
我们自定义 View 都会在 onDraw() 里实现自己的绘制逻辑,View 的 dispatchDraw() 是没有任何实现的,具体实现在 ViewGroup 里。
在 ViewGroup 后调用子元素的 draw() 将绘制流程传递到子元素,直到绘制完整个 View 树。
在完成整个 View 树的绘制后,就可以在屏幕上看见界面了。
相关类 & 概念
在 View 的绘制过程中,涉及到了很多类,这里就不做详细的介绍了,只在这里简单列一下,知道这些个的作用。
DecorView
整个 View 树的根节点,所有的绘制,事件都是从这个 View 开始分发的。
它继承自 FrameLayout 是一个 ViewGroup ,内部含有一个 LinearLayout 。
这个 LinearLayout 里有一个 id 为 content 的 FrameLayout ,我们通常设置的 setContentView() 就是加载到了这个 FrameLayout 里。
评论