前言
View的测量过程是View绘制三大步骤(测量、布局、绘制)中的第一步。
整个View树的测量涉及的流程很多,我们先看一些必要的前置知识:
首先来思考一下测量过程,测量的目的是确定View的尺寸,而Android中View是以树状结构组成的,那么:
- 每一个View(除去根节点以外)都存在于某一个ViewGroup中,子View的尺寸受到父View的限制,这就涉及到父View对子View的尺寸要求;
- 每一个ViewGroup又可能包含多个子View,所以ViewGroup不仅要考虑其父View对自己的尺寸要求,还要考虑到自己对多个子View的尺寸;
由此可见,测量过程中View之间不是孤立的,父View对子View存在着尺寸要求,那么代码中如何表述这种“要求”呢?答案是仅需要一个普通的int。具体的操作来看MeasureSpec类。
一、MeasureSpec:父View对子View的布局要求
MeasureSpec是View的一个静态内部类,用途是:将 父View对子View的布局要求 封装成一个int(目的是减少频繁的创建对象引起的性能问题)
MeasureSpec 将两部分信息填充到int中,一是模式,二是尺寸
一个int通常占用32位,MeasureSpec将其中的2位用于存储模式,剩余30位用于存储尺寸。
frameworks/base/core/java/android/view/View.java
public static class MeasureSpec {
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);
}
}
public static int getMode(int measureSpec) {
//noinspection ResourceType
return (measureSpec & MODE_MASK);
}
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
模式一共有3种,分别是:UNSPECIFIED
、EXACTLY
、AT_MOST
三种模式从名字也能看出,UNSPECIFIED
是尺寸未指定,EXACTLY
则是代表尺寸有具体的要求,AT_MOST
是规定了能够达到的最大尺寸。
此外MeasureSpec提供了静态方法makeMeasureSpec,用于将模式、尺寸组装成int,以及从int中解析模式和尺寸的getMode、getSize静态方法。
了解了父容器传递给子容器的测量要求MeasureSpec,接下来看一下具体代码中,ViewGroup怎样对子View进行测量
二、measureChildWithMargins:ViewGroup对子View的测量
ViewGroup中有measureChildWithMargins
方法对子View之一进行测量,根据注释,核心的工作是在getChildMeasureSpec
方法中完成的。
参数中:
child
是要测量的子ViewparentWidthMeasureSpec
、parentHeightMeasureSpec
是本ViewGroup的父容器传入的 对本ViewGroup的尺寸要求,可以认为是child
的“祖父View”对父View(也就是调用该方法的ViewGroup)的尺寸要求widthUsed
、heightUsed
是已经被父容器使用的额外空间(可能被父级的其他子View使用)
frameworks/base/core/java/android/view/ViewGroup.java
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
/* 从子View的xml中获取参数 */
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
/* 获取对子View的宽、高要求 */
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width)