简介
非分层式树结构自动布局工具,支持横向布局(从上到下)和竖向布局(从左到右)。
什么是非分层结构?
具有不同尺寸节点的树,使得相同深度的所有节点都垂直对齐,称之为分层结构;节点可以垂直放置在彼此之间固定距离,称之为非分层结构。
通过下面的图我们可以更清楚得理解这两种结构:
本项目只提供了非分层式的实现,并且只测试了竖向布局。
原理 本项目参考 Reingold-Tilford 算法,一共分两步执行。
遍历树,在目标方向(纵向/横向)移动节点。
递归树,自底向上对同级节点布局进行移动。
在算法中有一个概念是轮廓,轮廓分为左右轮廓,是当前子树能够从左边看到的所有节点和从右边看到的所有节点。
参考文章中把左右轮廓对下一节点的引用称为线程,本项目把左右轮廓所有节点都集中在左右列表中。
节点数据结构 节点数据包含 GraphView 的节点 RootNode 以及宽高、坐标、间隔等基础数据。
节点提供包围盒极限值的获取方法,以及重叠检测和节点移动的方法。
检测重叠的时候,我们只检测新节点相对于旧节点的某一面是否有重叠,即使新节点已经超过旧节点的包围盒,但只要是对于这一面来说属于重叠情况,整体就算重叠,如图所示:
绿色是新节点,红色是旧节点,方向是左右方向,新节点理应在旧节点的右边。
对于上方的情况来说,我们可以直接看到重叠;对于下方的情况来说,虽然绿色的矩形已经越过了红色的矩形,但是对于红色矩形的右边线来说,绿色矩形已经跨越到左边去了,因此依然属于重叠。
移动节点的时候不仅要移动自己,还要移动所有子节点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 using System.Collections.Generic;using GraphViewExtension;using UnityEngine;namespace AutoLayout { public class NodeData { public RootNode data; public List<NodeData> children = new List<NodeData>(); public float x { get { return _x; } set { _x = value ; data.style.left = value + hSpace; } } private float _x; public float y { get { return _y; } set { _y = value ; data.style.top = value + vSpace; } } private float _y; public float MinX => x; public float MaxX => x + width; public float MinY => y; public float MaxY => y + height; public float width; public float height; public float hSpace; public float vSpace; public bool CheckOverlap (NodeData other ) { if (MaxX < other.MinX || MaxY < other.MinY) { return false ; } else { return true ; } } public void MoveRight (float dis ) { x += dis; foreach (var child in children) { child.MoveRight(dis); } } public void MoveBottom (float dis ) { y += dis; foreach (var child in children) { child.MoveBottom(dis); } } public float GetTotalWidth () { float minX = GetMinX(); float maxX = GetMaxX(); return maxX - minX; } public float GetTotalHeight () { float minY = GetMinY(); float maxY = GetMaxY(); return maxY - minY; } private float GetMinX () { float minX = MinX; foreach (var child in children) { minX = Mathf.Min(minX, child.GetMinX()); } return minX; } private float GetMaxX () { float maxX = MaxX; foreach (var child in children) { maxX = Mathf.Max(maxX, child.GetMaxX()); } return maxX; } private float GetMinY () { float minY = MinY; foreach (var child in children) { minY = Mathf.Min(minY, child.GetMinY()); } return minY; } private float GetMaxY () { float maxY = MaxY; foreach (var child in children) { maxY = Mathf.Max(maxY, child.GetMaxY()); } return maxY; } } }
节点线程 节点线程维护两个列表,左轮廓列表和右轮廓列表。
节点线程提供两个关键方法:
检查新节点是否需要移动。
合并轮廓。
检查新节点是否需要移动的时候,我们需要检查新节点的左轮廓是否与旧节点的右轮廓有重叠。我们需要移动的就是轮廓重叠的部分。
以竖向布局为例,左轮廓节点特指新节点,右轮廓节点特指旧节点(在竖向布局中,左轮廓可以看作是上轮廓,右轮廓可以看做下轮廓):
graph TD;
A["左右轮廓索引是否超出范围"]
B["返回移动距离"]
C["当前右轮廓节点最小X值是否小于当前左轮廓节点最大X值 并且当前左右轮廓节点有重叠"]
D["计算轮廓重叠深度"]
E["判断当前左右轮廓节点最大X值的关系"]
F["左轮廓节点进位"]
G["右轮廓节点进位"]
H["左右轮廓节点都进位"]
G["继续循环"]
A--"是"-->B
A--"否"-->C
C--"是"-->D
C--"否"-->E
D-->E
E--"两节点最大X相等"-->H
E--"右轮廓节点最大X大于左轮廓节点"-->F
E--"左轮廓节点最大X大于右轮廓节点"-->G
H-->G
F-->G
G-->A
当前右轮廓节点最小 X 值是否小于当前左轮廓节点最大 X 值的判断是为了防止两个 X 坐标投影不重叠的节点进行比较,造成布局错误。
检查移动的示例如下(横向布局案例,视觉上更直观,共有两个子树,左子树为旧节点,右子树为新节点):
检查新旧节点对应轮廓的第一个节点。
由于第一个节点两者最大 Y 值一样,所以同时进位比较。
由于旧节点的最大 Y 值比新节点的大,因此新节点进位,旧节点不变。
旧节点的最大 Y 值比新节点的小,因此旧节点进位,后面依次按照规则检测。
合并轮廓的时候,我们需要对轮廓进行裁剪。
依然以竖向布局左轮廓为例:
graph TD;
A["反向遍历新节点左轮廓"]
B["判断新节点左轮廓节点最大X值小于等于旧节点左轮廓节点总的最大值X"]
C["裁剪新节点中接下来的所有节点"]
D["剩下的节点添加到旧节点左轮廓列表"]
A-->B
B--"是"-->C
B--"否"-->A
C-->D
对于右轮廓来说也是一样的流程,只不过新旧节点交换:
graph TD;
A["反向遍历旧节点右轮廓"]
B["判断旧节点右轮廓节点最大X值小于等于新节点右轮廓节点总的最大值X"]
C["裁剪旧节点中接下来的所有节点"]
D["剩下的节点添加到新节点左轮廓列表"]
E["新节点右轮廓赋值给旧节点左轮廓"]
A-->B
B--"是"-->C
B--"否"-->A
C-->D
D-->E
为什么要裁剪接下来的所有节点?
因为剩下的所有节点都会被覆盖看不到,因此直接裁去。
为什么都是赋值给旧节点?
因为最终合并的线程属于旧节点,属于已经参与完一次计算的节点。
蓝色是旧节点,绿色是新节点,计算完后的新节点就会合并到旧节点中去。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 using System;using System.Collections.Generic;namespace AutoLayout { public class NodeThread { public List<NodeData> left = new List<NodeData>(); public List<NodeData> right = new List<NodeData>(); public AutoLayoutDirection direction; public float MaxX (List<NodeData> self, List<NodeData> other ) { float max = self.Count == 0 ? 0 : self[0 ].MaxX; foreach (var node in other) { if (node.MaxX > max) { max = node.MaxX; } } return max; } public float MaxY (List<NodeData> self, List<NodeData> other ) { float max = self.Count == 0 ? 0 : self[0 ].MaxY; foreach (var node in other) { if (node.MaxY > max) { max = node.MaxY; } } return max; } public float CheckMove (NodeThread other ) { float move = 0 ; int i = 0 ; int j = 0 ; while (i < right.Count && j < other.left.Count) { var leftNode = right[i]; var rightNode = other.left[j]; if (direction == AutoLayoutDirection.Horizontal) { if (leftNode.MinY < rightNode.MaxY && leftNode.CheckOverlap(rightNode)) { float dis = leftNode.MaxX - rightNode.x; if (dis > move) { move = dis; } } if (Math.Abs(leftNode.MaxY - rightNode.MaxY) < 0.001 ) { ++i; ++j; } else if (leftNode.MaxY > rightNode.MaxY) { ++j; } else { ++i; } } else { if (leftNode.MinX < rightNode.MaxX && leftNode.CheckOverlap(rightNode)) { float dis = leftNode.MaxY - rightNode.y; if (dis > move) { move = dis; } } if (Math.Abs(leftNode.MaxX - rightNode.MaxX) < 0.001 ) { ++i; ++j; } else if (leftNode.MaxX > rightNode.MaxX) { ++j; } else { ++i; } } } return move; } public void SetLeftRight (NodeThread other ) { if (direction == AutoLayoutDirection.Horizontal) { float maxLeft = MaxY(left, left); for (int i = other.left.Count - 1 ; i >= 0 ; i--) { var node = other.left[i]; if (node.MaxY <= maxLeft) { other.left.RemoveRange(0 , i + 1 ); break ; } } left.AddRange(other.left); float maxRight = other.MaxY(other.right, other.right); for (int i = right.Count - 1 ; i >= 0 ; i--) { var node = right[i]; if (node.MaxY <= maxRight) { right.RemoveRange(0 , i + 1 ); break ; } } other.right.AddRange(right); right = other.right; } else { float maxLeft = MaxX(left, left); for (int i = other.left.Count - 1 ; i >= 0 ; i--) { var node = other.left[i]; if (node.MaxX <= maxLeft) { other.left.RemoveRange(0 , i + 1 ); break ; } } left.AddRange(other.left); float maxRight = other.MaxX(other.right, other.right); for (int i = right.Count - 1 ; i >= 0 ; i--) { var node = right[i]; if (node.MaxX <= maxRight) { right.RemoveRange(0 , i + 1 ); break ; } } other.right.AddRange(right); right = other.right; } } } }
第一步 第 1 步很简单,只需要普通遍历就行。每一级的 x 或 y 坐标只需要增加上一级的宽度或高度就行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 private static void FirstStep (NodeData node, float dirDis ){ if (direction == AutoLayoutDirection.Horizontal) { node.y = dirDis; foreach (var child in node.children) { FirstStep(child, dirDis + node.height); } } else { node.x = dirDis; foreach (var child in node.children) { FirstStep(child, dirDis + node.width); } } }
第二步 第二步是重点,这一步负责具体的布局。
第二步总体的逻辑如下(以纵向布局为例):
graph TD;
A["是否叶子结点"]
B["设置y坐标"]
C["返回一个左线程"]
D["遍历子节点"]
subgraph AA["循环体内部"];
E["是否不存在左线程"]
F["传入当前子节点递归获取左线程"]
G["传入当前子节点递归获取右线程"]
H["左右线程检测移动距离"]
I["移动子节点"]
J["平滑分布"]
K["合并轮廓到左线程"]
end;
L["移动当前节点到合适的位置"]
M["左线程左右轮廓插入当前节点到列表头部"]
A--"是"-->B
B-->C
A--"否"-->D
D-->AA
E--"是"-->F
E--"否"-->G
G-->H
H-->I
I-->J
J-->K
AA-->L
L-->M
M-->C
平滑分布需要三个额外参数:
last,上一个子节点。
index,上一次平滑分布的起始索引。
i,当前子节点索引。
关于平滑分布的逻辑如下:
graph TD;
A["判断当前子节点和上一子节点的间距"]
B["计算中间节点的平均额外间距"]
C["中间节点移动 平均额外间距*循环索引 的距离"]
D["更新index"]
E["更新last和i"]
A--"大于0"-->B
A--"等于0"-->E
D-->E
B-->C
C-->D
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 private static NodeThread SecondStep (NodeData node ){ if (node.children.Count == 0 ) { if (direction == AutoLayoutDirection.Horizontal) { node.x = 0 ; } else { node.y = 0 ; } return new NodeThread() { direction = direction, left = new List<NodeData>() { node }, right = new List<NodeData>() { node } }; } NodeThread left = null ; int i = 0 ; int index = 0 ; NodeData last = node.children[0 ]; foreach (var child in node.children) { if (left == null ) { left = SecondStep(child); } else { var right = SecondStep(child); float moveDis = left.CheckMove(right); float d = 0 ; if (direction == AutoLayoutDirection.Horizontal) { child.MoveRight(moveDis); d = child.MinX - last.MaxX; if (d > 0 ) { float bonus = d / (i - index); for (int j = index + 1 ; j < i; j++) { node.children[j].MoveRight(bonus * (j - index)); } index = i; } } else { child.MoveBottom(moveDis); d = child.MinY - last.MaxY; if (d > 0 ) { float bonus = d / (i - index); for (int j = index + 1 ; j < i; j++) { node.children[j].MoveBottom(bonus * (j - index)); } index = i; } } left.SetLeftRight(right); } last = child; i++; } if (direction == AutoLayoutDirection.Horizontal) { node.x += (node.children[0 ].MinX + node.children[^1 ].MaxX) * 0.5f - node.width * 0.5f ; } else { node.y += (node.children[0 ].MinY + node.children[^1 ].MaxY) * 0.5f - node.height * 0.5f ; } left.left.Insert(0 , node); left.right.Insert(0 , node); return left; }
代码 节点数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 using System.Collections.Generic;using GraphViewExtension;using UnityEngine;namespace AutoLayout { public class NodeData { public RootNode data; public List<NodeData> children = new List<NodeData>(); public float x { get { return _x; } set { _x = value ; data.style.left = value + hSpace; } } private float _x; public float y { get { return _y; } set { _y = value ; data.style.top = value + vSpace; } } private float _y; public float MinX => x; public float MaxX => x + width; public float MinY => y; public float MaxY => y + height; public float width; public float height; public float hSpace; public float vSpace; public bool CheckOverlap (NodeData other ) { if (MaxX < other.MinX || MaxY < other.MinY) { return false ; } else { return true ; } } public void MoveRight (float dis ) { x += dis; foreach (var child in children) { child.MoveRight(dis); } } public void MoveBottom (float dis ) { y += dis; foreach (var child in children) { child.MoveBottom(dis); } } public float GetTotalWidth () { float minX = GetMinX(); float maxX = GetMaxX(); return maxX - minX; } public float GetTotalHeight () { float minY = GetMinY(); float maxY = GetMaxY(); return maxY - minY; } private float GetMinX () { float minX = MinX; foreach (var child in children) { minX = Mathf.Min(minX, child.GetMinX()); } return minX; } private float GetMaxX () { float maxX = MaxX; foreach (var child in children) { maxX = Mathf.Max(maxX, child.GetMaxX()); } return maxX; } private float GetMinY () { float minY = MinY; foreach (var child in children) { minY = Mathf.Min(minY, child.GetMinY()); } return minY; } private float GetMaxY () { float maxY = MaxY; foreach (var child in children) { maxY = Mathf.Max(maxY, child.GetMaxY()); } return maxY; } } }
节点线程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 using System;using System.Collections.Generic;namespace AutoLayout { public class NodeThread { public List<NodeData> left = new List<NodeData>(); public List<NodeData> right = new List<NodeData>(); public AutoLayoutDirection direction; public float MaxX (List<NodeData> self, List<NodeData> other ) { float max = self.Count == 0 ? 0 : self[0 ].MaxX; foreach (var node in other) { if (node.MaxX > max) { max = node.MaxX; } } return max; } public float MaxY (List<NodeData> self, List<NodeData> other ) { float max = self.Count == 0 ? 0 : self[0 ].MaxY; foreach (var node in other) { if (node.MaxY > max) { max = node.MaxY; } } return max; } public float CheckMove (NodeThread other ) { float move = 0 ; int i = 0 ; int j = 0 ; while (i < right.Count && j < other.left.Count) { var leftNode = right[i]; var rightNode = other.left[j]; if (direction == AutoLayoutDirection.Horizontal) { if (leftNode.MinY < rightNode.MaxY && leftNode.CheckOverlap(rightNode)) { float dis = leftNode.MaxX - rightNode.x; if (dis > move) { move = dis; } } if (Math.Abs(leftNode.MaxY - rightNode.MaxY) < 0.001 ) { ++i; ++j; } else if (leftNode.MaxY > rightNode.MaxY) { ++j; } else { ++i; } } else { if (leftNode.MinX < rightNode.MaxX && leftNode.CheckOverlap(rightNode)) { float dis = leftNode.MaxY - rightNode.y; if (dis > move) { move = dis; } } if (Math.Abs(leftNode.MaxX - rightNode.MaxX) < 0.001 ) { ++i; ++j; } else if (leftNode.MaxX > rightNode.MaxX) { ++j; } else { ++i; } } } return move; } public void SetLeftRight (NodeThread other ) { if (direction == AutoLayoutDirection.Horizontal) { float maxLeft = MaxY(left, left); for (int i = other.left.Count - 1 ; i >= 0 ; i--) { var node = other.left[i]; if (node.MaxY <= maxLeft) { other.left.RemoveRange(0 , i + 1 ); break ; } } left.AddRange(other.left); float maxRight = other.MaxY(other.right, other.right); for (int i = right.Count - 1 ; i >= 0 ; i--) { var node = right[i]; if (node.MaxY <= maxRight) { right.RemoveRange(0 , i + 1 ); break ; } } other.right.AddRange(right); right = other.right; } else { float maxLeft = MaxX(left, left); for (int i = other.left.Count - 1 ; i >= 0 ; i--) { var node = other.left[i]; if (node.MaxX <= maxLeft) { other.left.RemoveRange(0 , i + 1 ); break ; } } left.AddRange(other.left); float maxRight = other.MaxX(other.right, other.right); for (int i = right.Count - 1 ; i >= 0 ; i--) { var node = right[i]; if (node.MaxX <= maxRight) { right.RemoveRange(0 , i + 1 ); break ; } } other.right.AddRange(right); right = other.right; } } } }
自动布局工具类
枚举 1 2 3 4 5 6 7 8 namespace AutoLayout { public enum AutoLayoutDirection { Horizontal, Verticle } }
自动布局工具 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 using System.Collections.Generic;using GraphViewExtension;namespace AutoLayout { public class AutoLayoutUtils { public static float hSpace; public static float vSpace; public static AutoLayoutDirection direction = AutoLayoutDirection.Verticle; public static float Layout (RootNode data,float bonus = 0 ) { NodeData root = GenerateNode(data); FirstStep(root, 0 ); SecondStep(root); if (direction == AutoLayoutDirection.Horizontal) { root.MoveRight(bonus); return root.GetTotalWidth(); } root.MoveBottom(bonus); return root.GetTotalHeight(); } private static NodeData GenerateNode (RootNode data ) { NodeData root = new NodeData() { data = data, width = data.resolvedStyle.width + hSpace, height = data.resolvedStyle.height + vSpace, hSpace = hSpace, vSpace = vSpace }; foreach (var edge in data.GetOutput().connections) { root.children.Add(GenerateNode(edge.input.node as RootNode)); } return root; } private static void FirstStep (NodeData node, float dirDis ) { if (direction == AutoLayoutDirection.Horizontal) { node.y = dirDis; foreach (var child in node.children) { FirstStep(child, dirDis + node.height); } } else { node.x = dirDis; foreach (var child in node.children) { FirstStep(child, dirDis + node.width); } } } private static NodeThread SecondStep (NodeData node ) { if (node.children.Count == 0 ) { if (direction == AutoLayoutDirection.Horizontal) { node.x = 0 ; } else { node.y = 0 ; } return new NodeThread() { direction = direction, left = new List<NodeData>() { node }, right = new List<NodeData>() { node } }; } NodeThread left = null ; int i = 0 ; int index = 0 ; NodeData last = node.children[0 ]; foreach (var child in node.children) { if (left == null ) { left = SecondStep(child); } else { var right = SecondStep(child); float moveDis = left.CheckMove(right); float d = 0 ; if (direction == AutoLayoutDirection.Horizontal) { child.MoveRight(moveDis); d = child.MinX - last.MaxX; if (d > 0 ) { float bonus = d / (i - index); for (int j = index + 1 ; j < i; j++) { node.children[j].MoveRight(bonus * (j - index)); } index = i; } } else { child.MoveBottom(moveDis); d = child.MinY - last.MaxY; if (d > 0 ) { float bonus = d / (i - index); for (int j = index + 1 ; j < i; j++) { node.children[j].MoveBottom(bonus * (j - index)); } index = i; } } left.SetLeftRight(right); } last = child; i++; } if (direction == AutoLayoutDirection.Horizontal) { node.x += (node.children[0 ].MinX + node.children[^1 ].MaxX) * 0.5f - node.width * 0.5f ; } else { node.y += (node.children[0 ].MinY + node.children[^1 ].MaxY) * 0.5f - node.height * 0.5f ; } left.left.Insert(0 , node); left.right.Insert(0 , node); return left; } } }
项目
更新日志 更新基本内容。 新增获取整个树宽高的方法。 自动布局新增返回宽高(根据布局方向)。