经过上一篇精读《磁贴布局 - 功能实现》的介绍,这次我们进入性能优化环节。优化精读tile布局性能的方法有很多,比如用空间换时间,存储父子关系索引,从而快速找到目标组件。但是有一个核心的性能优化点,就是碰撞性能优化。试想一下,判断组件碰撞最简单的方法是什么?一般会遍历画布的所有组件,根据当前组件位置和目标组件位置的相对位置来判断是否发生碰撞,所以只判断单个组件发生碰撞时,时间复杂度为在)。但是tile布局的碰撞判断涉及到整个canvas,因为一个组件的移动可能会引起另一个组件的移动,形成一系列的链式布局变化,比如下面的情况:[---][][A][]↑[---][--------][B][--------][---][C][---][-------][D][------]例如B向上移动,每个组件在下降时必须独立判断。由于最终的碰撞结果难以预料,只能逐个组件判断。比如上面的例子,结果如下:[--------][B][--------][---][---][C][][---][A][][---][--------][D][------]可以看出D本来就接近C,但是因为A组件已经下移,A比C高,所以靠近D的组件由C变成了A。在C进行独立的碰撞判断之前,很难分析画布的结构,更不用说结合画布的整体尺寸缩放,网格数量变化的影响,以及组件的最终落点必须在每个组件以正确的顺序碰撞后确定。因此,瓦片碰撞的时间复杂度为O(n2)。比如页面中有100个组件,至少要遍历10000次才能完成一次布局计算。极端情况下,比如页面有1000个组件,Layout计算肯定是很耗时的。考虑网格碰撞确定方法的另一个问题。正是因为tile布局的碰撞判定,使得tile布局不可能重叠组件。因此,即使canvas上有1000个组件,只要组件宽高不是特别小(比如每个宽1px高1000px的A组件是不可能聚集在一个小区域的,但是分散在一个大的范围内。那么离当前组件太远的组件根本不需要做碰撞判断,因为它们没有可能相交。类比人的视角判断碰撞,当canvas有1000组件,我们也可以一眼看出某个组件与哪些组件相交,但这种判断来自于肉眼扫过可见区域,而不是1000个组件。检查所有组件。这表明人眼对碰撞优化:以这个组件为圆心,上下左右扩大一定范围就可以了,看是否有碰撞。因此,我们模拟人眼的思路es寻找碰撞,将画布分成若干个格子,记录每个组件所在的格子,这样判断碰撞时,我们只需要在组件所在的格子里进行判断即可。画布分为如下几个格子:[---]││││[A]││││[---]││││────────────────────────────────────────────────────────———————————————││[b]│[---]│[│-----][C]│[G]││──────────┼──────────┼────[---]┼──────────┼──────────││[E]│[F]│││[-----------]│││[]│──────────┼──────────┼────[D]──┼──────────││[]│││[----------]│││││so当确定以下组件发生碰撞时,待比较的组件如下:A:没有被比较的组件。B:比较组件C。D:比较组件E、F和G。由于一个区域承载的组件数量是固定的,所以O(n2)时间复杂度优化为O(nxP),其中P是常数每个组件,所以时间复杂度最终为O(n)。当然,这里有几点需要注意:需要用空间换取时间,即存储每个组件属于哪些区域,每个区域有哪些组件,这样拖拽的时候就不需要遍历所有组件了下降。网格的大小不宜太大。如果格子太大,划分格子的意义就不会太大,因为一个格子里面的组件还是很多的。网格的尺寸不能太小,这样每个组件可能跨越很多网格,导致网格本身的数量循环次数甚至超过组件树,成为负优化。关于网格的大小,一般tile布局都会设置colsrowHeight两个选项。将这两个选项的正整数倍的网格设置为跨度比较合适,这样会尽量减少网格的无效区域。不同场景下的网格计算我们上面提到了如何使用网格计算来处理组件碰撞。再总结一下:判断组件碰撞,只需要找到当前组件所在的网格区域,遍历每个网格区域中的组件即可。除了碰撞判断之外,还有两个场景需要在瓦片拖动过程中计算组件之间的碰撞关系,主要包括落点位置和落点后组件排序这两个场景。比如下面这个例子:
