RelativeLayout源码详解

人生如逆旅,我亦是行人.

RelativeLayout布局我们平时使用频率很高,但是由于平时业务繁忙和懒一直没有仔细阅读其内部实现机制,最近接到的需求逼得我不得不去仔细阅读它!让我们一起来看看它是如何实现的吧~

RelativeLayout流程图

onMeasure处理过程

  • 1.将子View根据横向和纵向关系进行排序
//先把子View根据纵向关系和横向关系排序
if (mDirtyHierarchy) {
  mDirtyHierarchy = false;
  sortChildren();
}

/**
 * 每次调用requestLayout都会被赋值为true
 */
@Override
public void requestLayout() {
  super.requestLayout();
  mDirtyHierarchy = true;
}

 /**
  * 排序子View函数
  */
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));
  }

  //根据垂直方向的规则去排序垂直方向的子view
  graph.getSortedViews(mSortedVerticalChildren, RULES_VERTICAL);
  //根据水平方向的规则去排序水平方向的子view
  graph.getSortedViews(mSortedHorizontalChildren, RULES_HORIZONTAL);
}

依赖图DependencyGraph类和Node节点

private static class DependencyGraph {
    /**
     * List of all views in the graph.
     * 列出所有的View放在图中
     */
    private ArrayList<Node> mNodes = new ArrayList<Node>();

    /**
     * List of nodes in the graph. Each node is identified by its
     * view id (see View#getId()).
     * 图中的所有节点都会有一个特定的id
     */
    private SparseArray<Node> mKeyNodes = new SparseArray<Node>();

    /**
     * Temporary data structure used to build the list of roots
     * for this graph.
     * <p>
     * 临时数据结构用于构建此图的根列表。
     */
    private ArrayDeque<Node> mRoots = new ArrayDeque<Node>();

    /**
     * Clears the graph.
     */
    void clear() {
        final ArrayList<Node> nodes = mNodes;
        final int count = nodes.size();

        for (int i = 0; i < count; i++) {
            nodes.get(i).release();
        }
        nodes.clear();

        mKeyNodes.clear();
        mRoots.clear();
    }

    /**
     * Adds a view to the graph.
     * 将view添加进图中
     *
     * @param view The view to be added as a node to the graph.
     */
    void add(View view) {
        final int id = view.getId();
        //有图就有节点,根据view生成一个节点
        final Node node = Node.acquire(view);

        //如果当前的view有有效id则将其加入List中
        if (id != View.NO_ID) {
            mKeyNodes.put(id, node);
        }

        mNodes.add(node);
    }

    /**
     * Builds a sorted list of views. The sorting order depends on the dependencies
     * between the view. For instance, if view C needs view A to be processed first
     * and view A needs view B to be processed first, the dependency graph
     * is: B -> A -> C. The sorted array will contain views B, A and C in this order.
     * <p>
     * 创建一个有序的view列表
     *
     * @param sorted The sorted list of views. The length of this array must
     *               be equal to getChildCount().
     * @param rules  The list of rules to take into account.
     */
    void getSortedViews(View[] sorted, int... rules) {
        //首先找到不依赖别的view的view作为root节点
        final ArrayDeque<Node> roots = findRoots(rules);
        int index = 0;

        Node node;
        //读取roots下一个node,直到全部遍历完
        while ((node = roots.pollLast()) != null) {
            //取得view
            final View view = node.view;
            //取得view对应的id值
            final int key = view.getId();
            //把符合规则的view加入到sorted中
            sorted[index++] = view;


            //根据findRoots()方法分析
            //dependents里存的是依赖别人的node
            //如果A、C依赖的是B, 那么B的依赖表中存的是A、C
            final ArrayMap<Node, DependencyGraph> dependents = node.dependents;
            final int count = dependents.size();
            //编辑所有依赖自己的node
            for (int i = 0; i < count; i++) {
                final Node dependent = dependents.keyAt(i);
                //dependencies存的是被依赖node的规则和node
                //如果A依赖B和D才能确定位置,那么dependcies = B,D
                final SparseArray<Node> dependencies = dependent.dependencies;

                //移除当前node和dependencies的依赖关系
                //如果dependencies只和当前的node有依赖,那么移除之后
                //dependencies node也视为rootNode
                //如果B只被A依赖,那么移除和A的关系之后,B就不被任何Node依赖
                dependencies.remove(key);
                if (dependencies.size() == 0) {
                    roots.add(dependent);
                }
            }
        }

        //循环依赖异常报错
        if (index < sorted.length) {
            throw new IllegalStateException("Circular dependencies cannot exist"
                    + " in RelativeLayout");
        }
    }

    /**
     * Finds the roots of the graph. A root is a node with no dependency and
     * with [0..n] dependents.
     *
     * @param rulesFilter The list of rules to consider when building the
     *                    dependencies
     * @return A list of node, each being a root of the graph
     */
    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

        // 构建图的每个节点的依赖关系和依赖
        // 遍历所有node,node里存了当前的view,它所依赖的关系

        for (int i = 0; i < count; i++) {
            final Node node = nodes.get(i);

            final LayoutParams layoutParams = (LayoutParams) node.view.getLayoutParams();

            //取出当前view所有的依赖关系
            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) {
                    // The node this node depends on
                    // 该节点依赖的节点
                    final Node dependency = keyNodes.get(rule);

                    // Skip unknowns and self dependencies
                    // 忽略未知情况和自我依赖
                    if (dependency == null || dependency == node) {
                        continue;
                    }

                    // 这里一定要分清楚dependencies和dependency
                    //dependents里存的是依赖别人的node
                    //dependencies存的是被依赖node的规则和node
                    //举个例子 A View toLeftOf B View
                    // A 依赖 B
                    //dependecy.dependents.put(A,this);
                    //node.dependencies.put(rule,B);

                    // Add the current node as a dependent
                    dependency.dependents.put(node, this);
                    // Add a dependency to the current node
                    node.dependencies.put(rule, dependency);
                }
            }
        }

        final ArrayDeque<Node> roots = mRoots;
        roots.clear();

        // Finds all the roots in the graph: all nodes with no dependencies
        // 找到图中所有的根节点:所有节点无依赖
        for (int i = 0; i < count; i++) {
            final Node node = nodes.get(i);
            //如果node里存的依赖关系为0,即该view不依赖任何view
            if (node.dependencies.size() == 0) roots.addLast(node);
        }

        return roots;
    }

    /**
     * A node in the dependency graph. A node is a view, its list of dependencies
     * and its list of dependents.
     * <p>
     * 依赖图中的一个节点。 节点是一个View,其依赖关系列表及其依赖列表。
     * <p>
     * A node with no dependent is considered a root of the graph.
     * 一个节点没有依赖的话会被认为是这个视图根节点
     */
    static class Node {
        /**
         * The view representing this node in the layout.
         * 在布局中表示此节点的视图。
         */
        View view;

        /**
         * The list of dependents for this node; a dependent is a node
         * that needs this node to be processed first.
         *
         * 此节点的依赖列表; 一个依赖者是一个需要首先处理该节点的节点。
         */
        final ArrayMap<Node, DependencyGraph> dependents =
                new ArrayMap<Node, DependencyGraph>();

        /**
         * The list of dependencies for this node.
         *
         * 依赖该节点的列表。
         */
        final SparseArray<Node> dependencies = new SparseArray<Node>();

        /*
         * START POOL IMPLEMENTATION
         */
        // The pool is static, so all nodes instances are shared across
        // activities, that's why we give it a rather high limit
        private static final int POOL_LIMIT = 100;
        private static final SynchronizedPool<Node> sPool =
                new SynchronizedPool<Node>(POOL_LIMIT);

        static Node acquire(View view) {
            Node node = sPool.acquire();
            if (node == null) {
                node = new Node();
            }
            node.view = view;
            return node;
        }

        void release() {
            view = null;
            dependents.clear();
            dependencies.clear();

            sPool.release(this);
        }
        /*
         * END POOL IMPLEMENTATION
         */
    }
}

这里的dependents和dependencies有点绕,需要好好理解....

  • 2.相关变量的初始化赋值操作
    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;

    // 根据MeasureSpec去确定尺寸

    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;
    //判断是否是Gravity.START和Gravity.TOP
    //目的是确定左上角的坐标
    int gravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
    final boolean horizontalGravity = gravity != Gravity.START && gravity != 0;
    gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
    final boolean verticalGravity = gravity != Gravity.TOP && gravity != 0;

    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;

    //记录ignore的view
    if ((horizontalGravity || verticalGravity) && mIgnoreGravity != View.NO_ID) {
        ignore = findViewById(mIgnoreGravity);
    }

    //宽度和高度是否是wrap模式
    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".

    // 我们需要知道我们的大小是为了正确计算在RTL模式下的孩子定位,但没有实际的方法来获取它,而不是运行下面的代码。
    // 因此,我们只是在计算之前将宽度设置为“默认显示宽度”,而不是运行代码,而是作为最后一次通过,
    // 将以“DEFAULT_WIDTH - width”的偏移量更新其实际位置。

    final int layoutDirection = getLayoutDirection();
    if (isLayoutRtl() && myWidth == -1) {
        myWidth = DEFAULT_WIDTH;
    }
  • 3.处理水平方向的子View
//水平子View的集合
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();
    //根据方向获得子view中设置的规则
    int[] rules = params.getRules(layoutDirection);
    //根据这些左右方向的规则转化成左右坐标
    applyHorizontalSizeRules(params, myWidth, rules);
    //测量水平方向子view的尺寸
    measureChildHorizontal(child, params, myWidth, myHeight);
    //确定水平方向子view位置
    if (positionChildHorizontal(child, params, myWidth, isWrapContentWidth)) {
      offsetHorizontalAxis = true;
    }
  }
}

private void applyHorizontalSizeRules(LayoutParams childParams, int myWidth, int[] rules) {
  RelativeLayout.LayoutParams anchorParams;

  // 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

  // VALUE_NOT_SET 表示的是在方向上的"软需求"
  //例如: left = 10, right = VALUE_NOT_SET 意味着这个view必须在10的位置的开始,但是可以向右移动
  //     left=VALUE_NOT_SET, right=10 意味着这个view必须在10的位置结束,但是可以向左右移动
  //     left=10, right=20 意味着这个view的左右已经被固定

  childParams.mLeft = VALUE_NOT_SET;
  childParams.mRight = VALUE_NOT_SET;

  //取得的当前子View的LEFT_OF属性对应的View
  anchorParams = getRelatedViewParams(rules, LEFT_OF);
  if (anchorParams != null) {
    //如果设置了这个属性,那么当前子View的右坐标就是layout_toLeftOf属性对应的View的左边坐标减去对应View的左右Margin值
    childParams.mRight = anchorParams.mLeft - (anchorParams.leftMargin +
                                               childParams.rightMargin);
  } else if (childParams.alignWithParent && rules[LEFT_OF] != 0) {
    //如果alignWithParent == true 并且也 LEFT_OF属性存在
    //alignWithParent的值对应的是alignWithParentIfMissing
    //如果LEFT_OF对应的View是null或者gone alignWithParentIfMissing这个值就会起效
    //它会把RelativeLayout当做被依赖的对象
    if (myWidth >= 0) {
      //如果父容器的宽度大于等于0
      //子View的右边界就是父容器的宽度减去paddingRight值和子View的MarginRight值
      childParams.mRight = myWidth - mPaddingRight - childParams.rightMargin;
    }
  }

  //取得的当前子View的RIGHT_OF属性对应的View 处理逻辑同LEFT_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;
  }

  //取得的当前子View的ALIGN_LEFT属性对应的View 处理逻辑同LEFT_OF相似
  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;
  }

  //取得的当前子View的ALIGN_RIGHT属性对应的View 处理逻辑同LEFT_OF相似
  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;
    }
  }

  //对属性android:alignParentLeft进行处理
  if (0 != rules[ALIGN_PARENT_LEFT]) {
    //当前子View的left值为PaddingLeft和其自身的MarginLeft值相加
    childParams.mLeft = mPaddingLeft + childParams.leftMargin;
  }

  //对属性android:alignParentRighth进行处理
  if (0 != rules[ALIGN_PARENT_RIGHT]) {
    //父容器宽度大于0
    if (myWidth >= 0) {
      //当前子View的right值为父容器宽度减去PaddingRight和自身的MarginLeft
      childParams.mRight = myWidth - mPaddingRight - childParams.rightMargin;
    }
  }
}
private void measureChildHorizontal(
  View child, LayoutParams params, int myWidth, int myHeight) {
  //获得子View的宽度MeasureSpec
  final int childWidthMeasureSpec = getChildMeasureSpec(params.mLeft, params.mRight,
                                                        params.width, params.leftMargin, params.rightMargin, mPaddingLeft, mPaddingRight,
                                                        myWidth);

  final int childHeightMeasureSpec;

  //android版本为4.2及其以下时mAllowBrokenMeasureSpecs == true
  if (myHeight < 0 && !mAllowBrokenMeasureSpecs) {
    if (params.height >= 0) {
      //高度确定,直接以MeasureSpec.EXACTLY测量
      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;
    //在旧版本(4.2及其以下)的平台上有在水平测量期间计算子view的高度时不会考虑margins和padding
    if (mMeasureVerticalWithPaddingMargin) {
      maxHeight = Math.max(0, myHeight - mPaddingTop - mPaddingBottom
                           - params.topMargin - params.bottomMargin);
    } else {
      maxHeight = Math.max(0, myHeight);
    }

    final int heightMode;
    //如果子View的宽度是精确模式(MATCH_PARENT或者dimens),那么它的高度也是精确模式
    if (params.height == LayoutParams.MATCH_PARENT) {
      heightMode = MeasureSpec.EXACTLY;
    } else {
      //如果子View的宽度是AT_MOST模式,那么它的高度的也是AT_MOST模式
      heightMode = MeasureSpec.AT_MOST;
    }
    childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(maxHeight, heightMode);
  }
  //当宽和高都确定以后就可以开始测量了
  child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
/**
 * 确定水平方向的子View的位置
 *
 * @param child
 * @param params
 * @param myWidth
 * @param wrapContent
 * @return
 */
private boolean positionChildHorizontal(View child, LayoutParams params, int myWidth,
                                        boolean wrapContent) {
  //获取布局方向
  //RTL(从右向左)
  //LTR(默认情况:从左向右)
  final int layoutDirection = getLayoutDirection();
  //获取规则
  int[] rules = params.getRules(layoutDirection);

  if (params.mLeft == VALUE_NOT_SET && params.mRight != VALUE_NOT_SET) {
    // Right is fixed, but left varies
    // 左边界为无效值,右边界确定
    params.mLeft = params.mRight - child.getMeasuredWidth();
  } else if (params.mLeft != VALUE_NOT_SET && params.mRight == VALUE_NOT_SET) {
    // Left is fixed, but right varies
    // 左边界确定,右边界为无效值
    params.mRight = params.mLeft + child.getMeasuredWidth();
  } else if (params.mLeft == VALUE_NOT_SET && params.mRight == VALUE_NOT_SET) {
    // Both left and right vary
    // 左右边界均为无效值
    // 如果设置了CENTER_IN_PARENT或者CENTER_HORIZONTAL
    if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_HORIZONTAL] != 0) {
      // 如果不是wrap模式(一般就是match/dimens)
      if (!wrapContent) {
        //水平居中
        centerHorizontal(child, params, myWidth);
      } else {
        //wrap模式
        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.
      // RTL模式
      if (isLayoutRtl()) {
        params.mRight = myWidth - mPaddingRight - params.rightMargin;
        params.mLeft = params.mRight - child.getMeasuredWidth();
      } else {
        // TRL模式
        params.mLeft = mPaddingLeft + params.leftMargin;
        params.mRight = params.mLeft + child.getMeasuredWidth();
      }
    }
  }
  return rules[ALIGN_PARENT_END] != 0;
}
  • 4.处理垂直方向的子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 与measureChildHorizontal不同
    measureChild(child, params, myWidth, myHeight);
    //确定垂直方向的子View
    if (positionChildVertical(child, params, myHeight, isWrapContentHeight)) {
      offsetVerticalAxis = true;
    }

    //当width为wrapContent时做特殊处理
    if (isWrapContentWidth) {
      //判断布局是RTL还是LTR模式
      if (isLayoutRtl()) {
        //RTL 根据前面的兼容性处理 需要对4.3以后的版本进行margin处理
        if (targetSdkVersion < Build.VERSION_CODES.KITKAT) {
          width = Math.max(width, myWidth - params.mLeft);
        } else {
          width = Math.max(width, myWidth - params.mLeft - params.leftMargin);
        }
      } else {
        //LTR
        if (targetSdkVersion < Build.VERSION_CODES.KITKAT) {
          width = Math.max(width, params.mRight);
        } else {
          width = Math.max(width, params.mRight + params.rightMargin);
        }
      }
    }

    //当height为wrapContent时做特殊处理
    if (isWrapContentHeight) {
      //根据前面的兼容性处理 需要对4.3以后的版本进行margin处理
      if (targetSdkVersion < Build.VERSION_CODES.KITKAT) {
        height = Math.max(height, params.mBottom);
      } else {
        height = Math.max(height, params.mBottom + params.bottomMargin);
      }
    }

    //左上边界值计算
    if (child != ignore || verticalGravity) {
      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);
    }
  }
}

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) {
    if (myBaseline != -1) {
      baselineOffset -= myBaseline;
    }
    childParams.mTop = baselineOffset;
    childParams.mBottom = VALUE_NOT_SET;
    return;
  }

  // 基准线的偏移量 == -1的情况
  RelativeLayout.LayoutParams anchorParams;

  //默认值
  childParams.mTop = VALUE_NOT_SET;
  childParams.mBottom = VALUE_NOT_SET;

  //ABOVE属性对应的View的布局参数
  anchorParams = getRelatedViewParams(rules, ABOVE);
  if (anchorParams != null) {
    //当前子View的布局底部边界为ABOVE对应的顶部减去上下margin之和
    childParams.mBottom = anchorParams.mTop - (anchorParams.topMargin +
                                               childParams.bottomMargin);
  } else if (childParams.alignWithParent && rules[ABOVE] != 0) {
    //如果alignWithParent == true 并且也 ABOVE属性存在
    //alignWithParent的值对应的是alignWithParentIfMissing
    //如果ABOVE对应的View是null或者gone alignWithParentIfMissing这个值就会起效
    //它会把RelativeLayout当做被依赖的对象
    if (myHeight >= 0) {
      //如果父容器的宽度大于等于0
      //子View的右边界就是父容器的宽度减去paddingRight值和子View的MarginBottom值
      childParams.mBottom = myHeight - mPaddingBottom - childParams.bottomMargin;
    }
  }

  //BELOW属性对应的View的布局参数 处理逻辑同ABOVE相似
  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;
  }

  //ALIGN_TOP属性对应的View的布局参数 处理逻辑同ABOVE相似
  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;
  }

  //ALIGN_BOTTOM属性对应的View的布局参数 处理逻辑同ABOVE相似
  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;
    }
  }

  //alignParentTop属性
  if (0 != rules[ALIGN_PARENT_TOP]) {
    childParams.mTop = mPaddingTop + childParams.topMargin;
  }

  //alignParentBottom属性
  if (0 != rules[ALIGN_PARENT_BOTTOM]) {
    if (myHeight >= 0) {
      childParams.mBottom = myHeight - mPaddingBottom - childParams.bottomMargin;
    }
  }
}

/**
 * Measure a child. The child should have left, top, right and bottom information
 * stored in its LayoutParams. If any of these values is VALUE_NOT_SET it means
 * that the view can extend up to the corresponding edge.
 *
 * @param child    Child to measure 需要测量的子view
 * @param params   LayoutParams associated with child 与子view关联的布局参数
 * @param myWidth  Width of the the RelativeLayout 父布局的width
 * @param myHeight Height of the RelativeLayout 父布局的height
 */
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);
  int childHeightMeasureSpec = getChildMeasureSpec(params.mTop,
                                                   params.mBottom, params.height,
                                                   params.topMargin, params.bottomMargin,
                                                   mPaddingTop, mPaddingBottom,
                                                   myHeight);
  child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

/**
 * Get a measure spec that accounts for all of the constraints on this view.
 * This includes size constraints imposed by the RelativeLayout as well as
 * the View's desired dimension.
 *
 * @param childStart   The left or top field of the child's layout params
 *                     子View布局的左侧或者顶部字段
 * @param childEnd     The right or bottom field of the child's layout params
 *                     子View布局的右侧或底部字段
 * @param childSize    The child's desired size (the width or height field of
 *                     the child's layout params)
 * @param startMargin  The left or top margin
 *                     左侧或者顶部的Margin值
 * @param endMargin    The right or bottom margin
 *                     右侧或者底部的Margin值
 * @param startPadding mPaddingLeft or mPaddingTop
 *                     左侧或者顶部的Padding值
 * @param endPadding   mPaddingRight or mPaddingBottom
 *                     右侧或者底部的Padding值
 * @param mySize       The width or height of this view (the RelativeLayout)
 *                     父布局的width或者height值
 * @return MeasureSpec for the child
 */
private int getChildMeasureSpec(int childStart, int childEnd,
                                int childSize, int startMargin, int endMargin, int startPadding,
                                int endPadding, int mySize) {
  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."
  // RelativeLayout测量中mySize值为负值是因为“在RelativeLayout测量规范中我们得到了未指定的模式”。
  final boolean isUnspecified = mySize < 0;
  // 如果父容器的宽度的小于0并且系统版本大于4.3
  if (isUnspecified && !mAllowBrokenMeasureSpecs) {
    //子View的左右边间距都不等于VALUE_NOT_SET
    if (childStart != VALUE_NOT_SET && childEnd != VALUE_NOT_SET) {
      // Constraints fixed both edges, so child has an exact size.
      // 边界确定的话 子View的size也是个精确值
      childSpecSize = Math.max(0, childEnd - childStart);
      childSpecMode = MeasureSpec.EXACTLY;
    } else if (childSize >= 0) {
      // 如果不满足第一个条件但是childSize>=0 那么子View也是精确值
      // 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);
  }

  //mySize >= 0 的情况

  // Figure out start and end bounds.
  int tempStart = childStart;
  int tempEnd = childEnd;

  // If the view did not express a layout constraint for an edge, use
  // view's margins and our padding
  // 如果没有指定边界值,设置一个默认值
  if (tempStart == VALUE_NOT_SET) {
    tempStart = startPadding + startMargin;
  }
  if (tempEnd == VALUE_NOT_SET) {
    tempEnd = mySize - endPadding - endMargin;
  }

  // Figure out maximum size available to this view
  // 指明最大可用空间
  final int maxAvailable = tempEnd - tempStart;


  //当左右边界都是确定值时 基本上已经可用确定为精确模式和大小了
  //特殊情况是isUnspecified = true && mAllowBrokenMeasureSpecs = true
  if (childStart != VALUE_NOT_SET && childEnd != VALUE_NOT_SET) {
    // Constraints fixed both edges, so child must be an exact size.
    childSpecMode = isUnspecified ? MeasureSpec.UNSPECIFIED : MeasureSpec.EXACTLY;
    childSpecSize = Math.max(0, maxAvailable);
  } else {

    if (childSize >= 0) {
      //确定大小的情况
      // Child wanted an exact size. Give as much as possible.
      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) {
      // MATCH_PARENT情况
      // Child wanted to be as big as possible. Give all available
      // space.
      childSpecMode = isUnspecified ? MeasureSpec.UNSPECIFIED : MeasureSpec.EXACTLY;
      childSpecSize = Math.max(0, maxAvailable);
    } else if (childSize == LayoutParams.WRAP_CONTENT) {
      // 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);
}
  • 5.基准线计算
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;
  • 6.对wrap_content模式进行处理
//如果width是wrap模式
if (isWrapContentWidth) {
  // Width already has left padding in it since it was calculated by looking at
  // the right of each child view
  // 宽度已经根据每个子View的右边进行了填充
  width += mPaddingRight;

  if (mLayoutParams != null && mLayoutParams.width >= 0) {
    width = Math.max(width, mLayoutParams.width);
  }

  width = Math.max(width, getSuggestedMinimumWidth());
  width = resolveSize(width, widthMeasureSpec);

  //在得到最终width之后,就对依赖RelativeLayout的子view加上偏移量
  if (offsetHorizontalAxis) {
    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);
        //如果设置了CENTER_IN_PARENT或者CENTER_HORIZONTAL属性
        if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_HORIZONTAL] != 0) {
          centerHorizontal(child, params, width);
        } else if (rules[ALIGN_PARENT_RIGHT] != 0) {
          //如果设置了ALIGN_PARENT_RIGHT属性
          final int childWidth = child.getMeasuredWidth();
          params.mLeft = width - mPaddingRight - childWidth;
          params.mRight = params.mLeft + childWidth;
        }
      }
    }
  }
}

//如果height是wrap模式 同上
if (isWrapContentHeight) {
  // Height already has top padding in it since it was calculated by looking at
  // the bottom of each child view
  height += mPaddingBottom;

  if (mLayoutParams != null && mLayoutParams.height >= 0) {
    height = Math.max(height, mLayoutParams.height);
  }

  height = Math.max(height, getSuggestedMinimumHeight());
  height = resolveSize(height, heightMeasureSpec);

  if (offsetVerticalAxis) {
    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_VERTICAL] != 0) {
          centerVertical(child, params, height);
        } else if (rules[ALIGN_PARENT_BOTTOM] != 0) {
          final int childHeight = child.getMeasuredHeight();
          params.mTop = height - mPaddingBottom - childHeight;
          params.mBottom = params.mTop + childHeight;
        }
      }
    }
  }
}
  • 7.根据gravity对布局参数修正
//根据水平和垂直方向的gravity进行布局参数修正
if (horizontalGravity || verticalGravity) {
  final Rect selfBounds = mSelfBounds;
  selfBounds.set(mPaddingLeft, mPaddingTop, width - mPaddingRight,
                 height - mPaddingBottom);

  final Rect contentBounds = mContentBounds;
  Gravity.apply(mGravity, right - left, bottom - top, selfBounds, contentBounds,
                layoutDirection);

  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;
        }
      }
    }
  }
}
  • 8.RTL模式对布局参数进行修正
//RTL模式下对布局参数进行修正
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;
    }
  }
}

onLayout过程

  • 做法其实很简单,取出每个已经measure的子View的布局参数,然后根据左上右下确定布局。
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
  //  The layout has actually already been performed and the positions
  //  cached.  Apply the cached values to the children.
  //  布局实际上已经执行,位置缓存。将缓存的值应用于孩子

  final int count = getChildCount();
  for (int i = 0; i < count; i++) {
    View child = getChildAt(i);
    if (child.getVisibility() != GONE) {
      RelativeLayout.LayoutParams st =
        (RelativeLayout.LayoutParams) child.getLayoutParams();
      child.layout(st.mLeft, st.mTop, st.mRight, st.mBottom);
    }
  }
}

onDraw过程

在源码中并没有重载方法,因为RelativeLayout起的是一个容器的作用。

站在巨人的肩膀上才能看的更高更远
参考博客:http://blog.csdn.net/wz249863091/article/details/51757069

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 198,932评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,554评论 2 375
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 145,894评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,442评论 1 268
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,347评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,899评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,325评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,980评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,196评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,163评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,085评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,826评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,389评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,501评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,753评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,171评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,616评论 2 339

推荐阅读更多精彩内容