前言

在上一篇我们了解了Activity的构成后,开始了解一下View的工作流程,就是measure、layout和draw。measure用来测量View的宽高,layout用来确定View的位置,draw则用来绘制View。这一讲我们来看看measure流程,measure流程分为View的measure流程和ViewGroup的measure流程,只不过ViewGroup的measure流程除了要完成自己的测量还要遍历去调用子元素的measure()方法。

1.View的measure流程

先来看看onMeasure()方法(View.java):

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

看看setMeasuredDimension()方法:

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);
}

很显然是用来设置View的宽高的,先来看看getDefaultSize()方法处理了什么:

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;
}

specMode是View的测量模式,而specSize是View的测量大小,看到这里我们有必要先看看MeasureSpec类:

public static class MeasureSpec {
private static final int MODE_SHIFT = ;
private static final int MODE_MASK = 0x3 << MODE_SHIFT; /**
* Measure specification mode: The parent has not imposed any constraint
* on the child. It can be whatever size it wants.
*/
public static final int UNSPECIFIED = << MODE_SHIFT; /**
* Measure specification mode: The parent has determined an exact size
* for the child. The child is going to be given those bounds regardless
* of how big it wants to be.
*/
public static final int EXACTLY = << MODE_SHIFT; /**
* Measure specification mode: The child can be as large as it wants up
* to the specified size.
*/
public static final int AT_MOST = << MODE_SHIFT; ... public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
...
}

MeasureSpec类帮助我们来测量View,它是一个32位的int值,高两位为specMode (测量的模式),低30位为specSize (测量的大小),测量模式分为三种:

  • UNSPECIFIED:未指定模式,View想多大就多大,父容器不做限制,一般用于系统内部的测量。
  • AT_MOST:最大模式,对应于wrap_comtent属性,只要尺寸不超过父控件允许的最大尺寸就行。
  • EXACTLY:精确模式,对应于match_parent属性和具体的数值,父容器测量出View所需要的大小,也就是specSize的值。

让我们回头看看getDefaultSize()方法,很显然在AT_MOST和EXACTLY模式下,都返回specSize这个值,也就是View测量后的大小,而在UNSPECIFIED模式返回的是getDefaultSize()方法的第一次个参数的值,这第一个参数从onMeasure()方法来看是getSuggestedMinimumWidth()方法和getSuggestedMinimumHeight()得到的,那我们来看看getSuggestedMinimumWidth()方法做了什么,我们只需要弄懂getSuggestedMinimumWidth()方法,因为这两个方法原理是一样的:

protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}

如果View没有设置背景则取值为mMinWidth,mMinWidth是可以设置的,它对应于android:minWidth这个属性设置的值或者View的setMinimumWidth的值,如果不指定的话则默认为0:

public void setMinimumWidth(int minWidth) {
mMinWidth = minWidth;
requestLayout();
}

如果View设置了背景在取值为max(mMinWidth, mBackground.getMinimumWidth()),取值mMinWidth和mBackground.getMinimumWidth()的最大值,上面我们说过了mMinWidth,那来看看mBackground.getMinimumWidth(),这个mBackground是Drawable类型的,看一下Drawable类的getMinimumWidth()方法(Drawable.java):

public int getMinimumWidth() {
final int intrinsicWidth = getIntrinsicWidth();
return intrinsicWidth > ? intrinsicWidth : ;
}

intrinsicWidth得到的是这个Drawable的固有的宽度,如果固有宽度大于0则返回固有宽度,否则返回0。
总结一下getSuggestedMinimumWidth()方法就是:如果View没有设置背景则返回mMinWidth ,如果设置了背景就返回mMinWidth 和Drawable最小宽度两个值的最大值。

2.ViewGroup的measure流程

ViewGroup的measure原理

讲完了View的measure流程,接下来看看ViewGroup的measure流程,对于ViewGroup,它不只要measure自己本身,还要遍历的调用子元素的measure()方法,ViewGroup中没有定义onMeasure()方法,但他定义了measureChildren()方法(ViewGroup.java):

protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
for (int i = ; 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) {
final LayoutParams lp = child.getLayoutParams(); final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height); child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

调用child.getLayoutParams()方法来获得子元素的LayoutParams属性,并获取到子元素的MeasureSpec并调用子元素的measure()方法进行测量。getChildMeasureSpec()方法里写了什么呢?

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec); int size = Math.max(, specSize - padding); int resultSize = ;
int resultMode = ; switch (specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childDimension >= ) {
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 >= ) {
// 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 >= ) {
// 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 = ;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = ;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}

很显然这是根据父容器的MeasureSpec的模式再结合子元素的LayoutParams属性来得出子元素的MeasureSpec属性,有一点需要注意的是如果父容器的MeasureSpec属性为AT_MOST,子元素的LayoutParams属性为WRAP_CONTENT,那根据代码我们会发现子元素的MeasureSpec属性也为AT_MOST,它的specSize值为父容器的specSize减去padding的值,也就是说跟这个子元素设置LayoutParams属性为MATCH_PARENT效果是一样的,为了解决这个问题需要在LayoutParams属性为WRAP_CONTENT时指定一下默认的宽和高。

LinearLayout的measure流程

ViewGroup并没有提供onMeasure()方法,而是让其子类来各自实现测量的方法,究其原因就是ViewGroup有不同的布局的需要很难统一,接下来我们来简单分析一下ViewGroup的子类LinearLayout的measure流程,先来看看它的onMeasure()方法(LinearLayout.java):

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}

来看看垂直measureVertical()方法的部分源码:

void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
mTotalLength = ;
mTotalLength = ;
...
for (int i = ; i < count; ++i) {
final View child = getVirtualChildAt(i); if (child == null) {
mTotalLength += measureNullChild(i);
continue;
} if (child.getVisibility() == View.GONE) {
i += getChildrenSkipCount(child, i);
continue;
} if (hasDividerBeforeChildAt(i)) {
mTotalLength += mDividerHeight;
} LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams(); totalWeight += lp.weight; if (heightMode == MeasureSpec.EXACTLY && lp.height == && lp.weight > ) {
// Optimization: don't bother measuring children who are going to use
// leftover space. These views will get measured again down below if
// there is any leftover space.
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
skippedMeasure = true;
} else {
int oldHeight = Integer.MIN_VALUE; if (lp.height == && lp.weight > ) {
// heightMode is either UNSPECIFIED or AT_MOST, and this
// child wanted to stretch to fill available space.
// Translate that to WRAP_CONTENT so that it does not end up
// with a height of 0
oldHeight = ;
lp.height = LayoutParams.WRAP_CONTENT;
} // Determine how big this child would like to be. If this or
// previous children have given a weight, then we allow it to
// use all available space (and we will shrink things later
// if needed).
measureChildBeforeLayout(
child, i, widthMeasureSpec, , heightMeasureSpec,
totalWeight == ? mTotalLength : ); if (oldHeight != Integer.MIN_VALUE) {
lp.height = oldHeight;
} final int childHeight = child.getMeasuredHeight();
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
lp.bottomMargin + getNextLocationOffset(child));
... if (useLargestChild &&
(heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED)) {
mTotalLength = ; for (int i = ; i < count; ++i) {
final View child = getVirtualChildAt(i); if (child == null) {
mTotalLength += measureNullChild(i);
continue;
} if (child.getVisibility() == GONE) {
i += getChildrenSkipCount(child, i);
continue;
} final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
child.getLayoutParams();
// Account for negative margins
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + largestChildHeight +
lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
}
} // Add in our padding
mTotalLength += mPaddingTop + mPaddingBottom; int heightSize = mTotalLength; // Check against our minimum height

定义了mTotalLength用来存储LinearLayout在垂直方向的高度,然后遍历子元素,根据子元素的MeasureSpec模式分别计算每个子元素的高度,如果是wrap_content则将每个子元素的高度和margin垂直高度等值相加并赋值给mTotalLength得出整个LinearLayout的高度。如果布局高度设置为match_parent者具体数值则和View的测量方法一样。

Android View体系(七)从源码解析View的measure流程的更多相关文章

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

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

  2. 【Android应用开发】EasyDialog 源码解析

    示例源码下载 : http://download.csdn.net/detail/han1202012/9115227 EasyDialog 简介 : -- 作用 : 用于在界面进行一些介绍, 说明; ...

  3. Android Handler机制(四)---Handler源码解析

    Handler的主要用途有两个:(1).在将来的某个时刻执行消息或一个runnable,(2)把消息发送到消息队列. 主要依靠post(Runnable).postAtTime(Runnable, l ...

  4. Android Handler机制(三)----Looper源码解析

    一.Looper Looper对象,顾名思义,直译过来就是循环的意思,从MessageQueue中不断取出message. Class used to run a message loop for a ...

  5. Android Handler机制(二)---MessageQueue源码解析

    MessageQueue 1.变量 private final boolean mQuitAllowed;//表示MessageQueue是否允许退出 @SuppressWarnings(" ...

  6. Mybatis 系列10-结合源码解析mybatis 的执行流程

    [Mybatis 系列10-结合源码解析mybatis 执行流程] [Mybatis 系列9-强大的动态sql 语句] [Mybatis 系列8-结合源码解析select.resultMap的用法] ...

  7. Android View体系(八)从源码解析View的layout和draw流程

    前言 上一篇文章我们讲了View的measure的流程,接下来我们讲下View的layout和draw流程,如果你理解了View的measure的流程,那这篇文章自然就不在话下了. 1.View的la ...

  8. Android View体系(五)从源码解析View的事件分发机制

    1.处理点击事件的方法 View的层级 我们知道View的结构是树形的结构,View可以放在ViewGroup中,这个ViewGroup也可以放到另一个ViewGroup中,这样层层的嵌套就组成了Vi ...

  9. Android View 事件分发机制 源码解析 (上)

    一直想写事件分发机制的文章,不管咋样,也得自己研究下事件分发的源码,写出心得~ 首先我们先写个简单的例子来测试View的事件转发的流程~ 1.案例 为了更好的研究View的事件转发,我们自定以一个My ...

随机推荐

  1. EasyUI 获取展开表中行数据

    var index = $('#dg').datagrid('getRowIndex', row); //为destory_user.php传递参数id var ids = $("#dg&q ...

  2. D3.js的一些基础部分 (v3版本)

    最近公司有需求要做一些可视化的功能.之前一直都是用Echarts做的,但是Echarts难以满足我们的需求,经过多方请教,查找发现D3可以满足我们的需求.第一次接触到D3,发现这些图标的可交互性非常丰 ...

  3. Python——pytessercat识别简单的验证码

    什么是验证码 验证码(CAPTCHA)是“Completely Automated Public Turing test to tell Computersand Humans Apart” (全自动 ...

  4. 从svn下载项目,并在tomcat启动

    1.需要先在本地安装mysql,并且启动成功(配置环境变量.客户端等). 2.需要下载小乌龟,需要从svn上下载项目. 3.安装eclipse,并且在eclipse上下载项目,会下载成两个聚合项目,不 ...

  5. Java的语法糖

    1.前言 本文记录内容来自<深入理解Java虚拟机>的第十章早期(编译期)优化其中一节内容,其他的内容个人觉得暂时不需要过多关注,比如语法.词法分析,语义分析和字节码生成的过程等.主要关注 ...

  6. 211. Orchard学习 二 2、ManualResetEvent 与 AutoResetEvent

    一.Orchard里异步请求处理线程队列的控制 Orchard的Orchard.WarmupStarter模块,为HttpApplication.BeginRequest时间附加了一个异步处理事件:B ...

  7. BF算法(模式匹配)

    BF算法 (Brute-Force算法) 一种简单的模式匹配算法,目的是寻找模式串p是否在目标串s中有出现. 思想:先从第一个字符开始匹配,如果p[j]==s[i],那么继续向下比较,一旦不相等,即回 ...

  8. Windows抓屏技术

    Windows桌面共享中一些常见的抓屏技术 1. BitBlt   我想做Windows开发应该都知道这个API, 它能实现DC间的内容拷贝, 如果我们把源DC指定成Monitor DC或是桌面DC, ...

  9. ACM学习<一>

    c++指针|指针入门 什么是指针? 其实指针就像是其它变量一样,所不同的是一般的变量包含的是实际的真实的数据,而指针是一个指示器,它告诉程序在内存的哪块区域可以找到数据.这是一个非常重要的概念,有很多 ...

  10. 由浅入深:CNN中卷积层与转置卷积层的关系

    欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文由forrestlin发表于云+社区专栏 导语:转置卷积层(Transpose Convolution Layer)又称反卷积层或分数卷 ...