android
view
绘制流程
前言
今天复习了view的绘制流程,看了几篇博客,百度搜索的前几篇写的大志差不多,沿着源码比,有点读不下去,然后又搜到了这篇全面升级Android面试之View的绘制流程,看到这篇基本就把这个流程弄明白了。但是只是停留在基本明白,如果回过头来再想想,感觉还是很难组织起来,这就需要做个笔记和实践来加深印象了,所以写博客自己屡一下思路。那么我就通过自己的回忆写一写简单的流程。
读完一遍脑中的流程
- 首先映入脑海的应该是那张经典结构图,在从外到里结构是Window、Activity、DecorView、Actionbar、ContentView,ViewGroup、View。
- 然后是在哪启动绘制的呢?RootViewImpl里的performTraversals方法(),方法中调用view.measure(),layout(),draw()方法。
- measure方法中调用的是onMeasure方法。measure是不能重载的,onMeasure可以重载。onMeasure方法里调用setMeasuredDimension(),这个setMeasuredDimension可以自己定义尺寸,但是不推荐这样做,可能会影响子view的measure以及onlayout等。
- 上边是view里调用measure,ViewGroup调用measureChildren方法来测量它的子view。调用过程是measureChildren调用measureChild,measureChild里边先调用getChildMeasureSpec,然后获取child的宽高,再调用child的measure。将宽高传给child的measure,child的measure再调用onMeasure。这里边重点是getChildMeasureSpec的判断。
- 说起判断就要说下MeasureSpec,它一共32位,前2位(高2位)是表示Mode,后30位(低30位)表示size。getChildMeasureSpec方法里的判断主要就是判断父view的MeasureSpec的Mode。
- 那我们就主要看下getChildMreasureSpec()方法,代码如下
getChildMreasureSpec方法源码
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
//首先将父view的Measure分解Mode和Size
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
//定义size取0和specSize-padding的最大值
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
//判断父view的Mode为哪种类型
switch (specMode) {
//假如父view的Mode类型为EXACYLY(说明父view的大小是确切的数值,不管你子view多大不影响我)
case MeasureSpec.EXACTLY:
//如果child的params里width/height的值大于0,则返回的size结果就是子view的width/height值大小,
//Mode为EXACTLY
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
//如果child的params里width/height的值是MATCH_PARENT,则返回的size大小就是0和specSize-padding的最大值
//怎么理解上边这句话呢?因为父view是EXACTLY大小不变的,所以specSize是个固定值,然后子view是
//MATCH_PARENT,则给你子view的实际尺寸就是我父view的尺寸减去你设置的padding值。
//MODE为EXACTLY
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
//如果child的params里width/height的值是WRAP_CONTENT,则返回的size大小就是0和specSize-padding的最大值
//Mode为AT_MOST,这种情况就是子view自己宽高不确定,所以父view说,你别超过specSize-padding的尺寸就行了。
//我们注意到size是Math.max(0, specSize - padding),这里就有一个问题,你的子view会填充父容器。
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent has imposed a maximum size on us
//假如父view的Mode类型为AT_MOST,说明父view的大小现在是不确定的,最大值为specSize
case MeasureSpec.AT_MOST:
//如果child的params里width/height的值大于0,则返回的size结果就是子view的width/height值大小,
//Mode为EXACTLY
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent asked to see how big we want to be
//就是父view的size未被指定,子view随便吧,
case MeasureSpec.UNSPECIFIED:
//如果子view给出了确切的宽高,那子view的mode保证是EXACTLY的。
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
//子View来说无论是WRAP_CONTENT还是MATCH_PARENT,子View也是没有任何束缚的,想多大就多大,
//没有不能超过多少的要求,一旦没有任何要求和约束,size的值就没有任何意义了,所以一般都直接设置成0
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
//同上
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
因为调用getChildMeasureSpec方法是在measureChild方法里,代用代码如下:
/**
* Ask one of the children of this view to measure itself, taking into
* account both the MeasureSpec requirements for this view and its padding
* and margins. The child must have MarginLayoutParams The heavy lifting is
* done in getChildMeasureSpec.
*
* @param child The child to measure
* @param parentWidthMeasureSpec The width requirements for this view
* @param widthUsed Extra space that has been used up by the parent
* horizontally (possibly by other children of the parent)
* @param parentHeightMeasureSpec The height requirements for this view
* @param heightUsed Extra space that has been used up by the parent
* vertically (possibly by other children of the parent)
*/
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
我们看出getChildMeasureSpec方法参数spec就是child父view的MeasureSpec,padding就是child的左右或者上下padding,childDimension就是child.getLayoutParams(),LayoutParams返回的宽高只会有三种值,一个确定的值,MATCH_PARENT或WRAP_CONTENT。
知道这些就开始分析getChildMeasureSpec这个方法,根据自己的理解已经把注释写在源码里了,
其实概况起来就是,看child的LayoutParams是三种中的哪种,假如是确定的值,那么child的MeasureSpec就是确定的值+EXACTLY,如果child是MATCH_PARENT或WRAP_CONTENT,则需要看父view的MODE,如果父view的MODE是AT_MOST,说明子view也都是AT_MOST,size就是父view要求的最大size,如果父view的MODE是UNDEFINED,由于这种不确定性,子view也无法确定宽高,只能设为0;
接下来就是调用child的measure方法,然后measure调用onMeasure方法。基本测量就结束了。
关于layout和draw的理解,layout是不能被重载的,一般都是重写onLayout和onDraw方法,自定义ViewGroup一般重写onLayout方法,而view是没有子view的所以不需要重写onLayout方法,onDraw方法一般自定义ViewGroup和自定义View都会重写,ViewGroup一般重写onDraw来画子view间的分割线,View重写onDraw除了分隔线还会绘制其他。
上边的流程我是自己脑海想的,然后用as辅助查看了一下源码。写出来的。
思考
经过自己脑海中屡了一遍,然后再回过头来看网上文章做的分析。就弥补了许多残留的疑问。
总体思路还需要再揣摩。
好办法就是分析系统的自定义控件如Button,LinearLayout等控件,看系统是怎么写的。这样能更加体会view绘制流程在自定义控件里的应用。加油吧。
参考
Android View的绘制流程
Android应用层View绘制流程与源码分析
全面升级Android面试之View的绘制流程
Android View面试难点解析