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 了。





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章