测量篇(measure)
计算视图大小的过程-measure
首先需要澄清一个概念,即“视图大小”,视图大小是指什么?
应用程序开发时,我们可以在layout.xml
文件中使用android:layout_height
及layout_width
属性设置宽和高,这指的是视图大小吗?
答案是No!layout.xml
中设置的android:layout_height
和android:layout_width
指的是父视图给子View设置的“窗口大小”,因此更确切的说应该是视图的布局(layout)大小。该属性值可以是相对值WRAP_CONTENT,也可以是具体值例如100dp。
view内部使用measureWidth和measureHeight来保存其值,另外view内部还有mLeft、mRight、mTop、mBottom四个变量,其指的是该View在父视图中所占的区域,mRight-mLeft的大小一般是measureWidth的大小,mBottom-mTop的大小一般是measureHeight的大小。
Measure过程的本质就是把视图布局时使用的“相对值”转换为具体值的过程,即把WRAP_CONTENT及MATCH_PARENT转换为具体的值。如果FrameWork中不使用相对值,那么也就完全不需要measure过程了。
measure内部设计思路
View系统启动measure是从ViewRoot中调用host.measure()开始,host.measure()会 调 用 到View类 的 measure()函数,该函数然后回调onMeasure()。
View中measure函数的原型为:
public final void measure(int widthMeasureSpec,int heightMeasureSpec);
在该函数定义中,final
关键字说明,该函数是不能被重载的,即View系统定义的这个measure
框架不能被修改。参数widthMeasureSpec
和heightMeaureSpec
分别对应宽和高的measureSpec
,measureSpec
是一个int型值,当父视图对子视图进行measure操作时,会调用子视图的measure()函数,该参数的意思是父视图所提供的measure的“规格”,因为父视图最终为子视图提供的“窗口”大小是由父视图和子视图共同决定的。
该值由高32位和低16位组成,其中高32位保存的是specMode,低16位为specSize。specMode有三种,分别如下。
- MeasureSpec.EXACTLY:
父视图希望子视图的大小应该是specSize中指定的。在一般情况下,View的设计者应该遵守该指示,将View的measuredHeigth和measuredWidth设置成指定的值,当然,也可以不遵守。
- MeasureSpec.AT_MOST:
子视图的大小最多是specSize中指定的值。在一般情况下,View的设计者应该尽可能小地设置视图的大小,并且不能超过specSize,当然,也可以超过specSize。
- MeasureSpec.UNSPECIFIED:
此时View的设计者可以根据自身的特性设置视图的大小。
该参数是父视图传递给子视图的,那么最根上的measureSpec是怎么产生的呢?
ViewRoot中,调用host.measure()
函数时,参数分别是局部变量childWidthMeasureSpec
和childHeightMeasureSpec
,这两个变量的赋值最初是通过调用getRootMeasureSpec()
函数获得的,
childWidthMeasureSpe = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
其中参数desiredWindowHeight
代表了窗口希望的大小,其值一般为窗口本身大小,参数lp等于mWindowAttributes
,而mWindowAttributes
在ViewRoot对象初始化时创建,如以下代码所示:
public LayoutParams(){
super(LayoutParams.MATCH_PARENT, LayoutParam.MATCH_PARENT);
type = TYPE_APPLICATION;
format = PixelFormat.OPAQUE;
}
也就是说,lp.height的值是MATCH_PARENT。下面再来看getRootMeasureSpec()函数的过程,如以下代码所示:
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
//rootDimension就是lp.width和lp.height
switch (rootDimension) {
//可以看出来,在xml中定义的相对值在这里会被转换为具体的大小
//由于默认创建的lp.width和lp.height是MATCH_PARECT类型
//所以最根本的measureSpec类型就是EXCTLY类型
case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.
//注意,此时View的内部类MeasureSpec的三个模式在这里起作用哟
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY)
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// Window wants to be an exact size. Force root view to be that size.
//默认的就是EXACTLY类型了,所以我们布局的"fill_parent"是EXACTLY类型
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
这段代码正是产生measureSpec
的过程,它揭示了lp中MATCH_PARENT
及WRAP_CONTENT
和measureSpec
的关系。由于上面lp.height的值为MATCH_PARENT
,所以,最根儿上的measureSpec
类型就是EXACTLY
,并且specSize
等于windowSize
,即视图窗口的大小。看到这里有些读者可能会觉得有点混淆,请看以下layout.xml
文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:width="2"
android:text="I will be King"/>
</LinearLayout>
那么这段布局文件中LinearLayout
中声明的layout_height
和layout_width
属性将扮演两个角色,一个是和LinearLayout
的父视图一起对LinearLayout
本身进行measure
操作,另一个角色就是作为TextView
的measureSpec
,并和TextVIew
的layout_height
、layout_width
一起对TextView
进行measure
操作。
ViewGroup中的measureChildWithMargins()
ViewGroup提供了三个类似的函数用于对子视图进行measure()操作,分别如下:
- measureChildren(): 该函数内部使用for循环调用measureChild对每一个子视图进行measure操作
- measureChild(): 为指定的子视图进行measure操作
- measureChildWithMargins(): 该函数与measureChild的唯一区别在于,measure时考虑把margin及padding也作为子视图大小的一部分
以上三个函数的含义是一样的,因此接下来仅分析measureChildWithMargins函数。该函数原型如下:
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
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);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
其中参数parentHeightMeasureSpec
是该child的父视图传递过来的measureSpec
,参数heightUsed
是在该measureSpec
中,父视图已经使用了的高度。该函数内部流程分三步:
1. 调用了child.getLayoutParams()
获得子视图的LayoutParams
属性。
在 View类中,该函数的默认返回值是 ViewGroup丄ayoutParams
,而此处却被强制类型转换为ViewGroup.MarginLayoutParams
类型。强制类型转换一般只能把子类强制转换为父类,而不能把父类强制转换为子类, 除非该对象本身就是子类对象,否则会发生转换错误。而 此 处 MarginLayoutParams是子类,其父类是 LayoutParams
,但代码中却把getLayoutParams()
返回值强制转换为了 一个子类MarginLayoutParams
对象, 这是为什么呢?
这就要来看View对象的LayoutParams
属性是如何被赋值的。创建一个View对象一般有两种方法,
一种是使用X M L文件静态描述,另一种是程序动态调用View类的构造函数,构造函数中指明所使用 LayoutParams
参数。两种方法的本质是相同的,因此这里仅介绍第一种方法。
LayoutInflater inflator = (LayoutInflater)context.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
inflator.inflate(R.layout.testlayout,null);
该段代码中首先调用getSystemServiceO获得 Layoutlnflater服务,然后调用inflate()函数创建参数中指定的View对象,该函数的第一个参数对应XML文件,第二个参数指定该View的父视图,一般为空。 XML文件中则用具体的标签来描述一个View对象,比如< LinearLayout >描述一个ViewGroup对象,而在inflate()函数内部会根据这些指定的标签找到对应的类,比如LinearLayout类。找到这些类后,就会调用相应的构造函数创建View对象,创建好后,此时该对象内部的LayoutParams属性是空的,所有的View对象要想被显示在屏幕上,必须得先调用addView()方法把该View对象添加到当前窗口的View树中,
addView函数源代码:
public void addView(View child, int index) {
if (child == null) {
throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
}
LayoutParams params = child.getLayoutParams();
if (params == null) {
params = generateDefaultLayoutParams();
if (params == null) {
throw new IllegalArgumentException(
"generateDefaultLayoutParams() cannot return null ");
}
}
addView(child, index, params);
}
由于child当前的LayoutParams属性为空,于是调用generateDefaultLayoutParams()
函数将生成一个LayoutParams对象。所有的Viewroup对象实例一般都会重载该函数,比如LiearLayout中重载该函数返回一个LinearLayout.LayoutParams对象,而该类的父类就是ViewGroup.MarginLayoutParams。
这就是以上为什么能够被强制转换的原因,对 于 RelativeLayout及 FrameLayout等常见ViewGroup实例
也一样,其内部都定义了相应的LayoutParams类,并且这些类都是基于ViewGroup.MarginLayoutParams的。
当然,你可以设计一个ViewGroup实例,并且不重载generateDefaultLayoutParams()
。如果这样的话 ,应用程序在向你定义的这个 ViewGroup中添加 V iew 时,将 使 用 ViewGroup中默认的
ViewGroup.LayoutParams。这将导致程序员在XML中使用的margin的属性不会起作用,如以下代码所示:
<com.example.myCustomLinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="10dp" // 这一句将不会起作用
android:background="@color/white">
...
</myCustomLinearLayout>
并且,你将不能在你自定义的ViewGroup中使用measureChildWithMargins(),否则会因为强制类型转换错误而发生异常。
2. 调用两次getChildMeasureSpec()
函数,分别计算出该子视图的宽度和高度的Spec。
之前说过,子视图所占布局大小取决于父视图提供的规格和子视图本身希望占用的大小,而这就是该函数参数的意义。该函数源代码:
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 them 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);
}
参 数 spec正是父视图提供的spec,源码中对应的变量是parentHeightMeasureSpec,这个值是int型。 高 16位代表specMode, —共有三种模式,分别是AT—MOST、EXACTLY及 UNSPECIFIED;低 16位 代 表 specSize,对于非UNSPECIFIED模式,该值指父视图所允许的最大尺寸或者期望的理想尺寸。
参 数padding代表的是在父视图提供的spec尺寸中,已经使用的多少,源码中该值包含了 padding
填充、margin空余,以及父视图所提供的已经使用的高度heightUsed:
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed
getChildMeasureSpec
中会从parent的specSize中减去该padding,以便子视图使用已用的空间。
参数 childDimension即为子视图期望获得的高度,其值来源于lp.height,这个值正是XM L文件中
andorid:layout_height对应的值,可能是一个具体的值,也可能是 MATCH_PARENT或者 WRAP_CONTENT
getChildMeasnreSpecO的作用就是根据以上两个条件确定子视图的“测量规格”,注意,
为什么这里确定的不是最终子视图的尺寸而只是“测量规格” ?
读者可以换一个角度来理解parentMeasureSpec和
lp.height这两个参数,前者实际上是指父视图可以提供的尺寸,而对一个应用程序而言,程序员必须负 责父视图所包含的所有子视图的排列关系,以达到一个优美的界面布局,因此,程序员才使用lp.height指定期望该子视图的大小,而至于子视图到底有多大,还需要征询子视图本身的意思,所以该函数并不 能最终确定视图的尺寸。
该函数内部的执行逻辑如表13-6所示。
以上逻辑值得注意的地方是,就算父视图限制子视图的高度,比如400dip,而程序员依然可以设置lp.height=1000dip,这将导致最终子视图的大小是1000dip,这就会超出父视图的可视窗口。
3. 上一步已经确定了子视图的测量规格,最后一步就是“征求子视图本身的意思” 了,‘ 即调用
child.measure()函数。
子视图可以重载onMeasure()函数,并可调用setMeasureDimension()函数设置任意
大小的布局,而这将成为该子视图的最终布局大小。
以上逻辑是一种非常“ 民主” 的逻辑,该逻辑由三方组成,包括父视图、程序员、子视图的设计者。 父视图提供了上一级可以提供的大小,程序员在XM L文件中使用
layout_height
设置希望的大小,而视 图最终的大小则由子视图的设计者“拍板”,良好的视图设计者一般会根据子视图的measureSpec
设置合适的布局大小,以尊重程序员的意图。
LinearLayout 中的 onMeasure()过程举例
view中的measure()函数是不能被重载的,以保证View系统中measure的基本流程.ViewGroup的实例一般需要重载onMeasure()函数,并在该函数中调用ViewGroup的measureChild()相关函数对每一个子视图进行measure操作。LinearLayout中的onMeasure()函数内部,首先判断该LinearLayout是水平还是垂直的,并分别调用measureHorizontal()和measureVertical(),下面仅分析垂直方向的measure过程。
该过程表面上似乎挺复杂,实际上逻辑比较简单,可总体分为三个步骤:
- 第一个for循环是为了计算所有子视图的高度;代码如下:
for (int i = 0; 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;
}
nonSkippedChildCount++;
if (hasDividerBeforeChildAt(i)) {
mTotalLength += mDividerHeight;
}
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
totalWeight += lp.weight;
final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;
if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {
// Optimization: don't bother measuring children who are only
// laid out using excess space. These views will get measured
// later if we have space to distribute.
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
skippedMeasure = true;
} else {
if (useExcessSpace) {
// The heightMode is either UNSPECIFIED or AT_MOST, and
// this child is only laid out using excess space. Measure
// using WRAP_CONTENT so that we can find out the view's
// optimal height. We'll restore the original height of 0
// after measurement.
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).
final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
heightMeasureSpec, usedHeight);
final int childHeight = child.getMeasuredHeight();
if (useExcessSpace) {
// Restore the original height and record how much space
// we've allocated to excess-only children so that we can
// match the behavior of EXACTLY measurement.
lp.height = 0;
consumedExcessSpace += childHeight;
}
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
lp.bottomMargin + getNextLocationOffset(child));
if (useLargestChild) {
largestChildHeight = Math.max(childHeight, largestChildHeight);
}
}
/**
* If applicable, compute the additional offset to the child's baseline
* we'll need later when asked {@link #getBaseline}.
*/
if ((baselineChildIndex >= 0) && (baselineChildIndex == i + 1)) {
mBaselineChildTop = mTotalLength;
}
// if we are trying to use a child index for our baseline, the above
// book keeping only works if there are no children above it with
// weight. fail fast to aid the developer.
if (i < baselineChildIndex && lp.weight > 0) {
throw new RuntimeException("A child of LinearLayout with index "
+ "less than mBaselineAlignedChildIndex has weight > 0, which "
+ "won't work. Either remove the weight, or don't set "
+ "mBaselineAlignedChildIndex.");
}
boolean matchWidthLocally = false;
if (widthMode != MeasureSpec.EXACTLY && lp.width == LayoutParams.MATCH_PARENT) {
// The width of the linear layout will scale, and at least one
// child said it wanted to match our width. Set a flag
// indicating that we need to remeasure at least that view when
// we know our width.
matchWidth = true;
matchWidthLocally = true;
}
final int margin = lp.leftMargin + lp.rightMargin;
final int measuredWidth = child.getMeasuredWidth() + margin;
maxWidth = Math.max(maxWidth, measuredWidth);
childState = combineMeasuredStates(childState, child.getMeasuredState());
allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;
if (lp.weight > 0) {
/*
* Widths of weighted Views are bogus if we end up
* remeasuring, so keep them separate.
*/
weightedMaxWidth = Math.max(weightedMaxWidth,
matchWidthLocally ? margin : measuredWidth);
} else {
alternativeMaxWidth = Math.max(alternativeMaxWidth,
matchWidthLocally ? margin : measuredWidth);
}
i += getChildrenSkipCount(child, i);
}
源码中使用变量mTotalLength
保存已经measure过的child所占用的高度,该变量刚开始时是0。
for()循环中调用measureChildBeforLayout()
对每一个child进行测量,该函数实际上仅仅是调用了上节所 说的measureChildWithMargins()
,在调用该函数时,使用了两个参数。其中一个是heightMeasureSpec
, 该参数为LinearLayout本身的measureSpec
;另一个参数就是mTotalLength
,代表该LinearLayout
已经 被其他子视图所占用的高度,这个变量更应该命名为mTotalHeight
。注意第二个参数mTotalHeight
,并不是说所有的child调用时都使用mTotalHeight
,如果totalWeight>0的话,则第二个参数为0。
每次for()循环对child测量完毕后,调 用 child.getMeasuredHeight()
获取该子视图最终的高度,并将这个高度添加到mTotalLength
中。
在本步骤中,暂时避过了 lp.weight>0
的子视图,即暂时先不测量这些子视图,因为后面将把父视图剩余的高度按照weight大小均匀分配给相应的子视图。源码中使用了一个局部变量totalWeight
累计所有子视图的weight值。处 理 lp.weight>0的情况需要注意,如果变量heightMode是 EXACTLY,那么, 当其他子视图占满父视图的高度后,weight>0的子视图有可能分配不到布局空间,从而不被显示,只有当 heightMode是 AT_MOST或 者 UNSPECIFIED时,weigth>0的视图才能优先获得布局高度。请看以下两段代码。
第一段代码:
<LinearLayout
android:layout_width="match_parent"
android:layout_height="100dp"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="20dp" // 不管height是多少,TextView都不会被显示
android:layout_weight="2"
android:textSize="30sp"
android:text="I am alive"/>
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black"/>
</LinearLayout>
在第一段代码中,由于LinearLayout的 lp.height为 MATCH_PARENT,因此对应heightMode
是 EXACTLY,其包含的wdght>0的 TextView的高度无论是多少,都将由于其后的ListView占满屏幕,从而使得TextView所占的空间被消耗掉,也就是不能显示到屏幕上。
但如果 ListView不能占满整个屏幕,那么TextView的 height将起作用。请看一下代码:
第二段代码:
<LinearLayout
android:layout_width="match_parent"
android:layout_height="100dp"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_weight="2"
android:textSize="30sp"
android:text="I am alive"/>
<ListView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="2"
android:background="@color/black"/>
</LinearLayout>
在这段代码中,由于ListView本身的高度仅share剩余部分,所以TextView将有足够的空间显示,
其 height为 60,所以将首先获得60高度,此时父窗口 LinearLayout将剩余40,再把这40均匀分配给 TextView和 ListView,因为它们的weight值都为2。最终的结果是TextView将获得80高度,而 ListView获得20高度。
第三段代码:
<LinearLayout
android:layout_width="match_parent"
android:layout_height="100dp"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="2"
android:text="I am alive"
android:textSize="30sp" />
</LinearLayout>
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black" />
</LinearLayout>
因为TextView的父视图LinearLayout的 lp.height是WRAP_CONTENT,因此对应的heightMode是 AT_MOST。因此,TextView 的 lp.height被修改为WRAP_CONTENT,才能够优先于ListView显示,所以将被显示到屏幕上。
-
第二个for()循环,该段逻辑实际上对应用程序是不可见的, 因为它处理的是一种叫做useLargestChild的模式。这种模式本应该使用android:layout_useLargestChild
在 X M L 中指定,然 而 SDK中却暂时没有开放这个逻辑,其内部逻辑似乎有些问题。因为该段代码仅 仅是根据最大子视图高度修正了所有子视图的高度总和,而修正的算法仅仅是把最高子视图的margin重新计算在内,而这在第一步已经计算过了。因此,这段代码我们暂且不理,可以认为不会执行到该段代码。 -
上一步计算了正常的子视图的高度,接下来需要判断父视图中是否还有剩余空间,并将剩余空间均匀分配给weight>0的子视图们。
在该步骤中,首先调用resolveSize()
获取所有子视图最终能够占用的布局大小,因为mTotalHeight
可能很大,它仅仅代表所有子视图最终的高度总和。如果有些子视图不配合,可能给自己设置一个超过 父视图能提供的高度值,因此需要根据该LinearLayout
的heightMeasureSpec
和mTotalHeight
计算这些 子视图们最终可以占用的布局高度,并重新赋值给heightSize
。
此时,heightSize
代表了父视图能够提供的并且子视图肯定会占用完的高度,当然,可能这个值并不够子视图们使用。接着用heightSize
和子视图们真正的高度产生一个delta
值,delta
小 于 0 意味着空间不够用,因此需要那些weight>0
的子视图腾出空间;如 果delta
大 于 0,则意味着空间还有富余,因此把富余的空间平均分配给那些weight>0
的子视图。
该步骤中有一个变量mWeightSum,在默认情况下该值为1 ,应用程序可以在X M L 文件中使用
android:layout—weightSum设置该值,其意义是设置一个weight总和,以便其子视图可以使用比例值设 置子视图的权重。比如可以设置LinearLayout的 weightSum为2.5,然后设置子视图的weight值 为 0.5, 那么该子视图将获得剩余空间的20%。在默认情况下,该值为1,因此代码执行是使用totalWeight作 为局部变量weightSum的值。
接着,将 mTotalLength置 为 0,并开始调用for()循环。过程有些类似第1 步,所不同的是,第 1
步中已经measure()过了所有height不 为 0 的视图,因此,本步中不需要再重新measure(),而只需要调 用 childgetMeasureHeightO获取线程的高度即可。然后对原来的高度增加一个share,share有可能是负值也有可能是正值,修改高度后,再根据这个新高度重新生成一个measureSpec,并 以 该 measureSpec要求child再次进行measure。从该逻辑可以看出,View中的weight属性并不见得一定是占用剩余空间。
for()循环执行完毕后,如果那些height为 0、weight>0的视图配合,会给各自增加或者减少share
指定的高度,但它们也可能不配合。此时,如果子视图们的高度和还超过LinearLayout所能提供的高度, 那么LinearLayout也无能无力了。最后,重新调用resolveSize()获取高度,然后调用setMeasuredDimensio()设置该LinearLayout本身所占用的布局高度。
至此,LinearLayout的 measure过程就结束了。