博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android开发——View绘制过程源码解析(一)
阅读量:4928 次
发布时间:2019-06-11

本文共 8831 字,大约阅读时间需要 29 分钟。

0. 前言  

View的绘制流程从ViewRootperformTraversals开始,经过measurelayoutdraw三个流程,之后就可以在屏幕上看到View了。其中measure用于测量View的宽和高layout用于确定View在父容器中放置的位置draw则用于将View绘制到屏幕上

本文原创,转载请注明出处:。

1. MeasureSpec

说到measure那么就不得不提MeasureSpec,它是由ViewLayoutParams和父容器(顶级View则为屏幕尺寸)一起决定的,一旦确定了MeasureSpec,在onMeasure()中就可以确定View的宽高

MeasureSpec的值由SpecSize(测量值)和SpecMode(测量模式)共同组成。

SpecMode一共有三种类型:

1EXACTLY表示设置了精确的值,一般当childView设置其宽高为精确值、match_parent(同时父容器也是这种模式)时,ViewGroup会将其设置为EXACTLY

2AT_MOST表示子布局被限制在一个最大值内(即SpecSize),一般当childView设置其宽高为wrap_contentmatch_parent(同时父容器也是这种模式)时,ViewGroup会将其设置为AT_MOST

3UNSPECIFIED表示View可以设置成任意的大小,没有任何限制。这种情况比较少见。

 

2. MeasureSpec的生成过程

2.1 顶级ViewMeasureSpec

// 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()方法来组装一个MeasureSpecrootDimension参数等于MATCH_PARENTMeasureSpecSpecModeEXACTLY。并且MATCH_PARENTWRAP_CONTENT时的SpecSize都等于windowSize的,也就意味着根视图总是会充满全屏的。

 

2.2 普通ViewMeasureSpec

在对子元素进行measure之前,会先调用getChildMeasureSpec方法得到子元素的MeasureSpec。上面也提到过了,子元素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);}

其中上述源码总结如下:

1)当View为固定宽高时,MeasureSpecEXACTLY模式/LayoutParams中的大小。

2)当ViewWRAP_CONTENT时,MeasureSpecAT_MOST模式/父容器剩余空间大小。

3)当ViewMATCH_PARENT时,若父容器为EXACTLY模式,那么ViewMeasureSpecEXACTLY模式/父容器的剩余大小。若父容器为AT_MOST模式,那么ViewMeasureSpecAT_MOST模式/父容器的剩余大小。

3. Measure过程

3.1 普通ViewMeasure过程

Viewmeasure()方法是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;}

onMeasure()方法中调用setMeasuredDimension()方法来设定测量出的大小,这样一次measure过程就结束了。

 

getDefaultSize方法中,从上面源码的11-20行,结合2.2中的结论可以知道:

(1)如果设置了固定宽高,View是EXACTLY模式并且传入MeasureSpec的大小就是自定义的宽高,上述11行、18行和19行代码表示这种设置会显示正常。

(2)如果设置了MATCH_PARENT,View的MODE会有两种情况,不过不管是哪一种,结果都是从MeasureSpec中获取大小,通过2.2中的结论可知为父容器剩余大小,因此这种设置逻辑上也会显示正常。

(3)如果设置了WRAP_CONTENT,View的MODE一定会是AT_MOST,结果是从MeasureSpec中获取大小,通过2.2中的结论可知为父容器剩余大小,逻辑上就会显示不正常,包裹内容效果会失效。这里就不贴实例了,网上有很多,有兴趣可以查看。

说了这么多,我们得出的结论就是:直接继承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 ViewGroupMeasure过程

因为一个布局中一般都会包含多个子视图,因此每个视图都需要经历一次measure过程。

ViewGroup是没有onMeasure方法的,这个方法是交给子类自己实现的。不同的ViewGroup子类布局都不一样,那么onMeasure索性就全部交给他们自己实现好了。

ViewGroup中定义了一个measureChildren()方法来遍历测量子视图的大小,如下所示:

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,其实现为:

protected void measureChild(View child, int parentWidthMeasureSpec,int parentHeightMeasureSpec) {           //通过子布局参数和父容器MeasureSpec得到childMeasureSpec       //过程略..       //所以这其实是个递归调用,不断的去测量设置子视图的大小,直至完成整个View数测量遍历        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);    }

以上就是关于measure过程的一些解析,后面会更新另外layout和draw过程的解析。

本文原创,转载请注明出处。欢迎留言交流,谢谢。

转载于:https://www.cnblogs.com/qitian1/p/6461498.html

你可能感兴趣的文章
border-radius讲解1
查看>>
CLR via C#学习笔记-第九章-参数和返回类型的设计规范
查看>>
dom4j解析XML文件(3)—XML文件写入
查看>>
vi作者:Bill Joy
查看>>
0139-文件操作之二进制方式打开模式(一).abb
查看>>
自定义分享功能
查看>>
基于Attribute的Web API路由设置
查看>>
vue使用中的随笔
查看>>
use sql trigger call java function
查看>>
这个人很牛
查看>>
Unity 新老版本动画文件设置
查看>>
关于win7 下双击不能打开jar 文件
查看>>
学习进度(2016.5.29)
查看>>
Visual studio 创建项目失败vstemplate
查看>>
keras 上添加 roc auc指标
查看>>
Linux命令(二)关机重启
查看>>
[OpeCV] highgui头文件
查看>>
C# 获取远程图片
查看>>
Android——MaterialDesign之一Toolbar
查看>>
filebeat output redis 报错 i/o timeout
查看>>