0. 前言  

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

本文原创,转载请注明出处:SEU_Calvin的CSDN博客


1. MeasureSpec

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

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

SpecMode一共有三种类型:

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

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

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

2. MeasureSpec的生成过程

2.1 顶级View的MeasureSpec

// desiredWindowWidth和desiredWindowHeight为屏幕尺寸
// lp.width和lp.height都等于MATCH_PARENT
childWidthMeasureSpec = 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的,也就意味着根视图总是会充满全屏的。

2.2 普通View的MeasureSpec

在对子元素进行measure之前,会先调用getChildMeasureSpec方法得到子元素的MeasureSpec。上面也提到过了,子元素MeasureSpec与父容器的MeasureSpec和子元素本身的LayoutParams有关。源码介绍如下:

//参1为父容器的MeasureSpec
public 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为固定宽高时,MeasureSpec为EXACTLY模式/LayoutParams中的大小。

(2)当View为WRAP_CONTENT时,MeasureSpec为AT_MOST模式/父容器剩余空间大小。

(3)当View为MATCH_PARENT时,若父容器为EXACTLY模式,那么View的MeasureSpec为EXACTLY模式/父容器的剩余大小。若父容器为AT_MOST模式,那么View的MeasureSpec为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;
}

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()指定一个默认的宽高。逻辑如下:

@Override
protected 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过程

因为一个布局中一般都会包含多个子视图,因此每个视图都需要经历一次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过程的解析。

本文原创,转载请注明出处:SEU_Calvin的CSDN博客。欢迎留言交流,谢谢。

Android开发——View绘制过程源码解析(一)的更多相关文章

  1. Android开发——View绘制过程源码解析(二)

    0. 前言   View的绘制流程从ViewRoot的performTraversals开始,经过measure,layout,draw三个流程,之后就可以在屏幕上看到View了.上一篇已经介绍了Vi ...

  2. Android笔记--View绘制流程源码分析(二)

    Android笔记--View绘制流程源码分析二 通过上一篇View绘制流程源码分析一可以知晓整个绘制流程之前,在activity启动过程中: Window的建立(activit.attach生成), ...

  3. Android笔记--View绘制流程源码分析(一)

    Android笔记--View绘制流程源码分析 View绘制之前框架流程分析 View绘制的分析始终是离不开Activity及其内部的Window的.在Activity的源码启动流程中,一并包含 着A ...

  4. Android之View绘制流程源码分析

    版权声明:本文出自汪磊的博客,转载请务必注明出处. 对于稍有自定义View经验的安卓开发者来说,onMeasure,onLayout,onDraw这三个方法都不会陌生,起码多少都有所接触吧. 在安卓中 ...

  5. 自定义控件(View的绘制流程源码解析)

    参考声明:这里的一些流程图援引自http://a.codekk.com/detail/Android/lightSky/%E5%85%AC%E5%85%B1%E6%8A%80%E6%9C%AF%E7% ...

  6. 基于Android开发的天气预报app(源码下载)

    原文:基于Android开发的天气预报app(源码下载) 基于AndroidStudio环境开发的天气app -系统总体介绍:本天气app使用AndroidStudio这个IDE工具在Windows1 ...

  7. Android开发——通过扫描二维码,打开或者下载Android应用

    Android开发——通过扫描二维码,打开或者下载Android应用   在实现这个功能的时候,被不同的浏览器折磨的胃疼,最后实现了勉强能用,也查考了一下其他人的博客 android实现通过浏览器点击 ...

  8. android 开发 View _1_ View的子类们 和 视图坐标系图

    目录: android 开发 View _2_ View的属性动画ObjectAnimator ,动画效果一览 android 开发 View _3_ View的属性动画ValueAnimator a ...

  9. Android IntentService使用介绍以及源码解析

    版权声明:本文出自汪磊的博客,转载请务必注明出处. 一.IntentService概述及使用举例 IntentService内部实现机制用到了HandlerThread,如果对HandlerThrea ...

随机推荐

  1. 【洛谷5283】[十二省联考2019] 异或粽子(可持久化Trie树+堆)

    点此看题面 大致题意: 求前\(k\)大的区间异或和之和. 可持久化\(Trie\)树 之前做过一些可持久化\(Trie\)树题,结果说到底还是主席树. 终于,碰到一道真·可持久化\(Trie\)树的 ...

  2. Ural State University Internal Contest October'2000 Junior Session

    POJ 上的一套水题,哈哈~~~,最后一题很恶心,不想写了~~~ Rope Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 7 ...

  3. POJ 2771 Guardian of Decency 【最大独立集】

    传送门:http://poj.org/problem?id=2771 Guardian of Decency Time Limit: 3000MS   Memory Limit: 65536K Tot ...

  4. 【luogu P4231 三步必杀】 题解

    题目链接:https://www.luogu.org/problemnew/show/P4231 诶 我很迷啊..这跟树状数组有什么关系啊...拿二阶差分数组过了..? #include <cs ...

  5. eclipse 安装/卸载插件

    1.通过Help>>Install New Soft 之后弹出对话框,可以通过“Add”按钮添加已经有的插件的(zip等)或者输入安装地址,之后按照要求即可.2.对于安装失败的插件,再次进 ...

  6. HDFS副本存放读取

    HDFS作为Hadoop中 的一个分布式文件系统,而且是专门为它的MapReduce设计,所以HDFS除了必须满足自己作为分布式文件系统的高可靠性外,还必须为 MapReduce提供高效的读写性能,那 ...

  7. ubuntu16.04

    原来安装的14.04快捷键冲突,又改不过来,还有就是每次从新启动,桌面就恢复原来的状态了.然后突然有一天桌面没了,我一气之下,从新安装.装好16.04还是没有桌面,我也是醉了,还好解决了.应该是我的集 ...

  8. 复制功能 js

    示例: <input class="herf" type="text" v-model="herfUrl" readonly=&quo ...

  9. 一个logstash引发的连环案,关于logstash提示:Reached open files limit: 4095, set by the 'max_open_files' option or default, files yet to open: 375248

    不多说,直接上问题.版本logstash-2.4.0,启动后提示错误: !!! Please upgrade your java version, the current version '1.7.0 ...

  10. shell脚本中 [-eq] [-ne] [-gt] [-lt] [ge] [le]

    -eq //等于 -ne //不等于 -gt //大于 (greater ) -lt //小于 (less) -ge //大于等于 -le //小于等于 在linux 中 命令执行状态:0 为真,其他 ...