背景介绍
最近想看看Android中视图测量流程,先翻了一下相对布局的onMeasure()方法,发现确实比框架布局FrameLayout的复杂很多,为了防止遗忘,现在通过一篇博客以记之
我把RelativeLayout的onMeasure()划分为八个步骤,分别是:
1、对子view的相对位置依赖进行排序
2、横向测量每个子view
3、纵向测量每个子view
4、设置baseLine
5、根据当前布局宽高是否确定,修正每个子view的宽高及四个端点
6、根据当前布局重心,设置每个子view的端点
7、如果当前布局是从左往右显示,就再次对每个子view的左右端点进行调整
8、保存当前布局view的尺寸
一步一步看吧
对子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<Node> 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<Node, DependencyGraph> dependents = node.dependents; // 根节点的下属 final int count = dependents.size(); // 下属数量 for (int i = 0; i < count; i++) { final Node dependent = dependents.keyAt(i); // 第i个下属 final SparseArray<Node> 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<Node> findRoots(int[] rulesFilter) { final SparseArray<Node> keyNodes = mKeyNodes; final ArrayList<Node> 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<Node> 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(),里面又是一个二重循环,可见其时间复杂度不低。因此我们要尽量少用相对布局,也不要给每个节点设置那么多的规则约束
横向测量每个子view
做为一个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的横向测量就完成了,紧接着,进行纵向测量
纵向测量每个子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
这一步代码如下:
//设置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
根据当前布局宽高是否确定,修正子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
根据重心修正子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的宽度差,再度修正子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()方法源码的阅读过程