Android 进阶之旅

Android 进阶学习(三) RelativeLayout测量

2020-11-09  本文已影响0人  Tsm_2020

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的测量数会多不少,而且执行的复杂度更是不在一个级别上,

上一篇下一篇

猜你喜欢

热点阅读