效果图如下:

每秒15帧(所以看起来有点卡)

思路

采用层叠布局,将连接节点的树枝(树枝层)和节点(节点层)依次绘制到界面上,注意这个顺序很重要,要保证节点会遮住树枝的线段这样就不必再对树枝的长度进行额外处理(以避免树枝遮住节点上的数字的情况)。

缩放可以使用InteractiveViewer直接实现。节点的定位和树枝线段的起始与终止位置由所有节点的信息进行计算得出。

解决的问题

在此次绘制遇到了如下问题:

面对一个数量不定的数据源(List/Map),如何进行绘制?

节点的定位和树枝线段的起始与终止位置要如何确定?

如何在数据量较大导致界面无法容纳时进行缩放?

下面依次解决:

问题一

面对一个数量不定的数据源(List/Map),如何进行绘制?

这个问题其实很简单。

首先把每个节点封装为组件,然后在初始化霍夫曼树界面时对数据源进行处理,将所有数据装载到节点里,形成一个List<Widget>,作为节点层的stackchildren,但这其实会导致另一个问题。

由于节点层本身就是另一个层叠布局,而初始的节点组件会监听鼠标的移入移出,移入时展示节点卡片。由于层叠布局会令后添加的Widget覆盖在先添加的Widget上方,所以先添加的节点的卡片在出现时会被后添加的节点遮住。

我使用暴力方案解决了这个问题,即在鼠标移入节点时把相应卡片天加到节点层的Widget的List里,由于是最后添加,所以会被显示在最上层,然后通知进行重绘(GetX),鼠标移出时直接将这个List进行removeLast()即可。

问题二

节点的定位和树枝线段的起始与终止位置要如何确定?

这其实就变成了了一个数学问题。

我们可以直接利用的数据有:窗口宽高screenWidth wwscreenHeight hh所有节点 ii 的层数level lil_i 和层内编号 nin_i ,节点间的父子关系,节点大小nodeSize ss所有叶子节点的相应信息

示意图

以节点圆心为基准(其实在绘制时是以圆的外接正方形的左上角顶点为基准,这样只是方便表示,后续在确定节点位置时加上一个常量进行偏移纠正即可)。

设层间高度为 MM ,最底层相邻叶子节点间水平距离为 NN,则节点的竖直方向坐标xi=defaultMargin+lMx_i=defaultMargin+l \cdot M,难度在于确定节点的水平坐标yiy_i

定义levellevelll 的层的中轴系数cl=2l12c_l=\dfrac{2^l-1}{2}(不取整),则节点 ii中轴偏移系数ci=niclc_i=n_i-c_l

定义节点与最底层叶子节点之间的落差 d=hl1d=h-l-1,其中 h=max{level}+1h=max\{level\}+1,则节点ii中轴偏移量Ci=ci(s+N)2dC_i=c_i \cdot (s+N) \cdot 2^d,则 yi=w2+Ciy_i=\dfrac{w}{2}+C_i

有了节点坐标的计算策略,树枝线段起始点坐标计算策略与之相同,遍历建立树枝的策略与重建霍夫曼树的思想相同。

问题三

如何在数据量较大导致界面无法容纳时进行缩放?

这个问题也很简单。

由于层叠布局在InteractiveViewer中无法进行正常缩小,所以在界面初始化时,如果节点过多则会超出屏幕绘制界限,必须在初始化时完成一次对图层的缩放,由于无法直接用Transform组件对层叠布局进行正常缩放,所以必须在计算时加入缩放系数,对节点大小、树枝粗细及相应的偏移量进行缩放,系数由ww和满二叉树情况下最底层叶子所占的总宽度的比决定。