[源码阅读]RelativeLayout#onMeasure

源码版本:android29。

首先是入口:

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mDirtyHierarchy) {
            mDirtyHierarchy = false;
            sortChildren();
        }

如果脏了就排列儿子。什么时候会脏呢,唯一在requestLayout方法写入会脏。

排列儿子是什么操作呢:

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

首先在横竖方向,各有一个已排好序的View数组,mSortedVerticalChildren和mSortedHorizontalChildren。如果儿子数量和已排序数组不一致则重新创建儿子数组。

通过DependencyGraph#add方法,把所有儿子放到DependencyGraph中。

DependencyGraph翻译下就是依赖图表,其内部有一个全节点数组、一个以ViewId为key的SparseArray、一个临时根节点队列(用来构建图表的根列表):

    private static class DependencyGraph {
        /**
         * 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>();

看下Node的实现:

        /**
         * A node in the dependency graph. A node is a view, its list of dependencies
         * and its list of dependents.
         *
         * 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>();

Note包装了一个View,还有它依赖的SparseArray及依赖它的ArrayMap。

Node的创建使用以下方法,其中使用了一个静态变量sPool:

            static Node acquire(View view) {
                Node node = sPool.acquire();
                if (node == null) {
                    node = new Node();
                }
                node.view = view;
                return 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);

这个池子是静态的,跨Activity实例使用的,所以给了一个100的size。

Node还有一个释放的方法,先忽略。Node没有其他方法了,其成员变量直接被其他类使用。

回看sortChildren所调用的sortChildren#getSortedViews方法:

        /**
         * 创建一个排好序的视图列表. 排序由view间的依赖关系决定。
         * 比如,B需要A先处理,A需要C先处理,则依赖图谱变为B -> A -> C。
         * 排序数组将会按B, A 和 C 的顺序包含视图.
         *
         * @param sorted 已排好序的视图列表,大小必须和Layout的儿子数一致
         * @param rules 规则列表。横竖都有与之方向相关的属性,是这些属性组成的列表。
         */
        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);
                    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方法,我们先看findRoots方法干了啥:

        /**
         * 找到图表根. 根是没有依赖的节点,并且有[0..n]个依赖者。
         *
         * @param rulesFilter 规则列表
         *
         * @return 根节点列表
         */
        private ArrayDeque<Node> findRoots(int[] rulesFilter) {
            final SparseArray<Node> keyNodes = mKeyNodes;
            final ArrayList<Node> nodes = mNodes;
            final int count = nodes.size();

            // 查找节点可能被调用N次,所以要确保执行前先清除依赖及依赖者
            for (int i = 0; i < count; i++) {
                final Node node = nodes.get(i);
                node.dependents.clear();
                node.dependencies.clear();
            }

            // 为图表的每个节点重建依赖项
            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;

                // 只在参数中查找规则, 通过这种方式,我们只构建指定规则的依赖
                for (int j = 0; j < rulesCount; j++) {
                    final int rule = rules[rulesFilter[j]];
                    if (rule > 0 || ResourceId.isValid(rule)) {
                        // 这个节点依赖的节点
                        final Node dependency = keyNodes.get(rule);
                        // 跳过无效及自己
                        if (dependency == null || dependency == node) {
                            continue;
                        }
                        // 把自己当做一个依赖者
                        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;
        }

首先,rulesFilter根据方向的不同,会把不同的属性列表传进来,下面是竖直和水平方向分别对应的属性数组:

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

    private static final int[] RULES_HORIZONTAL = {
            LEFT_OF, RIGHT_OF, ALIGN_LEFT, ALIGN_RIGHT, START_OF, END_OF, ALIGN_START, ALIGN_END
    };

findRoots方法先把节点的所有依赖清空,然后有个两层循环:

外存i循环遍历所有节点,把每个节点的参数放在rules数组里;

内层j循环遍历rulesFilter,把这个节点所有和rulesFilter相关的属性找出来,rule大于0,说明这个属性的参照ViewId有效,通过之前的keyNodes对象按id找到对应的View,这就是当前node所依赖的,同理,当前节点也作为依赖项的依赖者。

在把所有节点的依赖构建完成后,最后再搞一个循环把依赖项为0的节点找出来,他们就是最终的根。

findRoots方法小结:

此方法用来查找根节点,即不依赖别人的节点。这里用了keyNodes变量来提升查找速度,keyNodes是sortChildren方法中DependencyGraph#add方法添加节点时顺便创建的一个<ViewId, View>的小型缓存池。

 

我们回到getSortedView。后面的代码就比较迷了:

外层先遍历root数组,移除尾部节点并添加节点到结果数组,内层循环遍历被移除的节点的依赖者,依次解除对被移除节点的依赖,如果依赖者不依赖任何节点则把它当做一个root节点放到root数组,直到结束。

这排出来的数组顺序有点迷,感受下。。。

 

接下来是判断MeasureSpec,然后进行横向的测量,包括判断RTL支持,在measureChildHorizontal中会遍历一遍child的measure方法:

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

 

然后是纵向测量:

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

 

在横纵都测量完了之后,应该可以知道RelativeLayout的宽高和上下左右了。上图省略号部分主要做一些本视图宽高、上下左右的最终计算。

这个循环再往下,又偷懒省略了一堆代码,其中包括BaseLine的确定、子视图LayoutParams的上下左右成员变量的修正,代码省略。

 

总结:

相对布局的测量要先生成依赖图谱。

依赖图谱根据子视图所拥有的、带有依赖关系的属性来确定视图间的依赖关系。

依赖图谱生成时会生成静态的小型缓存池可供跨Activity使用。

RelativeLayout总共要对所有child执行两次测量。

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值