利益相关:本文作者与极坐标⋅XYZ(jizuobiao.xyz)团队有关联。下面的数字是我们在 Manim Community 0.21.0(2026 年的最新版本)上跑出来的,代码都贴在文里,结论请自行验证。
成员一样大的时候,两种写法排出来的网格一模一样。差别出在成员大小不一的时候:先把每行各自arrange、再把几行上下叠起来,行与行之间互不知道对方,上下对不成列;arrange_in_grid是按整张表算的,同一列的成员中心在一条竖线上。所以要「表格」那种横竖都对齐的效果,用arrange_in_grid;每行个数不一样、或者每行要各自居中,用两层arrange。
Manim 是用 Python 代码写数学动画的开源引擎,3Blue1Brown 的视频就是它做的。它没有「表格布局」「弹性布局」这类东西,排版靠的是VGroup上的几个方法,每个方法调用一次就摆一次位置。排网格能用的就是上面这两条路。
一段代码把两种写法并排放
六个方块,边长依次是 0.5、2、0.5、1、0.5、1.5,间距都用 0.2。左边蓝色是两层arrange,右边黄色是arrange_in_grid:
from manim import * class GridVsNested(Scene): def construct(self): sides = [0.5, 2, 0.5, 1, 0.5, 1.5] def make(color): return [Square(side_length=s, color=color).set_fill(color, 0.5) for s in sides] # 左边:两行各自 arrange,再把两行上下叠起来 m = make(BLUE) row1 = VGroup(*m[:3]).arrange(RIGHT, buff=0.2) row2 = VGroup(*m[3:]).arrange(RIGHT, buff=0.2) nested = VGroup(row1, row2).arrange(DOWN, buff=0.2).move_to([-3.5, 0, 0]) # 右边:同样六个方块,交给 arrange_in_grid grid = VGroup(*make(YELLOW)).arrange_in_grid(rows=2, cols=3, buff=0.2).move_to([3.5, 0, 0]) self.add(nested, grid) self.wait()不想在本机装环境的话,把这段贴进极坐标⋅XYZ 的工作区点运行就能看,里面装的也是 Manim 0.21.0。
排出来差在哪
我们把每个方块的中心坐标打印了出来,x 以各自那一组的中心为零点:
| 两层 arrange(左) | arrange_in_grid(右) | |
|---|---|---|
| 第一行三个方块的 x | -1.45、0、1.45 | -1.95、-0.25、1.7 |
| 第二行三个方块的 x | -1.2、-0.25、0.95 | -1.95、-0.25、1.7 |
| 两行的 y | 0.85、-1.1 | 0.85、-1.1 |
| 同一行里相邻两个的间隙 | 都是 0.2 | 第一行 0.45、0.7,第二行 0.95、0.95 |
| 整体宽 × 高 | 3.4 × 3.7 | 4.9 × 3.7 |
行的高度两边一样,都是按这一行里最高的那个方块来的。
差别全在横向。左边每一行内部间隙严格是 0.2,排得紧,但六个中心落在六个不同的 x 上。右边两行的 x 完全相同,三列对得很齐,代价是间隙不再是 0.2:buff=0.2在这里是格子与格子之间的距离,小方块待在一个比它宽的格子正中,两边自然空出来一截。整体也因此宽了 1.5。
原因在两个方法的做法上。arrange是让第二个成员next_to到第一个旁边、第三个next_to到第二个旁边,一路接下去,它眼里只有相邻的两个。arrange_in_grid先把成员按行列分好,取每一列里最宽的当列宽、每一行里最高的当行高,画出一张格子,再把成员一个个放进去。
排完之后整组的位置也不一样
这一点和对不对齐没关系,但两条路混着用的时候很容易被它绊一下。
arrange排完会把整组挪到画面正中,arrange_in_grid排完整组留在原来的中心。我们把两种写法都先move_to([3, 2, 0])再排:两层arrange排完中心在 (0, 0),前面的move_to白写了;arrange_in_grid排完中心还是 (3, 2)。
所以上面那段代码里,两边的move_to都放在了排列的后面。用arrange的时候这个顺序是必须的,用arrange_in_grid时放前放后都行,统一放后面最省心。
arrange_in_grid 的格子还能怎么调
格子里靠哪边
默认是放在格子正中。cell_alignment=DOWN让每个成员贴着格子底边,我们试了上面那六个方块,同一行的底边 y 变得一样了(第一行 -0.15,第二行 -1.85),像东西摆在架子上。col_alignments="lcr"可以一列一列地指定左、中、右,做左列是名称、右列是数字的小表格时用得上。
格子固定大小
不想让列宽随内容变,可以直接给:col_widths=[2] * 3, row_heights=[2] * 2。这样排完,三列的中心相距都是 2.2(列宽 2 加间隙 0.2),和里面装的方块多大没关系。列表长度要和列数对上,我们试过给三列只传两个宽度,报的是ValueError: col_widths has a mismatching size.
横竖间隙分开设
buff=(0.2, 1.0),第一个数是横向间隙,第二个是纵向间隙。这是量出来的结果;0.21.0 的文档字符串里写的是(row, col),容易理解反。
行列数只写一个
七个成员写cols=3会排成 3、3、1,写rows=2是 4、3。两个都写但装不下(比如七个成员rows=2, cols=3)会报ValueError。
什么时候反而该用两层 arrange
arrange_in_grid有一件事做不到:每行各自居中。
七个边长 1 的方块arrange_in_grid(cols=3, buff=0.2),前两行的 x 是 -1.2、0、1.2,第三行只有一个,它在 x = -1.2,靠最左。网格是按格子填的,不满的那一行从第一格开始放。
换成两层arrange,每行是一个独立的小组,最外层arrange(DOWN)时默认中心对中心,第三行那一个就在 x = 0,居中了。
同样的道理,每行个数本来就不一样的排法只能用两层arrange。比如 1、2、3 的三角堆:
sq = [Square(side_length=1) for _ in range(6)] rows = VGroup(VGroup(sq[0]), VGroup(*sq[1:3]), VGroup(*sq[3:])) for r in rows: r.arrange(RIGHT, buff=0.2) rows.arrange(DOWN, buff=0.2)排出来第一行在 x = 0,第二行在 ±0.6,第三行在 -1.2、0、1.2,每行都以中线对称。
小结
| 两层 arrange | arrange_in_grid | |
|---|---|---|
| 成员一样大 | 结果相同 | 结果相同 |
| 成员大小不一 | 行内间隙相等,上下不成列 | 行列都对齐,间隙随格子变 |
| 每行个数不同、每行各自居中 | 能 | 不能,不满的行靠左 |
| 排完整组的位置 | 回到原点 | 留在原来的中心 |
| 格内对齐、固定格子大小 | 没有 | cell_alignment、col_alignments、col_widths |
| 代码量 | 每行一个小组,外面再套一层 | 一行 |
arrange的buff、aligned_edge、center=False这些基础用法,以及往VGroup里放图片报错怎么办,在极坐标⋅XYZ 的这篇文章里写得更细;配套视频在教程《布局(下)· 尺寸 / 包围 / 编组 / 深度 + 实战》的第三节。
实测环境:Manim Community 0.21.0 · 2026 年 10 月