RelativeLayout 原理浅析

转载请注明出处:http://blog.csdn.net/hjf_huangjinfu/article/details/78573779



概述

        本文基于api level 26 的代码,简单描述一下 RelativeLayout 的内部工作原理。


1、先来看一个例子

下面我们会根据源代码和这个例子,来说明一下内部工作原理。
一个简单的布局文件:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/A"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:padding="10dp"
        android:text="A"/>

    <TextView
        android:id="@+id/B"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_above="@+id/D"
        android:layout_toRightOf="@id/A"
        android:gravity="center"
        android:padding="10dp"
        android:text="B"/>

    <TextView
        android:id="@+id/C"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/B"
        android:gravity="center"
        android:padding="10dp"
        android:text="C"/>

    <TextView
        android:id="@id/D"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_marginBottom="300dp"
        android:layout_toRightOf="@id/A"
        android:gravity="center"
        android:padding="10dp"
        android:text="D"/>

</RelativeLayout>
很简单,界面如下图所示:


这里有4个控件,暂且我们叫它们为A、B、C、D。
A没有任何依赖,放置在屏幕的左上角。
D依赖于A,放置在A的右边,D还依赖于parent,与parent对齐(为了方便显示,加了margin,不影响流程分析)。
B依赖于A、D,放置在A的右边,放置在D的上边。
C依赖于B,放置在B的右边。



2、约束规则

RelativeLayout 为它内部的每一个子View都生成一个约束规则(位置关系),约束规则有两大类:
        相对规则,也就是说相对于它的兄弟View的约束规则 取值为View的id(int型),比如: 
android:layout_toRightOf="@id/A"
        绝对规则,也就是相对于其父布局的约束规则,取值范围为【true / false】比如:
android:layout_alignParentBottom="true"

这些规则统一保存在View对象的RelativeLayout.LayoutParams 对象中,RelativeLayout.LayoutParams 对象使用一个int[] mRules 数组来存储约束规则,mRules数据逻辑是这样的,每一位对应一个约束规则,现在总共有22个约束规则,所以它的长度为22。
每一位的值,有三种情况:
-1:代表着绝对规则的true。
0:代表没有设置此条约束。
大于0的数:代表着相对规则的值,也就是所依赖View的Id。

各个索引位置代表的约束含义如下所示:
public static final int LEFT_OF                  = 0;
public static final int RIGHT_OF                 = 1;
public static final int ABOVE                    = 2;
public static final int BELOW                    = 3;
public static final int ALIGN_BASELINE           = 4;
public static final int ALIGN_LEFT               = 5;
public static final int ALIGN_TOP                = 6;
public static final int ALIGN_RIGHT              = 7;
public static final int ALIGN_BOTTOM             = 8;
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;
public static final int CENTER_IN_PARENT         = 13;
public static final int CENTER_HORIZONTAL        = 14;
public static final int CENTER_VERTICAL          = 15;
public static final int START_OF                 = 16;
public static final int END_OF                   = 17;
public static final int ALIGN_START              = 18;
public static final int ALIGN_END                = 19;
public static final int ALIGN_PARENT_START       = 20;
public static final int ALIGN_PARENT_END         = 21;
除了约束规则,还有约束方向,也就是说是在哪个方向上面的约束,主要有水平方向和竖直方向。

上面的例子中,他们的约束关系是这样的:
A(Id = 2131165184):
        mRules全是0,mRules[0...21] = 0。
D(Id = 2131165189):
        mRules[1] = mRules[RIGHT_OF] =  2131165184
        mRules[12] = mRules[ALIGN_PARENT_BOTTOM] = -1。
        其他位都是0。
B(Id = 2131165186):
        mRules[1] = mRules[RIGHT_OF] =  2131165184
        mRules[2] = mRules[ABOVE] =  2131165189
        其他位都是0。
C(Id = 2131165187):
        mRules[1] = mRules[RIGHT_OF] =  2131165186
        其他位都是0。


3、工作过程

RelativeLayout给内部的所有View,分别在水平和竖直两个方向上面,按照一定的规则进行排序(后面会说这个规则),排序后的View,存储在下面这两个数组中:
private View[] mSortedHorizontalChildren;
private View[] mSortedVerticalChildren;
        RelativeLayout会分别在水平和竖直以相同的逻辑去处理这些View,这也就是为什么说在层级相同的情况下,能用LinearLayout就不用RelativeLayout,因为它要做两次处理,分别是水平和竖直方向,会影响性能。
        排序规则,我们知道,ViewGroup在摆放子View的时候,是按照某种顺序一个一个摆放的,那么这就涉及到一个先后顺序的问题。又因为是RelativeLayout内部的子View有相互依赖的关系,所以第一步就需要找出所有的没有相对规则的View,因为这些View不依赖于其他兄弟View,所以可以很明确的知道他们的位置。
        系统把这些没有相对依赖的View称为Roots,下面看一下代码:
/** 根据传进来的规则过滤器,来找到所有的没有相对规则的View
    规则过滤器,系统给了两大类,分别是 垂直过滤器 和 水平过滤器 ,过滤器中存储的是mRules值的索引。
    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
    };
    也就是说,在某一个方向上,当某一个View,它的约束规则(mRules),比如说垂直方向,RULES_VERTICAL 数组中的这些约束,在mRules中都为0,那么它就可以成为这个方向上的Root。
*/
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();
    }

    // 为每一个View构建它的依赖和被依赖关系
    for (int i = 0; i < count; i++) {
	//需要分析的View(Node对View做了包裹)
        final Node node = nodes.get(i);
	//取出View的布局参数,主要就是拿到mRules。
        final LayoutParams layoutParams = (LayoutParams) node.view.getLayoutParams();
        final int[] rules = layoutParams.mRules;
        final int rulesCount = rulesFilter.length;

	//检测mRules中,指定的规则过滤器中的规则,有没有被设置了值。
        for (int j = 0; j < rulesCount; j++) {
            final int rule = rules[rulesFilter[j]];
		//rule大于0,说明是一个View的Id,说明这个约束规则依赖于另外一个Id为rule的View。就说明该View依赖于Id为rule的View,依赖规则为rulesFilter[j]。
            if (rule > 0) {
		//找到Id为rule的View,也就是被该View依赖的View
                final Node dependency = keyNodes.get(rule);
                // 跳过未知依赖和自我依赖
                if (dependency == null || dependency == node) {
                    continue;
                }
                //记录下Id为rule的View的被依赖关系
                dependency.dependents.put(node, this);
		//记录下当前View的依赖关系
                node.dependencies.put(rule, dependency);
            }
        }
    }

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

    //找到所有相对依赖关系为空的View,他们就是Roots
    for (int i = 0; i < count; i++) {
        final Node node = nodes.get(i);
        if (node.dependencies.size() == 0) roots.addLast(node);
    }

    return roots;
}

上面的例子中,Roots分别如下:
竖直:
        A、C、D
水平:
        A


找到了Roots,再看看是如何根据这些Roots来生成水平和垂直方向的排序规则的,
/**
   生成排序后的数组,排序规则如下,如果C依赖于A,而A依赖于B,那么排序就为 B -> A -> C。
 */
void getSortedViews(View[] sorted, int... rules) {
	//查找所有Roots
    final ArrayDeque<Node> roots = findRoots(rules);
    int index = 0;

    Node node;
	//从Roots的队列尾部取出View
    while ((node = roots.pollLast()) != null) {
        final View view = node.view;
        final int key = view.getId();
        //放置到排序数组的前面
        sorted[index++] = view;
		//拿到所有依赖于该View的View
        final ArrayMap<Node, DependencyGraph> dependents = node.dependents;
        final int count = dependents.size();
        for (int i = 0; i < count; i++) {
			//拿到依赖于该View的View
            final Node dependent = dependents.keyAt(i);
            final SparseArray<Node> dependencies = dependent.dependencies;
			//移除对该View的依赖
            dependencies.remove(key);
			//如果对该View依赖的View,已经没有其他依赖关系了,那么他也就成为一个新的Roots
            if (dependencies.size() == 0) {
                roots.add(dependent);
            }
        }
    }
	//循环依赖的检测,非法的
    if (index < sorted.length) {
        throw new IllegalStateException("Circular dependencies cannot exist"
                + " in RelativeLayout");
    }
}

上述例子中,这两个数组最后的值为:

mSortedHorizontalChildren:[D、B、C、A]
mSortedVerticalChildren:[A、D、B、C]

有了这两个数组,就可以欢乐的进行 measure、layout 了。





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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值