关于RelativeLayout中onMeasure()的理解

onMeasure的大体步骤如图:


个人认为前三步是主体:

第一步:

sortChildren()

if (mDirtyHierarchy) {
            mDirtyHierarchy = false;
            sortChildren();
        }

      判断RelativeLayout的布局层次是否被打乱,即是否有子view被加入或被移除之类的操作,进而判断是否执行此方法;如果有则,执行此方法。这个方法的作用是对所有的子view进行排序,并分别将排好序的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));
        }

        graph.getSortedViews(mSortedVerticalChildren, RULES_VERTICAL);
        graph.getSortedViews(mSortedHorizontalChildren, RULES_HORIZONTAL);
    }
    mSortedVerticalChildren  与mSortedHorizontalChildren是两个数组用来存放排序好的水平与竖直方向的view,排序的顺序类似于一棵树,没有设置“连接属性(right_of等)”的view放在前面,例如B在A的右边(即B依靠A),C在B的右边(即C依靠B),则排列的顺序为A,B,C。

    graph可以理解为一个存放子view的仓库,它还封装了一些对子view的操作。其具体请看文档的末尾

    graph.getSortedViews(mSortedVerticalChildren,RULES_VERTICA);graph.getSortedViews(mSortedHorizontalChildren, RULES_HORIZONTAL);按照水平方向和竖直方向对子view进行分类排序;分别存放在上面两个数组中。其中RULES_VERTICA是一个数组,里面包含竖直方向的参数:其定义为:

private static final int[] RULES_VERTICAL = {
            ABOVE, BELOW, ALIGN_BASELINE, ALIGN_TOP, ALIGN_BOTTOM
    };
    
第二步:

    获得myWidth,myHeight

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;
        }

    myWidth,myHeight是下面测量时的依据;接下来是查看此RelativeLayout的长宽是不是wrapcontent,如果是,则用width与height使用对测量后的位置进行修正,代码如下:

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;
        }
    如注释所说:下面会进行两次计算,第一次是用myWidth计算,第二次用width去修正偏移值。layoutDirection表示的是:布局是RTL(从右到左)还是LTR(从左到右),接下来就是第三步了。

第三步:

    依据获得的水平/竖直数组进行测量;水平测量代码如下:

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);
                measureChildHorizontal(child, params, myWidth, myHeight);

                if (positionChildHorizontal(child, params, myWidth, isWrapContentWidth)) {
                    offsetHorizontalAxis = true;
                }
            }
 }

    水平测量分为3个小步完成

    1、设置view的mRight与mLeft

        applyHorizontalSizeRules(params, myWidth, rules);

    rules是一个存放此view与其他view的位置联系的数组,在layoutParam初始化时,会给rules进行赋值部分代码如下:

 for (int i = 0; i < N; i++) {
                int attr = a.getIndex(i);
                switch (attr) {
                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignWithParentIfMissing:
                        alignWithParent = a.getBoolean(attr, false);
                        break;
                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toLeftOf:
                        rules[LEFT_OF] = a.getResourceId(attr, 0);
                        break;
                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toRightOf:
                        rules[RIGHT_OF] = a.getResourceId(attr, 0);
                        break;
                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_above:
                        rules[ABOVE] = a.getResourceId(attr, 0);
                        break;

     applyHorizontalSizeRules(params, myWidth, rules);这个方法会根据锚点设置view的mleft,mright,所谓锚点就是与此view以某种位置关系相连的第一个可视view,具体的操作如下代码所示:

 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;
            }
        }
    anchorParams就是锚点的layoutParam,以此来确定当前view的mRight,mLeft的确定与此相似;可以发现是从getRelatedViewParams()得到的anchorParam,那么这个方法的具体代码为:

   private LayoutParams getRelatedViewParams(int[] rules, int relation) {
        View v = getRelatedView(rules, relation);
        if (v != null) {
            ViewGroup.LayoutParams params = v.getLayoutParams();
            if (params instanceof LayoutParams) {
                return (LayoutParams) v.getLayoutParams();
            }
        }
        return null;
    }

private View getRelatedView(int[] rules, int relation) {
        int id = rules[relation];
        if (id != 0) {
            DependencyGraph.Node node = mGraph.mKeyNodes.get(id);
            if (node == null) return null;
            View v = node.view;

            // Find the first non-GONE view up the chain
            while (v.getVisibility() == View.GONE) {
                rules = ((LayoutParams) v.getLayoutParams()).getRules(v.getLayoutDirection());
                node = mGraph.mKeyNodes.get((rules[relation]));
                if (node == null) return null;
                v = node.view;
            }

            return v;
        }

        return null;
    }
    上面的第一个方法将得到锚点view,并返回锚点viewLayoutParam,第二个方法是得到查找锚点view,找到以relation方式与当前view相连的第一个可视锚点view。这样设置mRight与mLeft的设置就结束了。要注意的一点是在设置mLeft与mRight时,会依据不同的位置关系得到多次锚点,从而进行多次设置,从而使他们的优先级不同,后面的设置会覆盖前面的设置,这与我们在XML中设置“位置属性”时的情况是相符的,即在设置一些与parent有关的属性值时,会将与此属性冲突的属性覆盖掉,使其不产生效果)。下面进行第二小步

    2、测量并保存子view的宽和高

         measureChildHorizontal(child, params, myWidth, myHeight);此方法在测量时,第一步会首先测量view的宽,然后根据传入的参数测量view的高;测量view宽的代码如下:

final int childWidthMeasureSpec = getChildMeasureSpec(params.mLeft, params.mRight,
                params.width, params.leftMargin, params.rightMargin, mPaddingLeft, mPaddingRight,
                myWidth);
/**
     * 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.
     *
     */
    private int getChildMeasureSpec(int childStart, int childEnd,
            int childSize, int startMargin, int endMargin, int startPadding,
            int endPadding, int mySize)
    宽度:这是这个方法的注释说明,它会根据子view要求的大小和此relativeLayout分配的大小来为当前的子view设置宽度。     

    至于高度:如果传入的mHeight>0,会根据传入的mHeight来设置,如果小于0,则用当前view的height来设置。最后调用子view的measure方法完成测量:

 child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    3、

        用测量好的宽度来重新设置mLeft和mRight,与此同时,判断当前的view是否与父view的结尾对齐,即是否设置了ALIGN_PARENT_RIGHT属性,如果对齐,则为true(API17以下一般返回false)

if (positionChildHorizontal(child, params, myWidth, isWrapContentWidth)) {
                    offsetHorizontalAxis = true;
                }
具体的的操作为:
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) {
            // 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) {
...............
return rules[ALIGN_PARENT_END]!=0;
    由此可以看出view的mLeft的确被重新设置了。此方法的返回值表示是否对ALIGN_PARENT_END属性进行了设置。如果设置了,则返回true。对于VALUE_NOT_SET的说明如下:

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

即如果left设置成此属性,则表示可以向左尽可能的延伸。

   对于子view竖直的测量基本与水平测量一致:

 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());
                measureChild(child, params, myWidth, myHeight);
                if (positionChildVertical(child, params, myHeight, isWrapContentHeight)) {
                    offsetVerticalAxis = true;
                }
.........
}

此处省略的是:

     1、如果RelativeLayout的width是wrapcontent的,则求出RelativeLayout的所有子view所占宽度总和(设置自身宽度的依据)

     2、如果RelativeLayout的height是wrapcontent的,则求出RelativeLayout的所有子view所占高度总和(同上)

     3、RelativeLayout中,获得在最左上角,子view的left,top和最右下角,子view的right,bottom。(如果RelativeLayout设置了Gravity,即不是从右上角开始布局,则用这四个值来设置偏移,更正子view的位置)

就此onMeasure方法的大体已经完成,下面来进行调整:

   调整一、

       获得基线view,此view是最左上角的view,代码如下:

 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;
                }
            }
        }
    调整二、

         检查RelativeLayout是否是wrapcontent,如果是,则对中心和右下view进行调整

if (isWrapContentWidth) {
......
}
if (isWrapContentHeight) {
......
}
    调整三、

        查看RelativeLayout是否设置了Gravity,如center、right、top等,如果设置了,得到偏移类型,并计算出偏移值,horizontalOffset和verticalOffset,具体代码如下:

   

 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;
                        }
                    }
                }
            }
        }
     selfBounds代表的是RelativeLayout的轮廓:

    contentBounds代表的是Gravity所表示的矩形:这个矩形携带了子view偏移量的信息,它的left,right,top,bottom等值是相对RelativeLayout的left,right,top,bottom的距离。然后就可以依据此距离来计算偏移量。具体Gravity.apply()方法的代码不在展示。

    调整四、

          如果排列是RTL(从右到左),则重新排列,一般较低的API不会有此属性,代码如下:

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;
                }
            }
        }

最后保存RelativeLayout的宽度和高度:

setMeasuredDimension(width, height);

这样,RelativeLayout的onMeasure方法就完成了。



PS:

       类DependencyGraph

       如代码所示,此类一共有3个变量,用来保存所有子view,有id的子view,无位置依赖的子view

 /**
         * List of all views in the graph.
         */
        private ArrayList<Node> mNodes = new ArrayList<Node>();

        /**
         * List of nodes in the graph. Each node is identified by its
         * view id (see View#getId()).
         */
        private SparseArray<Node> mKeyNodes = new SparseArray<Node>();

        /**
         * Temporary data structure used to build the list of roots
         * for this graph.
         */
        private ArrayDeque<Node> mRoots = new ArrayDeque<Node>();
    下面这个方法拓扑排序了所有的子view,并依据遍历的顺序得出了水平和竖直view的链表(每一次遍历将无依赖的view加入队列)。
void getSortedViews(View[] sorted, int... rules) {

final ArrayDeque<Node> roots = findRoots(rules);
......
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);
                    final SparseArray<Node> dependencies = dependent.dependencies;

                    dependencies.remove(key);
                    if (dependencies.size() == 0) {
                        roots.add(dependent);
                    }
                }
            }
........
}
    这里有一个Node类,里面两个链变量:

         1、dependents  用来保存依赖此节点(view)的节点(view)

         2、dependencies  用来保存此节点(view)依赖的节点(view)


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值