Android开发学习之RelativeLayout测量流程源码阅读

背景介绍

最近想看看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()方法源码的阅读过程



猜你喜欢

转载自blog.csdn.net/qq_37475168/article/details/80252753