源码版本: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执行两次测量。