最近想看看Android中视图测量流程,先翻了一下相对布局的onMeasure()方法,发现确实比框架布局FrameLayout的复杂很多,为了防止遗忘,现在通过一篇博客以记之
我把RelativeLayout的onMeasure()划分为八个步骤,分别是:
1、对子view的相对位置依赖进行排序
2、横向测量每个子view
3、纵向测量每个子view
4、设置baseLine
5、根据当前布局宽高是否确定,修正每个子view的宽高及四个端点
6、根据当前布局重心,设置每个子view的端点
7、如果当前布局是从左往右显示,就再次对每个子view的左右端点进行调整
8、保存当前布局view的尺寸
一步一步看吧
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
/**
* 8个步骤:
*
* 1、对子view的相对位置依赖进行排序
* 2、横向测量每个子view
* 3、纵向测量每个子view
* 此时还会再进行一次每个子view的横向测量,因为在上一步的后续处理中,子view的左右端点可能会发生变化
* 4、设置baseline,最右下角的子view
* 5、根据当前布局宽度高度是否确定,来修正子view的宽高,以及四个端点
* 6、如果当前布局不是左对齐或顶对齐,就根据当前布局的重心,设置每个子view的端点
* 7、如果当前布局从右往左显示,就再次进行子view左右端点的调整
* 8、保存当前view的尺寸
*/
if (mDirtyHierarchy) {
mDirtyHierarchy = false;
sortChildren(); // 多个二重循环对子view的相对位置依赖性进行排序
}
...
}
调用了sortChildren()方法,代码如下
private void sortChildren() {
final int count = getChildCount();
if (mSortedVerticalChildren == null || mSortedVerticalChildren.length != count) {
mSortedVerticalChildren = new View[count];
}
if (mSortedHorizontalChildren == null || mSortedHorizontalChildren.length != count) {
mSortedHorizontalChildren = new View[count];
}
final DependencyGraph graph = mGraph;
graph.clear();
for (int i = 0; i < count; i++) {
graph.add(getChildAt(i));
}
graph.getSortedViews(mSortedVerticalChildren, RULES_VERTICAL);
graph.getSortedViews(mSortedHorizontalChildren, RULES_HORIZONTAL);
}
又调用了graph.getSortedViews()方法,代码如下
void getSortedViews(View[] sorted, int... rules) {
final ArrayDeque roots = findRoots(rules);
int index = 0;
Node node;
while ((node = roots.pollLast()) != null) {
final View view = node.view;
final int key = view.getId();
sorted[index++] = view; // 保存当前根节点
final ArrayMap dependents = node.dependents; // 根节点的下属
final int count = dependents.size(); // 下属数量
for (int i = 0; i < count; i++) {
final Node dependent = dependents.keyAt(i); // 第i个下属
final SparseArray dependencies = dependent.dependencies; // 下属的上级
dependencies.remove(key); // 移除当前下属和这个根节点的联系
if (dependencies.size() == 0) { // 如果当前下属这样就没有了上级(也就是说当前根节点是此下属的唯一上级)
roots.add(dependent); // 当前下属就是被遍历的下一个根节点
}
}
}
if (index < sorted.length) {
throw new IllegalStateException("Circular dependencies cannot exist"
+ " in RelativeLayout");
}
}
首先调用了findRoots()方法,获取每一个不依赖别人的view做为根节点,代码如下
private ArrayDeque findRoots(int[] rulesFilter) {
final SparseArray keyNodes = mKeyNodes;
final ArrayList nodes = mNodes;
final int count = nodes.size();
// Find roots can be invoked several times, so make sure to clear
// all dependents and dependencies before running the algorithm
for (int i = 0; i < count; i++) {
final Node node = nodes.get(i);
node.dependents.clear();
node.dependencies.clear();
}
// Builds up the dependents and dependencies for each node of the graph
for (int i = 0; i < count; i++) {
final Node node = nodes.get(i);
final LayoutParams layoutParams = (LayoutParams) node.view.getLayoutParams();
final int[] rules = layoutParams.mRules;
final int rulesCount = rulesFilter.length;
// Look only the the rules passed in parameter, this way we build only the
// dependencies for a specific set of rules
for (int j = 0; j < rulesCount; j++) {
final int rule = rules[rulesFilter[j]];
if (rule > 0) {
// 根据规则找到当前节点的上一级
final Node dependency = keyNodes.get(rule);
// 检查上一级的合法性
if (dependency == null || dependency == node) {
continue;
}
// 把本身添加到上一级的附属map中
dependency.dependents.put(node, this);
// 添加上一级
node.dependencies.put(rule, dependency);
}
}
}
final ArrayDeque roots = mRoots;
roots.clear();
// 没有上一级的节点,就做为根节点
for (int i = 0; i < count; i++) {
final Node node = nodes.get(i);
if (node.dependencies.size() == 0) roots.addLast(node);
}
return roots;
}
getSortedChildren(),顾名思义,就是给每一个view的依赖性进行横向和纵向排序,每一个排序就是一个O(n^2)的二重循环,而且排序前还要调用findRoots(),里面又是一个二重循环,可见其时间复杂度不低。因此我们要尽量少用相对布局,也不要给每个节点设置那么多的规则约束
做为一个viewGroup,相对布局的尺寸肯定要受到子view的影响,所以要先测量每一个子view
在此之前,先初始化一些变量
int myWidth = -1;
int myHeight = -1;
int width = 0;
int height = 0;
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
// Record our dimensions if they are known;
if (widthMode != MeasureSpec.UNSPECIFIED) {
myWidth = widthSize;
}
if (heightMode != MeasureSpec.UNSPECIFIED) {
myHeight = heightSize;
}
if (widthMode == MeasureSpec.EXACTLY) {
width = myWidth;
}
if (heightMode == MeasureSpec.EXACTLY) {
height = myHeight;
}
View ignore = null;
int gravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
final boolean horizontalGravity = gravity != Gravity.START && gravity != 0;
// 当前view横向不是最左边对齐
gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
final boolean verticalGravity = gravity != Gravity.TOP && gravity != 0;
// 当前view纵向不是最上边对齐
int left = Integer.MAX_VALUE;
int top = Integer.MAX_VALUE;
int right = Integer.MIN_VALUE;
int bottom = Integer.MIN_VALUE;
boolean offsetHorizontalAxis = false;
boolean offsetVerticalAxis = false;
if ((horizontalGravity || verticalGravity) && mIgnoreGravity != View.NO_ID) {
ignore = findViewById(mIgnoreGravity);
}
final boolean isWrapContentWidth = widthMode != MeasureSpec.EXACTLY;
final boolean isWrapContentHeight = heightMode != MeasureSpec.EXACTLY;
// We need to know our size for doing the correct computation of children positioning in RTL
// mode but there is no practical way to get it instead of running the code below.
// So, instead of running the code twice, we just set the width to a "default display width"
// before the computation and then, as a last pass, we will update their real position with
// an offset equals to "DEFAULT_WIDTH - width".
final int layoutDirection = getLayoutDirection();
if (isLayoutRtl() && myWidth == -1) {
myWidth = DEFAULT_WIDTH;
}
View[] views = mSortedHorizontalChildren;
int count = views.length;
然后进行遍历测量
for (int i = 0; i < count; i++) {
View child = views[i];
if (child.getVisibility() != GONE) {
LayoutParams params = (LayoutParams) child.getLayoutParams();
int[] rules = params.getRules(layoutDirection);
applyHorizontalSizeRules(params, myWidth, rules); // 根据规则确定子view布局参数的左右端点
measureChildHorizontal(child, params, myWidth, myHeight); // 横向上测量子view的尺寸
if (positionChildHorizontal(child, params, myWidth, isWrapContentWidth)) {
// center_horizontal、center_in_parent、align_parent_end返回true
offsetHorizontalAxis = true;
}
}
}
先调用了applyHorizontalSizeRules()方法,代码如下
private void applyHorizontalSizeRules(LayoutParams childParams, int myWidth, int[] rules) {
RelativeLayout.LayoutParams anchorParams; // 规则所依赖的view的参数
// VALUE_NOT_SET indicates a "soft requirement" in that direction. For example:
// left=10, right=VALUE_NOT_SET means the view must start at 10, but can go as far as it
// wants to the right
// left=VALUE_NOT_SET, right=10 means the view must end at 10, but can go as far as it
// wants to the left
// left=10, right=20 means the left and right ends are both fixed
childParams.mLeft = VALUE_NOT_SET;
childParams.mRight = VALUE_NOT_SET;
// 根据左右规则,来确定子view的左右端点坐标
/* 可以得出优先级结论:右端点 align_parent_right > align_right > to_left_of
左端点 align_parent_left > align_left > to_right_of
*/
// to_left_of
anchorParams = getRelatedViewParams(rules, LEFT_OF);
if (anchorParams != null) {
childParams.mRight = anchorParams.mLeft - (anchorParams.leftMargin +
childParams.rightMargin);
} else if (childParams.alignWithParent && rules[LEFT_OF] != 0) {
if (myWidth >= 0) {
childParams.mRight = myWidth - mPaddingRight - childParams.rightMargin;
}
}
// to_right_of
anchorParams = getRelatedViewParams(rules, RIGHT_OF);
if (anchorParams != null) {
childParams.mLeft = anchorParams.mRight + (anchorParams.rightMargin +
childParams.leftMargin);
} else if (childParams.alignWithParent && rules[RIGHT_OF] != 0) {
childParams.mLeft = mPaddingLeft + childParams.leftMargin;
}
// align_left
anchorParams = getRelatedViewParams(rules, ALIGN_LEFT);
if (anchorParams != null) {
childParams.mLeft = anchorParams.mLeft + childParams.leftMargin;
} else if (childParams.alignWithParent && rules[ALIGN_LEFT] != 0) {
childParams.mLeft = mPaddingLeft + childParams.leftMargin;
}
// align_right
anchorParams = getRelatedViewParams(rules, ALIGN_RIGHT);
if (anchorParams != null) {
childParams.mRight = anchorParams.mRight - childParams.rightMargin;
} else if (childParams.alignWithParent && rules[ALIGN_RIGHT] != 0) {
if (myWidth >= 0) {
childParams.mRight = myWidth - mPaddingRight - childParams.rightMargin;
}
}
// align_parent_left
if (0 != rules[ALIGN_PARENT_LEFT]) {
childParams.mLeft = mPaddingLeft + childParams.leftMargin;
}
// align_parent_right
if (0 != rules[ALIGN_PARENT_RIGHT]) {
if (myWidth >= 0) {
childParams.mRight = myWidth - mPaddingRight - childParams.rightMargin;
}
}
}
我就不解释也不深入了,就是把我们设置的规则落实到四个端点上
回到onMeasure(),紧接着又调用了measureChildHorizontal()方法,代码如下
private void measureChildHorizontal(
View child, LayoutParams params, int myWidth, int myHeight) {
final int childWidthMeasureSpec = getChildMeasureSpec(params.mLeft, params.mRight,
params.width, params.leftMargin, params.rightMargin, mPaddingLeft, mPaddingRight,
myWidth);
final int childHeightMeasureSpec;
if (myHeight < 0 && !mAllowBrokenMeasureSpecs) {
// 如果高度没有被指定,就根据规则确定的参数进行设置
if (params.height >= 0) {
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
params.height, MeasureSpec.EXACTLY);
} else {
// Negative values in a mySize/myWidth/myWidth value in
// RelativeLayout measurement is code for, "we got an
// unspecified mode in the RelativeLayout's measure spec."
// Carry it forward.
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
}
} else {
// 如果高度已经被指定,就无效之,赋予其最大高度
final int maxHeight;
// 根据是否有间距,计算出子view的最大高度
if (mMeasureVerticalWithPaddingMargin) {
maxHeight = Math.max(0, myHeight - mPaddingTop - mPaddingBottom
- params.topMargin - params.bottomMargin);
} else {
maxHeight = Math.max(0, myHeight);
}
final int heightMode;
if (params.height == LayoutParams.MATCH_PARENT) {
heightMode = MeasureSpec.EXACTLY;
} else {
// 从此处可知,wrap_content和指定高度在相对布局中都不一定能取到
heightMode = MeasureSpec.AT_MOST;
}
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(maxHeight, heightMode);
}
// 测量子view
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
开门调用了getChildMeasureSpec()方法,代码如下
private int getChildMeasureSpec(int childStart, int childEnd,
int childSize, int startMargin, int endMargin, int startPadding,
int endPadding, int mySize) {
// 以宽度为例
/* 传入参数:
params.mLeft, params.mRight,
params.width, params.leftMargin, params.rightMargin, mPaddingLeft,
mPaddingRight, myWidth
*/
int childSpecMode = 0;
int childSpecSize = 0;
// Negative values in a mySize value in RelativeLayout
// measurement is code for, "we got an unspecified mode in the
// RelativeLayout's measure spec."
final boolean isUnspecified = mySize < 0;
if (isUnspecified && !mAllowBrokenMeasureSpecs) {
/* 当前view宽度没有设定(<0)并且sdk>=4.2
子view的两端已经被设定:childSizeSpec = childSize(或end - start), mode = exactly
否则:childSizeSpec = 0, mode = unspecified
*/
if (childStart != VALUE_NOT_SET && childEnd != VALUE_NOT_SET) {
// Constraints fixed both edges, so child has an exact size.
// 子view的始末位置却有值
childSpecSize = Math.max(0, childEnd - childStart);
childSpecMode = MeasureSpec.EXACTLY;
} else if (childSize >= 0) {
// The child specified an exact size.
childSpecSize = childSize;
childSpecMode = MeasureSpec.EXACTLY;
} else {
// Allow the child to be whatever size it wants.
childSpecSize = 0;
childSpecMode = MeasureSpec.UNSPECIFIED;
}
return MeasureSpec.makeMeasureSpec(childSpecSize, childSpecMode);
}
// Figure out start and end bounds.
int tempStart = childStart;
int tempEnd = childEnd;
// 如果子view两端没有被设置确定值,就利用内外间距计算两端位置
if (tempStart == VALUE_NOT_SET) {
tempStart = startPadding + startMargin;
}
if (tempEnd == VALUE_NOT_SET) {
tempEnd = mySize - endPadding - endMargin;
}
// 子view最大宽度
final int maxAvailable = tempEnd - tempStart;
/*
如果子view两端有确定值,就按最大值来设置sizeSpec
否则,如果子view大小确定,就尽量按其所需设置
如果子view大小不确定,就根据其match_parent或wrap_content设置mode,大小尽量设成最大
*/
if (childStart != VALUE_NOT_SET && childEnd != VALUE_NOT_SET) {
// 如果子view的两端有确定值,就按其要求来设置sizeSpec
childSpecMode = isUnspecified ? MeasureSpec.UNSPECIFIED : MeasureSpec.EXACTLY;
childSpecSize = Math.max(0, maxAvailable);
} else {
// 否则
if (childSize >= 0) {
// 如果子view大小确定,就尽量赋予它所要求的sizeSpec
childSpecMode = MeasureSpec.EXACTLY;
if (maxAvailable >= 0) {
// We have a maximum size in this dimension.
childSpecSize = Math.min(maxAvailable, childSize);
} else {
// We can grow in this dimension.
childSpecSize = childSize;
}
} else if (childSize == LayoutParams.MATCH_PARENT) {
// 如果子view大小不确定,根据match_parent或wrap_content具体设置
// 都尽量设置成最大
childSpecMode = isUnspecified ? MeasureSpec.UNSPECIFIED : MeasureSpec.EXACTLY;
childSpecSize = Math.max(0, maxAvailable);
} else if (childSize == LayoutParams.WRAP_CONTENT) {
// Child wants to wrap content. Use AT_MOST to communicate
// available space if we know our max size.
if (maxAvailable >= 0) {
// We have a maximum size in this dimension.
childSpecMode = MeasureSpec.AT_MOST;
childSpecSize = maxAvailable;
} else {
// We can grow in this dimension. Child can be as big as it
// wants.
childSpecMode = MeasureSpec.UNSPECIFIED;
childSpecSize = 0;
}
}
}
return MeasureSpec.makeMeasureSpec(childSpecSize, childSpecMode);
}
从measureChildHorizontal()方法返回后,执行了positionChildHorizontal()方法,代码如下
private boolean positionChildHorizontal(View child, LayoutParams params, int myWidth,
boolean wrapContent) {
final int layoutDirection = getLayoutDirection();
int[] rules = params.getRules(layoutDirection);
if (params.mLeft == VALUE_NOT_SET && params.mRight != VALUE_NOT_SET) {
// 右端点确定,但左端点未知
params.mLeft = params.mRight - child.getMeasuredWidth();
} else if (params.mLeft != VALUE_NOT_SET && params.mRight == VALUE_NOT_SET) {
// 左端点确定,右端点未知
params.mRight = params.mLeft + child.getMeasuredWidth();
} else if (params.mLeft == VALUE_NOT_SET && params.mRight == VALUE_NOT_SET) {
// 左右都未知
if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_HORIZONTAL] != 0) {
// 子view设置了当前布局居中或水平居中
if (!wrapContent) {
// 当前布局不是内容包裹(也就是有了确定的尺寸值),子view就强行居中
centerHorizontal(child, params, myWidth);
} else {
// 否则,放在最左,考虑左间距
params.mLeft = mPaddingLeft + params.leftMargin;
params.mRight = params.mLeft + child.getMeasuredWidth();
}
return true;
} else {
// This is the default case. For RTL we start from the right and for LTR we start
// from the left. This will give LEFT/TOP for LTR and RIGHT/TOP for RTL.
// 没有设置那两个属性,就根据当前布局方向确定左右端点位置
if (isLayoutRtl()) {
params.mRight = myWidth - mPaddingRight- params.rightMargin;
params.mLeft = params.mRight - child.getMeasuredWidth();
} else {
params.mLeft = mPaddingLeft + params.leftMargin;
params.mRight = params.mLeft + child.getMeasuredWidth();
}
}
}
return rules[ALIGN_PARENT_END] != 0;
}
方法返回后,子view的横向测量就完成了,紧接着,进行纵向测量
话不多说,代码如下
views = mSortedVerticalChildren;
count = views.length;
final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;
for (int i = 0; i < count; i++) {
final View child = views[i];
if (child.getVisibility() != GONE) {
final LayoutParams params = (LayoutParams) child.getLayoutParams();
applyVerticalSizeRules(params, myHeight, child.getBaseline()); // 根据规则确定子view布局参数的上下端点
measureChild(child, params, myWidth, myHeight);
// 测量子view的尺寸,这里又测量了一遍子view的宽度信息(getChildMeasureSpec())
// 因为子view的左右端点可能在positionChildHorizontal()中改变
if (positionChildVertical(child, params, myHeight, isWrapContentHeight)) {
// center_in_parent、center_vertical、align_parent_bottom返回true
offsetVerticalAxis = true;
}
if (isWrapContentWidth) {
// 当前布局宽度没有固定,就根据当前布局方向,确定当前布局的宽度,要尽量设大
if (isLayoutRtl()) {
if (targetSdkVersion < Build.VERSION_CODES.KITKAT) {
width = Math.max(width, myWidth - params.mLeft);
} else {
width = Math.max(width, myWidth - params.mLeft - params.leftMargin);
}
} else {
if (targetSdkVersion < Build.VERSION_CODES.KITKAT) {
width = Math.max(width, params.mRight);
} else {
width = Math.max(width, params.mRight + params.rightMargin);
}
}
}
// 高度同理
if (isWrapContentHeight) {
if (targetSdkVersion < Build.VERSION_CODES.KITKAT) {
height = Math.max(height, params.mBottom);
} else {
height = Math.max(height, params.mBottom + params.bottomMargin);
}
}
// 一般不会是ignore,而且如果子view垂直方向上不跟当前布局顶部对齐
if (child != ignore || verticalGravity) {
// 确定子view内容左端和上端,尽量靠左上角,和本身左上角对齐
left = Math.min(left, params.mLeft - params.leftMargin);
top = Math.min(top, params.mTop - params.topMargin);
}
// 横向一样的道理
if (child != ignore || horizontalGravity) {
right = Math.max(right, params.mRight + params.rightMargin);
bottom = Math.max(bottom, params.mBottom + params.bottomMargin);
}
}
}
和横向测量类似,先调用了applyVerticalSizeRules()方法,落实规则,代码如下
private void applyVerticalSizeRules(LayoutParams childParams, int myHeight, int myBaseline) {
final int[] rules = childParams.getRules();
// Baseline alignment overrides any explicitly specified top or bottom.
int baselineOffset = getRelatedViewBaselineOffset(rules);
if (baselineOffset != -1) { // 如果当前子view有baseline,则上下端点以baseline为准
if (myBaseline != -1) {
baselineOffset -= myBaseline;
}
childParams.mTop = baselineOffset;
childParams.mBottom = VALUE_NOT_SET;
return;
}
RelativeLayout.LayoutParams anchorParams;
childParams.mTop = VALUE_NOT_SET;
childParams.mBottom = VALUE_NOT_SET;
// 根据规则确定子布局的上下端点
// 可知规则优先级:上端:align_parent_top > align_top > below
// 下端:align_parent_bottom > align_bottom > above
anchorParams = getRelatedViewParams(rules, ABOVE);
if (anchorParams != null) {
childParams.mBottom = anchorParams.mTop - (anchorParams.topMargin +
childParams.bottomMargin);
} else if (childParams.alignWithParent && rules[ABOVE] != 0) {
if (myHeight >= 0) {
childParams.mBottom = myHeight - mPaddingBottom - childParams.bottomMargin;
}
}
anchorParams = getRelatedViewParams(rules, BELOW);
if (anchorParams != null) {
childParams.mTop = anchorParams.mBottom + (anchorParams.bottomMargin +
childParams.topMargin);
} else if (childParams.alignWithParent && rules[BELOW] != 0) {
childParams.mTop = mPaddingTop + childParams.topMargin;
}
anchorParams = getRelatedViewParams(rules, ALIGN_TOP);
if (anchorParams != null) {
childParams.mTop = anchorParams.mTop + childParams.topMargin;
} else if (childParams.alignWithParent && rules[ALIGN_TOP] != 0) {
childParams.mTop = mPaddingTop + childParams.topMargin;
}
anchorParams = getRelatedViewParams(rules, ALIGN_BOTTOM);
if (anchorParams != null) {
childParams.mBottom = anchorParams.mBottom - childParams.bottomMargin;
} else if (childParams.alignWithParent && rules[ALIGN_BOTTOM] != 0) {
if (myHeight >= 0) {
childParams.mBottom = myHeight - mPaddingBottom - childParams.bottomMargin;
}
}
if (0 != rules[ALIGN_PARENT_TOP]) {
childParams.mTop = mPaddingTop + childParams.topMargin;
}
if (0 != rules[ALIGN_PARENT_BOTTOM]) {
if (myHeight >= 0) {
childParams.mBottom = myHeight - mPaddingBottom - childParams.bottomMargin;
}
}
}
唯一跟水平方向有区别的,就是要判断一下子view的baseline
返回后,调用measureChild()方法
private void measureChild(View child, LayoutParams params, int myWidth, int myHeight) {
int childWidthMeasureSpec = getChildMeasureSpec(params.mLeft,
params.mRight, params.width,
params.leftMargin, params.rightMargin,
mPaddingLeft, mPaddingRight,
myWidth);
// 根据最新的横向端点位置,测量子view宽度
int childHeightMeasureSpec = getChildMeasureSpec(params.mTop,
params.mBottom, params.height,
params.topMargin, params.bottomMargin,
mPaddingTop, mPaddingBottom,
myHeight);
// 根据纵向端点位置,测量子view高度信息
//测量子view
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
除了纵向测量外,还对子view进行第二次横向测量
返回后,调用了positionChildVertical()方法,这个跟横向的类似,代码如下
private boolean positionChildVertical(View child, LayoutParams params, int myHeight,
boolean wrapContent) {
int[] rules = params.getRules();
// 跟positionChildHorizontal()一样的道理
if (params.mTop == VALUE_NOT_SET && params.mBottom != VALUE_NOT_SET) {
// Bottom is fixed, but top varies
params.mTop = params.mBottom - child.getMeasuredHeight();
} else if (params.mTop != VALUE_NOT_SET && params.mBottom == VALUE_NOT_SET) {
// Top is fixed, but bottom varies
params.mBottom = params.mTop + child.getMeasuredHeight();
} else if (params.mTop == VALUE_NOT_SET && params.mBottom == VALUE_NOT_SET) {
// Both top and bottom vary
if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_VERTICAL] != 0) {
if (!wrapContent) {
centerVertical(child, params, myHeight);
} else {
// 当前布局如果wrap_content(也就是还没有确定的值),就放到最上面
params.mTop = mPaddingTop + params.topMargin;
params.mBottom = params.mTop + child.getMeasuredHeight();
}
return true;
} else {
params.mTop = mPaddingTop + params.topMargin;
params.mBottom = params.mTop + child.getMeasuredHeight();
}
}
return rules[ALIGN_PARENT_BOTTOM] != 0;
}
然后就没有进一步方法调用了,走完循环后,再进行baseline(基准线)的设置
这一步代码如下:
//设置baseline,把最右下角而且不是gone的子view设为baseline
View baselineView = null;
LayoutParams baselineParams = null;
for (int i = 0; i < count; i++) {
final View child = views[i];
if (child.getVisibility() != GONE) {
final LayoutParams childParams = (LayoutParams) child.getLayoutParams();
if (baselineView == null || baselineParams == null
|| compareLayoutPosition(childParams, baselineParams) < 0) {
baselineView = child;
baselineParams = childParams;
}
}
}
mBaselineView = baselineView;
调用了compareLayoutPosition()方法进行位置的比较,代码如下
private int compareLayoutPosition(LayoutParams p1, LayoutParams p2) {
final int topDiff = p1.mTop - p2.mTop;
if (topDiff != 0) {
return topDiff;
}
return p1.mLeft - p2.mLeft;
}
前者view在后者view的左上角就返回true,否则返回false
这一步相对简单,而后进行下一步:根据当前view宽高确定与否,修正子view
这里横向纵向修正的思路完全一样,所以我以横向修正为了,把代码贴出来
if (isWrapContentWidth) {
// Width already has left padding in it since it was calculated by looking at
// the right of each child view
width += mPaddingRight;
if (mLayoutParams != null && mLayoutParams.width >= 0) {
width = Math.max(width, mLayoutParams.width);
}
width = Math.max(width, getSuggestedMinimumWidth());
width = resolveSize(width, widthMeasureSpec);
// 给当前view获取尽可能大的宽度,此时当前布局的宽度就已经确定了
/*
当前布局宽度类型:at_most:子view宽度 = min(自身计算的宽度,父宽度)
exactly:这种情况进不到判断里面
unspecified:自身计算的宽度
*/
if (offsetHorizontalAxis) {
// 给center_horizontal、center_in_parent、align_parent_end三种情况修正子view的左右端点
for (int i = 0; i < count; i++) {
final View child = views[i];
if (child.getVisibility() != GONE) {
final LayoutParams params = (LayoutParams) child.getLayoutParams();
final int[] rules = params.getRules(layoutDirection);
if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_HORIZONTAL] != 0) {
centerHorizontal(child, params, width); // 强行居中
} else if (rules[ALIGN_PARENT_RIGHT] != 0) {
final int childWidth = child.getMeasuredWidth();
params.mLeft = width - mPaddingRight - childWidth;
params.mRight = params.mLeft + childWidth;
}
}
}
}
}
没什么重要的方法值得展示,就进行下一步:根据重心修正子view
if (horizontalGravity || verticalGravity) {
// 当前布局不是横向左对齐或纵向顶对齐
final Rect selfBounds = mSelfBounds;
selfBounds.set(mPaddingLeft, mPaddingTop, width - mPaddingRight,
height - mPaddingBottom);
// 设置子view框架的四个端点
final Rect contentBounds = mContentBounds;
Gravity.apply(mGravity, right - left, bottom - top, selfBounds, contentBounds,
layoutDirection);
// 根据当前布局的重心,确定子view的四个端点
// 根据重心确定的端点,对子view的四个端点进行修正
final int horizontalOffset = contentBounds.left - left;
final int verticalOffset = contentBounds.top - top;
if (horizontalOffset != 0 || verticalOffset != 0) {
for (int i = 0; i < count; i++) {
final View child = views[i];
if (child.getVisibility() != GONE && child != ignore) {
final LayoutParams params = (LayoutParams) child.getLayoutParams();
if (horizontalGravity) {
params.mLeft += horizontalOffset;
params.mRight += horizontalOffset;
}
if (verticalGravity) {
params.mTop += verticalOffset;
params.mBottom += verticalOffset;
}
}
}
}
}
虽然复杂,但也没啥可说的,那就走到下一步,判断布局方向,再次修正子view
代码如下
// 如果当前布局方向从右往左,就利用父子view的宽度差,再度修正子view的左右端点
if (isLayoutRtl()) {
final int offsetWidth = myWidth - width;
for (int i = 0; i < count; i++) {
final View child = views[i];
if (child.getVisibility() != GONE) {
final LayoutParams params = (LayoutParams) child.getLayoutParams();
params.mLeft -= offsetWidth;
params.mRight -= offsetWidth;
}
}
}
然后走到最后一步,保存,完事
// 保存最终当前布局宽高
setMeasuredDimension(width, height);
可以看到,相对布局主要是操作子view的四个端点,当前布局的宽高反倒是顺便完成的任务。但每一次操作就是一次遍历,在纵向测量子view的时候,甚至对子view的横向数据进行了第二次测量,其复杂度不可谓不高。
而且在刚开始确定子view依赖关系时,两次用到了二重循环,时间复杂度进一步提高,大大影响的程序的性能
所以,我们以后,要少用复杂的相对布局,即便要用,也不要给子view设置太多的规则约束,尽量赋予确定的宽高,需要改动时,尽量通过动态的在java代码中设置间距的方式改变,这样虽然不灵活,但性能比都是wrap_content要高不少
当然,这也要具体情况具体分析,不可一概而论,只能通过经验去判断了
而后,我会在文章安卓开发学习之RelativeLayout的布局过程中记录RelativeLayout.onLayout()方法源码的阅读过程