今天主要学习记录一下Android View绘制三部曲的第一步,onMeasure,测量。

起源

在Activity中,所有的View都是DecorView的子View,然后DecorView又是被ViewRootImpl所控制,当Activity显示的时候,ViewRootImpl的performTranversals方法开始运行,这个方法很长,不过核心的三个流程就是依次调用performMeasure、performLayout、performDraw三个方法,从而完成DecorView的绘制。

ViewRootImpl#performMeasure

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}

这里直接调用了mView的measure方法,参数是两个经过设置的MeasureSpec,接下来我们分析一下MeasureSpec是如何设置的。

MeasureSpec

这个MeasureSpec不是实际测绘值,而是父View传递给子View的布局要求,MeasureSpec涵盖了对子View大小和模式的要求。其中,三种模式要求分别是:

  • UNSPECIFIED:对子View无任何要求,想要测绘多少由子View决定。
  • EXACTLY:父View已确定了自己确切的大小。子View将在这个边界内测绘自己的宽高。
  • AT_MOST:父View对子View没有要求,子View可以达到它想要的大小。

首先这个MeasureSpec是个32位的int值,其中31,32两位代表的是三种模式的要求,分别是00….、01….、11….,makeMeasureSpec方法中,sUseBrokenMakeMeasureSpec默认是false,所以一般执行(size & ~MODE_MASK) | (mode & MODE_MASK)这个语句,这个意思就是说,MeasureSpec的高两位代表的是模式,低30位代表父View的尺寸。
下面是对应的方法:

private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
public static final int EXACTLY = 1 << MODE_SHIFT;
public static final int AT_MOST = 2 << MODE_SHIFT;
public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
@MeasureSpecMode int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}

View#measure

计算完MeasureSpec,DecorView就该执行measure方法了。

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
···
final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
···
final boolean needsLayout = specChanged
&& (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize); if (forceLayout || needsLayout) {
···
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
···
}
···
}
···
}
···
}

measure方法是final的,所以不能重写,不过measure方法最主要的作用就是调用了onMeasure方法,由于DecorView是继承的FrameLayout,所以本篇文章我们主要分析FrameLayout的onMeasure方法。

FrameLayout#onMeasure

这个onMeasure方法,可能是View绘制中最难理解的了,所以我们逐步分析。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//获取子View个数
int count = getChildCount(); //判断是否是确定宽高的
//如果宽高都确定,那么boolean为false
//如果有一个不确定,那么boolean为true
final boolean measureMatchParentChildren =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
mMatchParentChildren.clear(); int maxHeight = 0;
int maxWidth = 0;
int childState = 0; //遍历所有不为GONE的子View,并加以计算
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
//计算各个子View宽高,包括Margin参数以及padding参数
//该方法详细分析见下文
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
maxWidth = Math.max(maxWidth,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
maxHeight = Math.max(maxHeight,
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
//用按位或的方法合并所有子View的State
//getMeasuredState方法详细解析在下面
childState = combineMeasuredStates(childState, child.getMeasuredState());
//如果宽高有不确定的(即warp_content模式),将子View中宽或高是match_parent的添加到mMatchParentChildren中。
if (measureMatchParentChildren) {
if (lp.width == LayoutParams.MATCH_PARENT ||
lp.height == LayoutParams.MATCH_PARENT) {
mMatchParentChildren.add(child);
}
}
}
} // 计算padding
maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground(); // 与最小宽高作比较,二者取较大的
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth()); // 与前景图宽高作比较,二者取较大的
final Drawable drawable = getForeground();
if (drawable != null) {
maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
} //计算并保存measured宽高
//resolveSizeAndState方法分析在下面
//setMeasuredDimension方法分析在下面
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT)); //计算macth_parent的子View的个数
count = mMatchParentChildren.size();
//只有FrameLayout中宽或者高有warp_content属性,
//并且match_parent的子view个数大于1才会执行下面代码。
//因为如果宽高都是match_parent的,或者设置好dp数值的,则mMatchParentChildren永远是空的。 //在这里会重新计算传递给子View的MeasureSpec值,并重新测绘子View。
//关于MeasureSpec值的计算,可以参考下文ViewGroup#getChildMeasureSpec的表格。
//这里需要注意的是,match_parent行所有的结果均改为:EXACTLY + parentSize
if (count > 1) {
for (int i = 0; i < count; i++) {
final View child = mMatchParentChildren.get(i);
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); final int childWidthMeasureSpec;
if (lp.width == LayoutParams.MATCH_PARENT) {
final int width = Math.max(0, getMeasuredWidth()
- getPaddingLeftWithForeground() - getPaddingRightWithForeground()
- lp.leftMargin - lp.rightMargin);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
width, MeasureSpec.EXACTLY);
} else {
childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
lp.leftMargin + lp.rightMargin,
lp.width);
} final int childHeightMeasureSpec;
if (lp.height == LayoutParams.MATCH_PARENT) {
final int height = Math.max(0, getMeasuredHeight()
- getPaddingTopWithForeground() - getPaddingBottomWithForeground()
- lp.topMargin - lp.bottomMargin);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
height, MeasureSpec.EXACTLY);
} else {
childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
lp.topMargin + lp.bottomMargin,
lp.height);
} child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
}

ViewGroup#measureChildWithMargins

该方法作用是测绘子View,在父View的onMeasure中循环调用,达到遍历的效果。

protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
//获取子View的LayoutParams
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); //计算子View的MeasureSpec
//getChildMeasureSpec方法分析见下文
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);
//调用子View的measure方法,对子View进行测绘
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

ViewGroup#getChildMeasureSpec

此方法通过父View的MeasureSpec值以及LayoutParams的宽高,来生成子View的MeasureSpec值,具体代码如下:

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
//计算父View的size和mode
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec); //计算刨除padding的size
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;
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}

上面代码,其实通过一张表格,就能很清楚的解释转换规律

竖列代表LayoutParams的宽或高属性\横行父View传递的Mode EXACTLY AT_MOST UNSPECIFIED
精确值 EXACTLY + childSize EXACTLY + childSize EXACTLY + childSize
match_parent EXACTLY + parentSize AT_MOST + parentSize UNSPECIFIED + parentSize
warp_content AT_MOST + parentSize AT_MOST + parentSize UNSPECIFIED + parentSize

View#getMeasuredState

public final int getMeasuredState() {
return (mMeasuredWidth&MEASURED_STATE_MASK)
| ((mMeasuredHeight>>MEASURED_HEIGHT_STATE_SHIFT)
& (MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT));
}

该方法返回一个表达View宽高measure_state值的整数,第8位代表height是否是MEASURED_STATE_TOO_SMALL,第24为代表width是否是MEASURED_STATE_TOO_SMALL的。
例如:
宽是MEASURED_STATE_TOO_SMALL的
返回:0000 0001 0000 0000 0000 0000 0000 0000
高是MEASURED_STATE_TOO_SMALL的
返回:0000 0000 0000 0000 0000 0001 0000 0000
宽高均是MEASURED_STATE_TOO_SMALL的
返回:0000 0001 0000 0000 0000 0001 0000 0000

View#resolveSizeAndState

public static final int MEASURED_STATE_TOO_SMALL = 0x01000000;
public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
//计算父view传递的size和mode
final int specMode = MeasureSpec.getMode(measureSpec);
final int specSize = MeasureSpec.getSize(measureSpec);
final int result;
switch (specMode) {
case MeasureSpec.AT_MOST:
if (specSize < size) {
//如果父View给的size小于自身测绘出的size,
//则在第24位上加上measure_state标记。
result = specSize | MEASURED_STATE_TOO_SMALL;
} else {
result = size;
}
break;
case MeasureSpec.EXACTLY:
result = specSize;
break;
case MeasureSpec.UNSPECIFIED:
default:
result = size;
}
//如果child在第24为上有measure_state标记,则在result的第24位上也加上measure_state标记,然后返回result。
return result | (childMeasuredState & MEASURED_STATE_MASK);
}

View#setMeasuredDimension

该方法将计算好的measuredWidth和measuredHeight设置给成员变量mMeasuredWidth及mMeasuredHeight,并且将flag设置成PFLAG_MEASURED_DIMENSION_SET。
在4.3版本以上,如果设置了optical模式,则还要对width、height进一步修改,然后再设置mMeasuredWidth、mMeasuredHeight。
mMeasuredWidth和mMeasuredHeight的值,不光包括size,同时还包括state,具体请看getMeasuredWidthgetMeasuredWidthAndStategetMeasuredHeightgetMeasuredHeightAndState方法

protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int opticalWidth = insets.left + insets.right;
int opticalHeight = insets.top + insets.bottom; measuredWidth += optical ? opticalWidth : -opticalWidth;
measuredHeight += optical ? opticalHeight : -opticalHeight;
}
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
} private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight; mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}

时序图

图为onMeasure时序图

小结

到这里,关于View的测绘我们大概的走了一遍。Measure的原理就是通过遍历,从上至下,利用传递的MeasureSpec以及子View的LayoutParams,依次进行测绘。不同的layout可能会进行多次的measure,所以熟读源码,合理布局,可以帮我们避免不必要的measure开销,达到提升性能的效果。

最后,感谢阅读,也希望可以和大家多多交流,共同进步。

系列文章

Android 视图及View绘制分析笔记之setContentView
View绘制分析笔记之onMeasure
View绘制分析笔记之onLayout
View绘制分析笔记之onDraw

2.View绘制分析笔记之onMeasure的更多相关文章

  1. 3.View绘制分析笔记之onLayout

    上一篇文章我们了解了View的onMeasure,那么今天我们继续来学习Android View绘制三部曲的第二步,onLayout,布局. ViewRootImpl#performLayout pr ...

  2. 4.View绘制分析笔记之onDraw

    上一篇文章我们了解了View的onLayout,那么今天我们来学习Android View绘制三部曲的最后一步,onDraw,绘制. ViewRootImpl#performDraw private ...

  3. 1.Android 视图及View绘制分析笔记之setContentView

    自从1983年第一台图形用户界面的个人电脑问世以来,几乎所有的PC操作系统都支持可视化操作,Android也不例外.对于所有Android Developer来说,我们接触最多的控件就是View.通常 ...

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

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

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

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

  6. Android应用层View绘制流程与源码分析

    1  背景 还记得前面<Android应用setContentView与LayoutInflater加载解析机制源码分析>这篇文章吗?我们有分析到Activity中界面加载显示的基本流程原 ...

  7. Android中View绘制流程以及invalidate()等相关方法分析

    [原文]http://blog.csdn.net/qinjuning 整个View树的绘图流程是在ViewRoot.java类的performTraversals()函数展开的,该函数做的执行过程可简 ...

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

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

  9. Android中View绘制流程以及invalidate()等相关方法分析(转)

    转自:http://blog.csdn.net/qinjuning 前言: 本文是我读<Android内核剖析>第13章----View工作原理总结而成的,在此膜拜下作者 .同时真挚地向渴 ...

随机推荐

  1. Eclipse换背景色

    上班后,长时间看代码,眼睛感觉有些疲惫,就想想如果能换个肤色就好了,于是在网上搜了一下,果真Eclipse提供了这个方面功能,心情小激动, 顿时感觉萌萌哒,于是乐呵呵的把肤色改了.在这感谢网上的亲们, ...

  2. 第四章 --- 关于Javascript 设计模式 之 迭代器模式

    今天我先写 两个常用的迭代器的 例子.(同学们先自行体会这二种迭代器的优缺点) 需求:比较两个数组是否相等 tips: 当数组的下标不为数字的时候,默认为 该键值对 为 对象. 然后迭代器的原理基本来 ...

  3. java初始化

    一.成员初始化 1.成员变量没有赋值,则被初始化成默认值. 2.局部变量没有赋值,编译时报错. 二.构造器初始化 1.成员变量在构造器初始化之前,已经被初始化. 2.变量定义的顺序决定了初始化的顺序. ...

  4. elastic search使用总结

    1. elasticsearch安装 官方下载地址:https://www.elastic.co/downloads/elasticsearch 解压文件 elasticsearch-2.4.0.zi ...

  5. 安卓app开发笔记

    移动app应用开发也是信息技术课程科技创新的范畴,所以在个人开发app时候记录一些笔记,可能会很乱,所以选择按点来写. 首先是一些入门的资料,有很多需要自己学习的 https://www.oschin ...

  6. 关于JQ toggle 的注意事项

    1.9.1以后的版本,好像不支持 jq 的 toggle function的用法啦.

  7. 卸载linux Mint自带jdk并安装最新jdk

    查看安装的软件包sudo dpkg --list | grep -i jdk 删除jdksudo apt-get purge openjdk* 删除其他的包sudo apt-get purge ice ...

  8. Mac 使用Sublime Text 3 搭建C开发环境

    Sublime Text 3  (安装包,注册码 ,汉化包) 1)工具-编译系统-新建编译器 { "cmd" : ["gcc -o ${file_base_name} $ ...

  9. 412. Fizz Buzz

    https://leetcode.com/problems/fizz-buzz/ 没什么好说的,上一个小学生解法 class Solution(object): def fizzBuzz(self, ...

  10. Java基础高级二(多线程)

    1.进程和线程的区别:线程是轻量级的,本省不会持太多资源,需要的时候向进程申请 2.线程的状态:创建,可执行,执行中,等待,休眠,阻塞 3.线程状态之间的转换 4.线程API:Thread类,Runn ...