mind_map_template.dart 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. import 'dart:ui';
  2. import 'package:o2_flutter/common/models/mindmap/mind_node.dart';
  3. import 'package:o2_flutter/pages/mind_map/theme/mind_map_theme.dart';
  4. ///
  5. /// 脑图结构 模版
  6. ///
  7. abstract class BaseTemplate {
  8. final String name;
  9. BaseTheme theme;
  10. BaseTemplate(this.name, this.theme);
  11. ///
  12. /// 计算画板的大小 根据内部节点元素大小来计算
  13. /// 每个模版结构不一样 计算方式不一样
  14. /// @return 画布大小
  15. ///
  16. Size canvasSize(NodePaintElement root);
  17. ///
  18. /// 计算每个需要画出来的元素在画布中的位置
  19. /// 每个模版结构不一样 位置也不一样
  20. /// @return 返回各个节点之间的连接线
  21. ///
  22. LinePaintElement? paintElementPosition(NodePaintElement root, Size canvasSize);
  23. }
  24. ///
  25. /// 默认模版
  26. /// 默认模版分 左右两边,第三级开始没有节点的背景只有一条底边
  27. ///
  28. class DefaultTemplate extends BaseTemplate {
  29. late Size rightChildrenSize;
  30. late Size leftChildrenSize;
  31. DefaultTemplate(theme) : super('default', theme);
  32. @override
  33. Size canvasSize(NodePaintElement root) {
  34. var width = root.nodeSize.width;
  35. var height = root.nodeSize.height;
  36. final len = root.children?.length;
  37. if (len == null || len == 0) {
  38. return Size(width*1.5, height*1.5);
  39. }
  40. var rightLen = 0;
  41. if (len > 2) {
  42. if (len % 2 == 0) {
  43. rightLen = len ~/ 2;
  44. } else {
  45. rightLen = len ~/ 2 + 1;
  46. }
  47. }else {
  48. rightLen = 1;
  49. }
  50. var rightList = root.children?.sublist(0, rightLen) ?? [];
  51. var leftList = len-rightLen>0 ? root.children?.sublist(rightLen, len) ?? [] : <NodePaintElement>[];
  52. // 右边子节点
  53. var rightWidth = 0.0;
  54. var rightHeight = 0.0;
  55. if(rightList.isNotEmpty) {
  56. for (var right in rightList) {
  57. var childSize = childNodeSize(
  58. right, theme.nodeVerticalSpace, theme.nodeHorizontalSpace);
  59. rightWidth =
  60. rightWidth > childSize.width ? rightWidth : childSize.width;
  61. rightHeight += childSize.height + theme.nodeHorizontalSpace;
  62. }
  63. rightHeight -= theme.nodeHorizontalSpace;
  64. }
  65. rightChildrenSize = Size(rightWidth, rightHeight);
  66. // 左边子节点
  67. var leftWidth = 0.0;
  68. var leftHeight = 0.0;
  69. if(leftList.isNotEmpty) {
  70. for (var left in leftList) {
  71. var childSize = childNodeSize(
  72. left, theme.nodeVerticalSpace, theme.nodeHorizontalSpace);
  73. leftWidth = leftWidth > childSize.width ? leftWidth : childSize.width;
  74. leftHeight += childSize.height + theme.nodeHorizontalSpace;
  75. }
  76. leftHeight -= theme.nodeHorizontalSpace;
  77. }
  78. leftChildrenSize = Size(leftWidth, leftHeight);
  79. ///
  80. /// root节点本身的宽+2个间隔+left和right最大的宽度*2
  81. /// root节点本身的高 和 left、right最大的高度*2
  82. ///
  83. width += (rightWidth > leftWidth ? rightWidth : leftWidth) * 2 + theme.nodeHorizontalSpace * 2;
  84. height += (rightHeight > leftHeight ? rightHeight : leftHeight) * 2;
  85. return Size(width*1.5, height*1.5);
  86. }
  87. ///
  88. /// 递归计算子节点的大小
  89. ///
  90. Size childNodeSize(NodePaintElement node, double verticalSpace, double horizontalSpace) {
  91. var width = node.nodeSize.width;
  92. var height = node.nodeSize.height;
  93. // 模版中第三级开始 有一条底部边线
  94. if (node.level>1) {
  95. height += theme.lineWidth;
  96. }
  97. var childrenWidth = 0.0;
  98. var childrenHeight = 0.0;
  99. if(node.children != null && node.children?.isNotEmpty == true) {
  100. for(var child in node.children!) {
  101. var childSize = childNodeSize(child, verticalSpace, horizontalSpace);
  102. childrenWidth = childrenWidth > childSize.width ? childrenWidth:childSize.width;
  103. childrenHeight += childSize.height + horizontalSpace;
  104. }
  105. childrenHeight -= horizontalSpace;
  106. }
  107. final childSize = Size(childrenWidth, childrenHeight);
  108. node.childrenSize = childSize;
  109. width += childrenWidth + verticalSpace;
  110. height = height>childrenHeight ? height : childrenHeight;
  111. return Size(width, height);
  112. }
  113. @override
  114. LinePaintElement? paintElementPosition(NodePaintElement root, Size canvasSize) {
  115. final centerPoint = Offset(canvasSize.width/2, canvasSize.height/2);
  116. //确定root的offset
  117. final rootTop = centerPoint.dy-root.nodeSize.height/2;
  118. final rootLeft = centerPoint.dx-root.nodeSize.width/2;
  119. root.offset = Offset(rootLeft, rootTop);
  120. nodeElementsPosition(rootTop, rootLeft, root);
  121. final len = root.children?.length;
  122. if (len == null || len == 0) {
  123. return null;
  124. }
  125. List<NodeConnectLine> lines = [];
  126. var rightLen = 0;
  127. if (len > 2) {
  128. if (len % 2 == 0) {
  129. rightLen = len ~/ 2;
  130. } else {
  131. rightLen = len ~/ 2 + 1;
  132. }
  133. }else {
  134. rightLen = 1;
  135. }
  136. final rightList = root.children?.sublist(0, rightLen) ?? [];
  137. final leftList = len-rightLen>0 ? root.children?.sublist(rightLen, len) ?? [] : <NodePaintElement>[];
  138. if(rightList.isNotEmpty) {
  139. final rightCenterPoint = Offset(centerPoint.dx + root.nodeSize.width/2, centerPoint.dy);
  140. rightNodePosition(rightList, rightChildrenSize, lines, rightCenterPoint, rightCenterPoint);
  141. }
  142. if(leftList.isNotEmpty) {
  143. final leftCenterPoint = Offset(centerPoint.dx - root.nodeSize.width/2, centerPoint.dy);
  144. leftNodePosition(leftList, leftChildrenSize, lines, leftCenterPoint, leftCenterPoint);
  145. }
  146. return LinePaintElement(lines, PaintStyle(color: theme.lineColor, strokeWidth: theme.lineWidth, style: PaintingStyle.stroke));
  147. }
  148. ///
  149. /// rootRightStart root节点的右边中心点 也是连接右边子节点的线条起点
  150. ///
  151. void rightNodePosition(List<NodePaintElement> rightList, Size allListSize, List<NodeConnectLine> lines, Offset parentRightCenter, Offset parentLineStart) {
  152. final firstNode = rightList[0];
  153. final firstNodeAndChildrenRegionHeight = firstNode.nodeSize.height > (firstNode.childrenSize?.height ?? 0) ?
  154. firstNode.nodeSize.height : (firstNode.childrenSize?.height ?? 0);
  155. final lastNode = rightList[rightList.length - 1];
  156. final lastNodeAndChildrenRegionHeight = lastNode.nodeSize.height > (lastNode.childrenSize?.height ?? 0) ?
  157. lastNode.nodeSize.height : (lastNode.childrenSize?.height?? 0);
  158. // 第一个子节点的中心点Y轴
  159. final centerGap = (allListSize.height - (firstNodeAndChildrenRegionHeight/2 + lastNodeAndChildrenRegionHeight/2))/2;
  160. final firstNodeCenterY = parentRightCenter.dy - (centerGap>0 ? centerGap : 0);
  161. final nodeLeft = parentRightCenter.dx + theme.nodeVerticalSpace;
  162. var nodeTop = 0.0; // 每个节点的top
  163. var y = 0.0; // node中心y
  164. for (var i=0;i<rightList.length;i++) {
  165. final rightNode = rightList[i];
  166. if (i == 0) {
  167. y = firstNodeCenterY;
  168. }else {
  169. final preNodeHeight = (rightList[i-1].childrenSize?.height ?? 0) > rightList[i-1].nodeSize.height ? (rightList[i-1].childrenSize?.height ?? 0) : rightList[i-1].nodeSize.height;
  170. final nodeHeight = (rightNode.childrenSize?.height ?? 0) > rightNode.nodeSize.height ? (rightNode.childrenSize?.height ?? 0) : rightNode.nodeSize.height;
  171. y = y + preNodeHeight /2 + nodeHeight/2 + theme.nodeHorizontalSpace;
  172. }
  173. nodeTop = y - rightNode.nodeSize.height/2;
  174. rightNode.offset = Offset(nodeLeft, nodeTop);
  175. nodeElementsPosition(nodeTop, nodeLeft, rightNode);
  176. final lineEndTop = rightNode.level>1 ? y + rightNode.nodeSize.height/2 + theme.lineWidth : y;
  177. // 第三级开始有底边
  178. if (rightNode.level>1) {
  179. lines.add(NodeConnectLine( Offset(nodeLeft, lineEndTop), Offset(nodeLeft + rightNode.nodeSize.width, lineEndTop)
  180. ));
  181. }
  182. lines.add(NodeConnectLine( parentLineStart, Offset(nodeLeft, lineEndTop)));
  183. if (rightNode.children!=null && rightNode.children?.isNotEmpty == true) {
  184. rightNodePosition(
  185. rightNode.children!,
  186. rightNode.childrenSize ?? const Size(0, 0),
  187. lines,
  188. Offset(nodeLeft+rightNode.nodeSize.width, y),
  189. Offset(nodeLeft+rightNode.nodeSize.width, lineEndTop));
  190. }
  191. }
  192. }
  193. void leftNodePosition(List<NodePaintElement> leftList, Size allListSize, List<NodeConnectLine> lines, Offset parentLeftCenter, Offset parentLineStart) {
  194. final firstNode = leftList[0];
  195. final firstNodeAndChildrenRegionHeight = firstNode.nodeSize.height > (firstNode.childrenSize?.height ?? 0) ?
  196. firstNode.nodeSize.height : (firstNode.childrenSize?.height ?? 0) ;
  197. final lastNode = leftList[leftList.length - 1];
  198. final lastNodeAndChildrenRegionHeight = lastNode.nodeSize.height > (lastNode.childrenSize?.height ?? 0) ?
  199. lastNode.nodeSize.height : (lastNode.childrenSize?.height ?? 0);
  200. // 第一个子节点的中心点Y轴
  201. final centerGap = (allListSize.height - (firstNodeAndChildrenRegionHeight/2 + lastNodeAndChildrenRegionHeight/2))/2;
  202. final firstNodeCenterY = parentLeftCenter.dy - (centerGap>0 ? centerGap : 0);
  203. final nodeRight = parentLeftCenter.dx - theme.nodeVerticalSpace;
  204. var nodeTop = 0.0; // 每个节点的top
  205. var nodeLeft = 0.0;
  206. var y = 0.0; // node 中心y
  207. for (var i=0;i<leftList.length;i++) {
  208. final leftNode = leftList[i];
  209. if (i == 0) {
  210. y = firstNodeCenterY;
  211. }else {
  212. final preNodeHeight = (leftList[i-1].childrenSize?.height ?? 0) > leftList[i-1].nodeSize.height ? (leftList[i-1].childrenSize?.height ?? 0) : leftList[i-1].nodeSize.height;
  213. final nodeHeight = (leftNode.childrenSize?.height ?? 0) > leftNode.nodeSize.height ? (leftNode.childrenSize?.height ?? 0) : leftNode.nodeSize.height;
  214. y = y + preNodeHeight /2 + nodeHeight/2 + theme.nodeHorizontalSpace;
  215. }
  216. nodeTop = y - leftNode.nodeSize.height/2;
  217. nodeLeft = nodeRight - leftNode.nodeSize.width;
  218. leftNode.offset = Offset(nodeLeft, nodeTop);
  219. nodeElementsPosition(nodeTop, nodeLeft, leftNode);
  220. final lineEndTop = leftNode.level>1 ? y + leftNode.nodeSize.height/2 + theme.lineWidth : y;
  221. // 第三级开始有底边
  222. if (leftNode.level>1) {
  223. lines.add(NodeConnectLine(Offset(nodeLeft, lineEndTop), Offset(nodeRight,
  224. lineEndTop)
  225. ));
  226. }
  227. lines.add(NodeConnectLine(parentLineStart,
  228. Offset(nodeRight, lineEndTop)
  229. ));
  230. if (leftNode.children!=null && leftNode.children?.isNotEmpty == true) {
  231. leftNodePosition(
  232. leftNode.children!,
  233. leftNode.childrenSize ?? const Size(0, 0),
  234. lines,
  235. Offset(nodeLeft, y),
  236. Offset(nodeLeft, lineEndTop));
  237. }
  238. }
  239. }
  240. ///
  241. /// 计算节点内部的元素位置
  242. ///
  243. void nodeElementsPosition(double top, double left, NodePaintElement node) {
  244. Map<NodeElement, PaintElement> elements = node.paintElements;
  245. if(elements.isNotEmpty) {
  246. // 外边框
  247. if(elements.containsKey(NodeElement.border)) {
  248. var border = elements[NodeElement.border];
  249. if (border != null) {
  250. if(border is RRectPaintElement) {
  251. border.rrect = RRect.fromLTRBR(left, top, left + border.rrect.width, top + border.rrect.height, border.rrect.blRadius);
  252. }
  253. elements[NodeElement.border] = border;
  254. }
  255. }
  256. // 背景
  257. if(elements.containsKey(NodeElement.background)) {
  258. var back = elements[NodeElement.background];
  259. if (back != null) {
  260. if(back is RRectPaintElement) {
  261. var backLeft = left;
  262. var backTop = top;
  263. if(elements.containsKey(NodeElement.border)) {
  264. backLeft += theme.lineWidth;
  265. backTop += theme.lineWidth;
  266. }
  267. back.rrect = RRect.fromLTRBR(backLeft, backTop, backLeft+back.rrect.width, backTop+back.rrect.height, back.rrect.blRadius);
  268. }
  269. elements[NodeElement.background] = back;
  270. }
  271. }
  272. // 文字
  273. final text = elements[NodeElement.text] as TextPaintElement;
  274. final textWidth = text.painter.width;
  275. final textHeight = text.painter.height;
  276. var bottomWidth = textWidth;
  277. var bottomHeight = textHeight;
  278. if(elements.containsKey(NodeElement.priority)) {
  279. bottomWidth += theme.priorityIconSize + theme.elementGap;
  280. bottomHeight = bottomHeight > theme.priorityIconSize ? bottomHeight : theme.priorityIconSize;
  281. }
  282. if(elements.containsKey(NodeElement.progress)) {
  283. bottomWidth += theme.progressIconSize + theme.elementGap;
  284. bottomHeight = bottomHeight > theme.progressIconSize ? bottomHeight : theme.progressIconSize;
  285. }
  286. if(elements.containsKey(NodeElement.hyperlink)) {
  287. bottomWidth += theme.linkIconSize + theme.elementGap;
  288. bottomHeight = bottomHeight > theme.linkIconSize ? bottomHeight : theme.linkIconSize;
  289. }
  290. double leftPadding = (node.nodeSize.width - bottomWidth) / 2;
  291. double textLeft = left + leftPadding;
  292. double rectPadding = theme.nodeRectPadding;
  293. if(node.level == 0) {
  294. rectPadding = theme.rootRectPadding;
  295. }else if(node.level == 1) {
  296. rectPadding = theme.secondRectPadding;
  297. }
  298. // 优先级
  299. if(elements.containsKey(NodeElement.priority)) {
  300. var priority = elements[NodeElement.priority] as ImagePaintElement;
  301. var center = bottomHeight > theme.priorityIconSize ? (bottomHeight - theme.priorityIconSize)/2 : (theme.priorityIconSize-bottomHeight)/2;
  302. var priorityTop = top + node.nodeSize.height - rectPadding - bottomHeight + center;
  303. priority.rect = Rect.fromLTWH(left+leftPadding, priorityTop, theme.priorityIconSize, theme.priorityIconSize);
  304. elements[NodeElement.priority] = priority;
  305. textLeft += theme.priorityIconSize + theme.elementGap;
  306. }
  307. // 文字
  308. var textTopGap = bottomHeight > textHeight ? (bottomHeight - textHeight)/2 : (textHeight-bottomHeight)/2;
  309. var textTop = top + node.nodeSize.height - rectPadding - bottomHeight + textTopGap;
  310. text.offset = Offset(textLeft, textTop);
  311. elements[NodeElement.text] = text;
  312. // 进度
  313. if(elements.containsKey(NodeElement.progress)) {
  314. var progress = elements[NodeElement.progress] as ImagePaintElement;
  315. var center = bottomHeight > theme.progressIconSize ? (bottomHeight - theme.progressIconSize)/2 : (theme.progressIconSize-bottomHeight)/2;
  316. var progressTop = top + node.nodeSize.height - rectPadding - bottomHeight + center;
  317. progress.rect = Rect.fromLTWH(
  318. textLeft + textWidth + theme.elementGap,
  319. progressTop,
  320. theme.progressIconSize,
  321. theme.progressIconSize);
  322. elements[NodeElement.progress] = progress;
  323. }
  324. // 超链接
  325. if(elements.containsKey(NodeElement.hyperlink)) {
  326. var hyperlink = elements[NodeElement.hyperlink] as ImagePaintElement;
  327. var center = bottomHeight > theme.linkIconSize ? (bottomHeight - theme.linkIconSize)/2 : (theme.linkIconSize-bottomHeight)/2;
  328. var hyperlinkTop = top + node.nodeSize.height - rectPadding - bottomHeight + center;
  329. var hyperlinkLeft = elements.containsKey(NodeElement.progress) ? textLeft + textWidth + theme.progressIconSize + theme.elementGap*2 : textLeft + textWidth + theme.elementGap;
  330. hyperlink.rect = Rect.fromLTWH(
  331. hyperlinkLeft,
  332. hyperlinkTop,
  333. theme.linkIconSize,
  334. theme.linkIconSize);
  335. elements[NodeElement.hyperlink] = hyperlink;
  336. }
  337. // 图片
  338. if(elements.containsKey(NodeElement.image)) {
  339. var image = elements[NodeElement.image] as ImagePaintElement;
  340. image.rect = Rect.fromLTWH(left+(node.nodeSize.width - image.rect.width) / 2, top+rectPadding, image.rect.width, image.rect.height);
  341. elements[NodeElement.image] = image;
  342. }
  343. node.paintElements = elements;
  344. }
  345. }
  346. }