Android开发——View绘制过程源码解析(一)
来源:互联网 发布:c语言入门经典(第四版) 编辑:程序博客网 时间:2024/06/09 20:35
0. 前言
View的绘制流程从ViewRoot的performTraversals开始,经过measure,layout,draw三个流程,之后就可以在屏幕上看到View了。其中measure用于测量View的宽和高,layout用于确定View在父容器中放置的位置,draw则用于将View绘制到屏幕上。
本文原创,转载请注明出处:SEU_Calvin的CSDN博客。
1. MeasureSpec
说到measure那么就不得不提MeasureSpec,一旦确定了MeasureSpec,在onMeasure()中就可以确定View的宽高。
MeasureSpec的值由SpecSize(测量值)和SpecMode(测量模式)共同组成。它是由布局参数和父容器的测量属性一起决定的。
其中测量模式一共有三种类型:
(1)EXACTLY:表示精确模式,一般当childView设置其宽高为精确值、match_parent(同时父容器也是这种模式)的情况。
(2)AT_MOST:表示最大值模式,一般当childView设置其宽高为wrap_content、match_parent(同时父容器也是这种模式)的情况。
(3)UNSPECIFIED:表示子视图可以想要任何尺寸,一般用于系统内部,开发时很少使用。
2. MeasureSpec的生成过程
2.1 顶级View的MeasureSpec
// desiredWindowWidth和desiredWindowHeight为屏幕尺寸// lp.width和lp.height都等于MATCH_PARENTchildWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width); childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height); //…private int getRootMeasureSpec(int windowSize, int rootDimension) { int measureSpec; switch (rootDimension) { case ViewGroup.LayoutParams.MATCH_PARENT: measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY); break; case ViewGroup.LayoutParams.WRAP_CONTENT: measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST); break; default: measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY); break; } return measureSpec; }
从源码中可以看出,这里使用了MeasureSpec.makeMeasureSpec()方法来组装一个MeasureSpec,rootDimension参数等于MATCH_PARENT,MeasureSpec的SpecMode为EXACTLY。并且MATCH_PARENT和WRAP_CONTENT时的SpecSize都等于windowSize的,也就意味着根视图总是会充满全屏的。
总结一下就是,顶级View的测量属性中,测量大小就是屏幕大小,测量模式就是EXACTLY。
2.2 普通View的MeasureSpec
在对子元素进行measure之前,会先调用getChildMeasureSpec方法得到子元素的测量属性。上面也提到过了,子元素MeasureSpec与父容器的MeasureSpec和子元素本身的LayoutParams有关。
//参1为父容器的MeasureSpecpublic static int getChildMeasureSpec(int spec, int padding, int childDimension) { int specMode = MeasureSpec.getMode(spec); int specSize = MeasureSpec.getSize(spec); int size = Math.max(0, specSize - padding); int resultSize = 0; int resultMode = 0; switch (specMode) { // Parent has imposed an exact size on us case MeasureSpec.EXACTLY: if (childDimension >= 0) { resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size. So be it. resultSize = size; resultMode = MeasureSpec.EXACTLY; } 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 case MeasureSpec.AT_MOST: 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 case MeasureSpec.UNSPECIFIED: if (childDimension >= 0) { // Child wants a specific size... let him have it resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } 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; } return MeasureSpec.makeMeasureSpec(resultSize, resultMode);}
getChildMeasureSpec()方法的源码里的逻辑:
(1)当View为固定宽高时,测量模式是EXACTLY模式,测量值就是布局参数中的大小。
(2)当View为WRAP_CONTENT时,测量模式是AT_MOST模式,测量值是父容器的剩余空间大小。
(3)当View为MATCH_PARENT时,测量值是父容器的剩余空间大小,测量模式分两种情况,如果父容器是EXACTLY模式,那就是EXACTLY模式,如果父容器是AT_MOST模式,那么View也是AT_MOST模式。
3. Measure过程
3.1 普通View的Measure过程
View的measure()方法是final的,因此我们无法在子类中去重写这个方法,在该方法内部会调用onMeasure()方法,源码如下所示。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // setMeasuredDimension设置视图的大小,这样就完成了一次measure的过程 setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }//这个方法就是近似的返回spec中的specSize,除非你的specMode是UNSPECIFIED//UNSPECIFIED 这个一般都是系统内部测量才用的到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方法完成,这个方法以测量属性作为参数,并将结果作为参数传入setMeasuredDimension()方法,完成一次Measure。
在getDefaultSize方法中,不管是测量属性中的模式是AT_MOST还是EXACTLY,都是直接返回测量属性中的测量值。
(1)如果View设置了固定宽高,返回的就是设置的大小,xdp/ydp。
(2)如果设置了MATCH_PARENT,View的测量模式会有两种情况,不过不管是哪一种,返回的都是父容器剩余大小。
(3)但是如果设置了WRAP_CONTENT,也是父容器剩余大小,和包裹内容的效果会失效。
这里就不贴实例了,网上有很多,有兴趣可以查看这一篇。
说了这么多,我们得出的结论就是:继承View的自定义控件,需要重写onMeasure()的同时,要设置WRAP_CONTENT时自身大小。
解决方式就是在onMeasure里针对WRAP_CONTENT属性直接通过setMeasuredDimension()方法为其指定一个默认的宽高。逻辑如下:
@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //默认值 int desiredWidth = 100; int desiredHeight = 100; int widthMode = MeasureSpec.getMode(widthMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); int width; int height; //Measure Width if (widthMode == MeasureSpec.EXACTLY) { //Must be this size width = widthSize; } else if (widthMode == MeasureSpec.AT_MOST) { //Can't be bigger than... width = Math.min(desiredWidth, widthSize); } else { //Be whatever you want width = desiredWidth; } //Measure Height if (heightMode == MeasureSpec.EXACTLY) { //Must be this size height = heightSize; } else if (heightMode == MeasureSpec.AT_MOST) { //Can't be bigger than... height = Math.min(desiredHeight, heightSize); } else { //Be whatever you want height = desiredHeight; } //MUST CALL THIS setMeasuredDimension(width, height);}
【思考】我翻了所有的资料,解决这个问题时都默认AT_MOST就等于WRAP_CONTENT。我们知道顶级容器默认是EXACTLY模式,所以在这篇博客里的例子中上述代码可以解决WRAP_CONTENT失效的问题。
但是如果布局参数写为MATCH_PARENT并且父容器为AT_MOST模式时,得出的子View也是AT_MOST模式,那么上述代码好像是有逻辑漏洞的。想了想,好像确实很难出现这种情况,具体不太清楚,有清楚的朋友可以留言交流一下。
3.2 ViewGroup的Measure过程
ViewGroup是没有onMeasure()方法的,因为不同的ViewGroup子类布局都不一样,所以这个方法是交给子类自己实现的。
ViewGroup中定义了一个measureChildren()方法,其中通过for循环来遍历测量子视图的大小,如下所示:
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(child, widthMeasureSpec, heightMeasureSpec); } } }
遍历的过程中调用measureChild()方法,传入子View参数和父容器的测量属性,先得出子View的测量属性,在把它作为参数传给子View的measure()方法,完成一个子View的测量。其实现为:
protected void measureChild(View child, int parentWidthMeasureSpec,int parentHeightMeasureSpec) { //通过子布局参数和父容器MeasureSpec得到childMeasureSpec //过程略.. //所以这其实是个递归调用,不断的去测量设置子视图的大小,直至完成整个View数测量遍历 child.measure(childWidthMeasureSpec, childHeightMeasureSpec); }
以上就是关于measure过程的一些解析,后面会更新另外layout和draw过程的解析。
本文原创,转载请注明出处:SEU_Calvin的CSDN博客。欢迎留言交流,谢谢。
- Android开发——View绘制过程源码解析(一)
- Android开发——View绘制过程源码解析(二)
- Android开发——View绘制过程源码解析(二)
- 从零开始的自定义View(一)——View和ViewGroup绘制过程源码解析
- Android View绘制过程,基于Framework源码解析
- Android View 绘制过程解析
- Android中View绘制过程(一) decorView绘制
- android高级技术总结图(一)——view的绘制过程
- Android View绘制流程与源码解析
- Android View 源码解析(一)
- Android学习之路--View--绘制过程(一)
- Android View绘制流程完全解析(一)
- Android绘制源码走读,View视图绘制过程解读
- Android开发——View绘制流程
- 从源码角度解析View的绘制过程
- android绘制view的过程(自定义view一)
- android绘制view的过程(自定义view一)
- android绘制view的过程(自定义view一)
- junit问题记录
- 排序算法——归并排序
- 题目1002:Grading
- 线性结构--->循环队列的链式储存实现
- 如何用MAT分析Android程序的内存泄露
- Android开发——View绘制过程源码解析(一)
- JavaScript简介及在HTML文件中的实现
- POJ2282 The Counting Problem:
- HTML5中<script>标签中的defer与async属性详解
- Lights
- Bag的实现
- Android几种数据存储方式的应用场景
- C++中一个由编译器决定结果的程序
- HDU4251 The Famous ICPC Team Again (主席树)