Android 进阶学习(三) RelativeLayout测量
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的测量数会多不少,而且执行的复杂度更是不在一个级别上,