美文网首页Android 进阶之旅
Android 进阶学习(三) RelativeLayout测量

Android 进阶学习(三) RelativeLayout测量

作者: Tsm_2020 | 来源:发表于2020-11-09 11:21 被阅读0次

    RelativeLayout 测量过程相对于LinearLayout的测量过程会相对复杂很多,我们先来说一下这个负责的过程是如何进行的

    RelativeLayout 将各种属性分为两个类别,垂直和水平,above below 就是垂直方向的,left_of right_of就是水平方向的, 使用 DependencyGraph 保存了所有控件的依赖关系,包含这个控件相对于其他控件的位置,也有其他控件相对于这个控件的位置, 在 DependencyGraph 中有两个比较重要的 关系树,其中一个就是mRoot ,所有child 如果有没有依赖其他view布局,就可以放进mRoot中,还有一个就是类型为SparseArray mKeyNodes ,一个child如果有id表示他有很大的概率会被其他的child所依赖,使用SparseArray 可以快速的找到这种依赖,在所有依赖关系都找到后,RelativeLayout 才会从垂直和水平两个方向上分别测量和计算当前child的位置,用以完成布局

    过程分析完了,我们再去看源码,

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

    onMeasure 方法 是以一个名字为sortChildren 的方法开始的,我们先看看他里面的实现逻辑

       private void sortChildren() {
           final int count = getChildCount();/// 找到所有child
           if (mSortedVerticalChildren == null || mSortedVerticalChildren.length != count) {
               ////创建和child 的个数相同的垂直child的集合
               mSortedVerticalChildren = new View[count];
           }
    
           if (mSortedHorizontalChildren == null || mSortedHorizontalChildren.length != count) {
               ///创建和child的个数相同的水平child的集合
               mSortedHorizontalChildren = new View[count];
           }
           final DependencyGraph graph = mGraph;
           ///清空所有child的依赖关系
           graph.clear();
           ////遍历所有child ,将child的依赖关系加入到graph中, 仅仅只是child的依赖关系,
           for (int i = 0; i < count; i++) {
               graph.add(getChildAt(i));
           }
           ////整理垂直child 的关系的集合
           graph.getSortedViews(mSortedVerticalChildren, RULES_VERTICAL);
           ////整理水平child的关系的集合
           graph.getSortedViews(mSortedHorizontalChildren, RULES_HORIZONTAL);
       }
    

    在sortChildren 的方法中,我们可以看到在整理child的依赖关系的时候先将child add到了graph中,我们看看在这个add方法中都干了什么

           void add(View view) {
               final int id = view.getId();///获取id  
               final Node node = Node.acquire(view);///找到这个控件的依赖关系
    
               if (id != View.NO_ID) {
                   mKeyNodes.put(id, node);///如果这个child 有id,他将它放入到mKeyNodes中
               }
    
               mNodes.add(node);///使用mNodes 添加这个child
           }
    

    看到这里如果想要弄明白就必须要知道DependencyGraph 和 Node 这两个类到底干了什么

           static class Node {
               ///每一个Node中都有一个view,
               View view;
               /// 被依赖关系
               final ArrayMap<Node, DependencyGraph> dependents =  new ArrayMap<Node, DependencyGraph>();
               ///依赖关系
               final SparseArray<Node> dependencies = new SparseArray<Node>();
               ///创建Node  
                static Node acquire(View view) {
                   Node node = sPool.acquire();
                   if (node == null) {
                       node = new Node();
                   }
                   node.view = view;
                   return node;
               }
           }
    

    Node 中包含了child 还有当前的child 的依赖和被依赖的关系,

       private static class DependencyGraph {
           ///每一个被DependencyGraph  add 的child所生成的Node ,都会被放入mNodes中
           private ArrayList<Node> mNodes = new ArrayList<Node>();
           ///每一个有id的child所生成的Node ,都会被被放入到mKeyNodes中
           private SparseArray<Node> mKeyNodes = new SparseArray<Node>();
           ///每一个没有依赖其他child所生成的Node 都会被放入mRoots 中
           private ArrayDeque<Node> mRoots = new ArrayDeque<Node>();
    
    

    看完了上面这两个类的介绍我们再去看 DependencyGraph 的add 方法就会很容易理解了,add方法根据child生成一个Node,这个Node中包含这个child的依赖和被依赖的关系并将它放入到mNodes中,但是在这里并没有整理他的依赖关系和被依赖关系,只是保存了这个两个关系的列表,如果这个child有id就将它放入到mKeyNodes中,

    我们再来看看graph.getSortedViews(mSortedVerticalChildren, RULES_VERTICAL); 这段代码所代表的含义

           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(rules); 都干了什么,才能情况的知道他是怎么归类的

     private ArrayDeque<Node> findRoots(int[] rulesFilter) {
               final SparseArray<Node> keyNodes = mKeyNodes;
               final ArrayList<Node> nodes = mNodes;
               final int count = nodes.size();
               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;
           }
    

    其实这段代码的意思就是根据你所提供的依赖方式,找到使用你提供的方式的控件的依赖和被依赖的关系,放入到Node中,
    我们看到findRoots的入参是一个int[] 的数组,他是由 graph.getSortedViews(mSortedVerticalChildren, RULES_VERTICAL);
    graph.getSortedViews(mSortedHorizontalChildren, RULES_HORIZONTAL); 这两个方法所提供的,那么RULES_VERTICAL 和 RULES_HORIZONTAL到底是什么呢,我们看一下源码

      垂直方向的属性  例如上下
       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中还有一个属性layoutParams.mRules 这个比较难以理解,我们看看他是什么吧,截取一部分代码

       public static final int ALIGN_PARENT_LEFT        = 9;
       public static final int ALIGN_PARENT_TOP         = 10;
       public static final int ALIGN_PARENT_RIGHT       = 11;
       public static final int ALIGN_PARENT_BOTTOM      = 12;
    final int[] rules = mRules;
               final int[] initialRules = mInitialRules;
               final int N = a.getIndexCount();
               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;
                       case com.android.internal.R.styleable.RelativeLayout_Layout_layout_below:
                           rules[BELOW] = a.getResourceId(attr, 0);
                           break;
                       case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignBaseline:
                           rules[ALIGN_BASELINE] = a.getResourceId(attr, 0);
                           break;
                       case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignLeft:
                           rules[ALIGN_LEFT] = a.getResourceId(attr, 0);
                           break;
    

    layoutParams.mRules 是一个int 类型的数组,他长度是固定的,就是RelativeLayout 中所有和方向有关的属性的个数,每一个属性有一个固定的位置,这个位置上的值就是你所使用了这个属性上的id,

    根据这些信息我们再来看一下findRoots的代码,相信大家应该比较好理解了

     private ArrayDeque<Node> findRoots(int[] rulesFilter) {
               ////所有包含id 的Node 的集合
               final SparseArray<Node> keyNodes = mKeyNodes;
               //// 所有被add 到DependencyGraph 中所生成的Node 的集合
               final ArrayList<Node> nodes = mNodes;
               final int count = nodes.size();
               ///清除所有Node的依赖和被依赖关系,
               for (int i = 0; i < count; i++) {
                   final Node node = nodes.get(i);
                   node.dependents.clear();
                   node.dependencies.clear();
               }
               //遍历所有被添加进入的Node
               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;///找到他所有依赖的id
                   final int rulesCount = rulesFilter.length;
                   for (int j = 0; j < rulesCount; j++) {
                       final int rule = rules[rulesFilter[j]];///找到使用这种属性控件的id
                       if (rule > 0 || ResourceId.isValid(rule)) {
                           final Node dependency = keyNodes.get(rule);///通过id找到这个Node
                           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);///遍历所有的node ,如果他没有依赖关系,将它放入到mRoots中
                   if (node.dependencies.size() == 0) roots.addLast(node);
               }
    
               return roots;
           }
    

    看完了findRoos 我们再去看getSortedViews

           void getSortedViews(View[] sorted, int... rules) {
               final ArrayDeque<Node> roots = findRoots(rules);
               int index = 0;
               Node node;
               while ((node = roots.pollLast()) != null) {///找到roots中view
                   final View view = node.view;
                   final int key = view.getId();
                   sorted[index++] = view;将这个view先放入到sorted中在执行 index+
                   final ArrayMap<Node, DependencyGraph> dependents = node.dependents; 找到这个view的 依赖关系
                   final int count = dependents.size();
                   for (int i = 0; i < count; i++) {//遍历这个依赖 
                       final Node dependent = dependents.keyAt(i);找到所有依赖依赖他的child
                       final SparseArray<Node> dependencies = dependent.dependencies;找到这个child 的依赖关系,
                       dependencies.remove(key);删除这个child中使用了 当前child 的依赖,
                       if (dependencies.size() == 0) {///如果这个view只使用了当前这个child作为依赖,
                           roots.add(dependent);///将它放入到roots中
                       }
                   }
               }
    
               if (index < sorted.length) {
                   throw new IllegalStateException("Circular dependencies cannot exist"
                           + " in RelativeLayout");
               }
           }
    

    先把roots当前的view加入到数组中,然后找寻唯一依赖 当前子view的 其他view,如果其他的view,有且仅依赖当前子view,那么,就把找到的这个view,加入到roots队列中,通过这样一层一层去找,直到遍历完整个roots,这样就把所有符合条件的view归类到对应的数组中了。

    到此我们就整理完所有sortChildren 的过程了,剩下的测量的过程就比较简单了

           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);
                   measureChildHorizontal(child, params, myWidth, myHeight);
    
                   if (positionChildHorizontal(child, params, myWidth, isWrapContentWidth)) {
                       offsetHorizontalAxis = true;
                   }
               }
           }
    

    测量水平方向的大小和位置

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

    测量垂直方向上的位置和大小

    我们可以看出来一个child 如果同时使用了above和left_to 两个属性,那么他就会被分别放入两个两个sortedView 的数组中,进而测量了2次,就也就是说为什么RelativeLayout 相对了LinearLayout的测量数会多不少,而且执行的复杂度更是不在一个级别上,

    相关文章

      网友评论

        本文标题:Android 进阶学习(三) RelativeLayout测量

        本文链接:https://www.haomeiwen.com/subject/psxtbktx.html