Flutter 圆角矩形研究

一项关于 Flutter 中不同圆角矩形 ShapeBorder 选项的研究与比较,使用 Flutter 应用通过视觉方式比较可用的圆角矩形形状。

概述

目前在 **Flutter** 中没有已知的圆角矩形能够精确匹配例如 Swift-UI 中使用 `RoundedRectangle(cornerRadius: myRadius, style: .continuous)` 创建的圆角矩形形状,并且在 Flutter 中也没有问题。这种具有连续曲率的圆角矩形也称为超椭圆形状或圆角矩形。

在本研究中,使用了一个名为 `figma_squircle` 的包作为参考。该包被许多人认为是 **Flutter 中最接近 iOS 圆角矩形**形状的实现。为了验证这一点,应该将其与实际的 iOS SwiftUI 产生的形状进行比较。作为起点,它被用于比较和呈现本研究中的发现。

通过此仓库中的 Flutter **squircle_study** 应用,您可以交叉比较任意两个选定的形状在不同尺寸和曲率下的表现。

在 Flutter GitHub 仓库中,issue #91523 用于跟踪实现与 iOS 匹配的连续圆角矩形。

研究发现总结

Flutter SDK 中的 `ContinuousRectangleBorder` 是一个超椭圆形状,但它与 iOS UI 元素中使用的形状不匹配。

`figma_squircle` 包是 **Figma** 中使用的圆角矩形形状的实现。Figma 的圆角矩形声称在平滑因子为 0.6 时与 iOS 形状匹配。当边框半径小于形状的圆角半径的一半时,它可能是一个很好的匹配,但这仍需验证。当半径大于形状圆角半径的 0.5 倍时,形状开始接近标准的圆形边框曲率。在圆角半径等于形状的圆角半径时,它与圆形椭圆形形状完全相同。这不是一个连续圆角的椭圆形形状。当边框半径大于形状的圆角半径时,该形状还会 **失效**。

有一个不太为人所知的包叫做 `smooth_corner`,它生成的形状与 `figma_squircle` 完全相同。当边框半径大于形状的圆角半径时,它 **不会** 失效。

使用 **除** `RoundedRectangleBorder` **之外的** 任何形状的性能影响,至少在推特评论中被提及过。他们通常会提到 **FigmaSquircle**,甚至只是 SDK 的 **ContinuousRectangleBorder**,但对于 `figma_squircle` 更是如此。这些形状的 **性能影响** 应进一步研究。有关更多信息,请参阅研究报告末尾的 **附录 A**。

所有研究的形状都没有轮廓边框选项。有些可能没有正确实现线性插值,而且可能没有一个实现了从一个 `ShapeBorder` 到另一个 Flutter SDK `ShapeBorder` 的形状变换,而大多数 Flutter SDK `ShapeBorder` 都可以做到。一个正确的 Flutter iOS 圆角矩形形状应该正确地完成所有这些事情。研究的能够绘制轮廓的形状,除了 **SquircleBorder PR** 和 **SquircleStadiumBorder PR**(它们从未有过任何边框选项)之外,在相同厚度值下边框的视觉外观似乎会产生不同的结果。这可能需要深入研究原因。关键部分当然是官方的圆角矩形应该使用与当前 Flutter `OutlinedBorder` 形状相同的原理。

待办事项

  1. 比较所使用的和研究的圆角矩形 **ShapeBorder** 与标准的 `RoundedRectangleBorder` 和 `StadiumBorder` 的性能影响。
  2. 将以下被声称是最佳匹配 iOS 圆角矩形形状的形状,与 **实际** 的 iOS Swift-UI 绘制的不同圆角矩形形状进行比较。

研究和可用的形状

此演示允许您直观地比较不同超椭圆(或所谓的 **圆角矩形**)边框圆角与矩形之间的差异或相似性。

使用此 **squircle_study** 应用可以比较的 **Shape** 选项有:

显然,它们都不是超椭圆或圆角矩形,但为比较起见包含在内。

shapes

研究过的类似圆角矩形的 Flutter **ShapeBorder** 实现

下面我们仔细看看它们,将它们与 `figma_squircle` 包中的 **FigmaSquircle** 进行比较。

圆形即 RoundedRectangleBorder

Flutter SDK 提供的标准的圆形圆角矩形边框形状,带有轮廓。

  • 名称:**RoundedRectangleBorder**
  • 来自 Flutter SDK
  • 形状是否会失效:**否**

当半径超过其圆角半径时,会保持圆形椭圆形形状。

研究发现

**圆形** 和 **FigmaSquircle** 之间的差异很微妙,但仍然可以被敏锐的设计师眼睛察觉。当边框半径小于圆角半径的 0.5 倍时(在下面的示例中为 96 dp),我们可以看到一个微妙但明显的差异。

circular low

**ShapeBorder** RoundedRectangleBorder

放大后更明显

circular low zoom

**ShapeBorder** RoundedRectangleBorder,放大查看

随着半径的增加,标准圆形边框和 **FigmaSquircle** 之间的差异会减小,并且我们越来越接近圆角半径。

circular

**ShapeBorder** RoundedRectangleBorder 在圆角半径的 0.75 倍处

放大后,即使是更明显的,在圆角半径的 0.75 倍处几乎没有区别。

circular zoom

**ShapeBorder** RoundedRectangleBorder 在圆角半径的 0.75 倍处,放大查看

当我们与圆形的 **StadiumBorder** 进行比较时,我们会进一步看到这种效果。看起来 **FigmaSquircle** 在接近圆角半径时并没有实现任何连续的边框效果,在圆角半径时也完全没有。与期望的实际 **iOS 圆角矩形** 边框相比,这是否是正确的行为尚不清楚,但似乎不太可能。

当半径等于或大于形状的圆角半径时,**RoundedRectangleBorder** 会生成 **StadiumBorder**。

circular_stadium

**ShapeBorder** RoundedRectangleBorder 在半径等于圆角半径时与 **StadiumBorder** 相同

结论 – Circular RoundedRectangleBorder

如果 **FigmaSquircle** 在平滑度为 **0.6** 时是 **iOS Swift-UI** 圆角矩形的正确表示,那么如果在中等边框半径下追求高保真度,**Circular RoundedRectangleBorder** **不是** 一个可接受的折衷方案。

对于低保真度来说,它可能是可接受的,但敏锐的眼睛会觉得有些不对劲。

我们也有第一个迹象表明,当边框半径接近或等于圆角半径时,**FigmaSquircle** 可能不是 **Swift-UI** 圆角矩形的正确表示,因为它变得等于一个圆形的椭圆形边框。

ContinuousRectangleBorder

Flutter SDK 提供的连续圆角矩形边框形状。它本应将 iOS 圆角矩形边框引入 Flutter,但实现却离它很远。

  • 名称:**ContinuousRectangleBorder**
  • 来自 Flutter SDK
  • 形状是否会失效:**是**

源代码提到了限制检查,以防止 `ContinuousRectangleBorder` 出现所谓的“TIE-fighter”形状。在此演示中,我们仍然可以在较高的边框半径下观察到 TIE-fighter 形状。因此,防止这种形状的尝试并不完全成功。**TODO(rydmike)**:考虑为此提出一个 issue。

  • 其他问题

它不提供椭圆形边框选项。

研究发现

**ContinuousRectangleBorder** 和 **FigmaSquircle** 之间的差异非常显著。

continuous_rectangle_border

**ShapeBorder** ContinuousRectangleBorder

在高边框半径下(如下所示),**ContinuousRectangleBorder** 会创建 TIE-fighter 形状。这是不理想的。

continuous_rectangle_border_break

**ShapeBorder** ContinuousRectangleBorder 出现 TIE-fighter 形状

结论 – ContinuousRectangleBorder

如果 **FigmaSquircle** 在平滑度为 **0.6** 时是 **iOS Swift-UI** 圆角矩形的正确表示,那么 **ContinuousRectangleBorder** **根本不是** 一个可接受的选项。

**ContinuousRectangleBorder** 相较于 **RoundedRectangleBorder** 的性能影响也被提及为一个问题。使用 **除** `RoundedRectangleBorder` **之外的** 任何形状时的性能影响应进一步研究。

ContinuousRectangleBorder x 2.3529

使用半径乘以 2.3529 的 Flutter 连续圆角矩形边框形状。在 Flutter issue 中提到,将 `ContinuousRectangleBorder` 的边框半径乘以 2.3529 可以接近 iOS 圆角矩形。

与 iOS 边框的匹配声称在 issue 91523 中提到,“目前,`ContinuousRectangleBorder` 需要大约 24 的 `borderRadius` 才能像 `RoundedRectangle` 的大约 10.2 的 `cornerRadius`。” 所以基本上 24/10.2 = 2.3529,就像这里使用的,应该是一个匹配。

  • 名称:**ContinuousRectangleBorder x 2.3529**
  • 来自 Flutter SDK x 因子
  • 形状是否会失效:**是**

TIE fighter 形状的问题也适用于这里。由于半径乘以 2.3529,它出现得更早。

研究发现

**ContinuousRectangleBorder x 2.3529** 和 **FigmaSquircle** 之间的差异很明显,但在边框半径小于圆角半径的 0.5 倍时,差异并不那么显著。

continuous_rectangle_border_mult_low

**ShapeBorder** ContinuousRectangleBorder x 2.3529 与 FigmaSquircle 在平滑度为 0.6 时

特别是当我们从 **FigmaSquircle** 的平滑度 **0.6** 更改为最大 **1.0** 时。如下所示,它就变成了一个几乎完美的匹配。

continuous_rectangle_border_mult_low_smooth_1

**ShapeBorder** ContinuousRectangleBorder x 2.3529 与 FigmaSquircle 在平滑度为 1.0 时

在更高的边框半径下,例如在圆角半径的 0.75 倍处,视觉上的差距变得更加显著,并且 **FigmaSquircle** 上的平滑度因子也无关紧要了。

continuous_rectangle_border_mult

**ShapeBorder** ContinuousRectangleBorder x 2.3529,在其圆角半径的 0.75 倍处

在这个边框半径下,**FigmaSquircle** 也可能是 **较差** 的圆角矩形形状。

当使用 **ContinuousRectangleBorder x 2.3529** 时,TIE fighter 效果在圆角半径的 0.6 倍处就已经开始出现。

结论 – ContinuousRectangleBorder x 2.3529

如果 **FigmaSquircle** 在平滑度为 **0.6** 时是 **iOS Swift-UI** 圆角矩形的正确表示,那么 **ContinuousRectangleBorder x 2.3529** **不是** 精确匹配的,但它 **不是** 一个糟糕的圆角矩形形状。

在边框半径小于圆角半径的 0.5 倍时,并使用 **FigmaSquircle** 的平滑度 1.0 时,**ContinuousRectangleBorder x 2.3529** 对于实际的视觉比较来说是一个很好的匹配。

SquircleBorder PR

一个被 Flutter SDK 拒绝的 Squircle PR。它在这里被讨论过 flutter/flutter#27523。这是 RydMike 对 PR 的代码复兴,并进行了一些小的修改。

`SquircleBorder` 边框在较高的边框半径下行为不佳,它不能变成椭圆形,它在高相对边框半径下停止改变形状。

  • 其他问题

实现不是 `OutlinedBorder`,所以它没有轮廓功能。这需要添加。

研究发现

**SquircleBorder PR** 和 **FigmaSquircle** 之间的差异在边框半径小于圆角半径的 0.49 倍时不存在,当使用平滑度为 0.6 的 **FigmaSquircle** 时。

squircle_border_pr

**ShapeBorder** SquircleBorder PR 在半径小于其圆角半径的 0.5 倍处

**SquircleBorder PR** 在半径大于或等于其圆角半径的 0.5 倍时停止响应边框半径的增加,并且不能用于边框半径为圆角半径 0.5 倍到 1 倍的 Squircle 形状。直到该半径,它工作得很好并且与 **FigmaSquircle** 匹配。

另一方面,也有研究表明,对于超过圆角半径 0.5 倍的半径范围,**FigmaSquircle** 越来越接近由 **RoundedRectangleBorder** 表示的普通圆形边框。

squircle_border_pr_break

**ShapeBorder** SquircleBorder PR 在半径小于其圆角半径的 0.75 倍处

也可以认为 **SquircleBorder PR** 避免绘制形状,因为它的半径不再产生正确的 squircle 结果。而 **FigmaSquircle** 则转而接近一个普通的圆形边框,因为它接近了圆角半径。

结论 – SquircleBorder PR

如果 **FigmaSquircle** 在平滑度为 **0.6** 时是 **iOS Swift-UI** 圆角矩形的正确表示,那么 **SquircleBorder PR** 在小于 **0.5 倍圆角半径** 的较低半径下是一个 **可接受** 的选项。在更高的边框半径下,它在视觉上停止改变半径,因为它无法在该半径以上绘制 squircle 形状。

FigmaSquircle

Figma 圆角平滑的 Flutter 包实现。

该包被许多人认为是 **iOS 圆角矩形** 形状的最佳近似值之一。它是 **FigmaSquircle** 形状的实现。它有一个从 0.0 到 1.0 的平滑度因子,值为 **0.6** 时,Figma 形状 **声称** 与 **iOS 圆角矩形形状** **相同**。

在相同的平滑度下,此 `figma_squircle` 包也应该与 iOS 圆角矩形相同。为了 **验证** 此点,应将其与 **实际** 的 iOS SwiftUI 产生的形状进行比较。

在本研究中,我们将其用作参考,以展示其他形状如何偏离它。

  • 名称:**FigmaSquircle**
  • 来自包 figma_squircle
  • 形状是否会失效:**是**

当边框半径超过形状的圆角半径时,形状会失效。

研究发现

**FigmaSquircle** 在设置的边框半径超过其圆角半径时会失效。

figma_squircle_breaks

**ShapeBorder** FigmaSquircle (SmoothRectangleBorder) 在半径大于其圆角半径时,形状会失效

对于边框半径大于形状圆角半径的 0.5 倍且小于等于 1.0 倍时,**FigmaSquircle** 越来越接近普通的圆形圆角矩形边框。在边框半径大于形状圆角半径的 0.9 倍且小于等于 1.0 倍时,我们再也无法从 **RoundedRectangleBorder** 制成的普通圆形边框中观察到任何实际差异。

figma_squircle_equal

**ShapeBorder** FigmaSquircle (SmoothRectangleBorder) 在半径为其圆角半径的 0.9 倍处,几乎与圆形 **RoundedRectangleBorder** 相同

结论 – FigmaSquircle

如果 **FigmaSquircle** 在平滑度为 **0.6** 时是 **iOS Swift-UI** 圆角矩形的正确表示,那么这是与 iOS 形状保真度良好的选择。

然而,**FigmaSquircle** 形状在半径超过形状的圆角半径时会失效。一个好的形状算法在超过其圆角半径时应该保持其等效的椭圆形形状。

还观察到,对于边框半径大于形状圆角半径的 0.5 倍且小于等于 1.0 倍时,**FigmaSquircle** 形状开始变成普通的圆形圆角矩形边框。此时尚不清楚这与实际 **iOS** 椭圆形形状的对比是否正确。根据快速查看,可能不是。**这需要验证**。

**FigmaSquircle** 的性能影响也在 issue 中被提及。应进一步研究。

SmoothCorner

具有模仿 Figma 的可变平滑度的矩形边框。

这是 **Figma 圆角矩形** 的 **另一个** 实现。它在形状上似乎与上面的 **FigmaSquircle** 完全相同。

  • 名称:**SmoothCorner**
  • 来自包 smooth_corner
  • 形状是否会失效:**否**

当边框半径超过其圆角半径时,形状保持正确。

研究发现

**SmoothCorner** 和 **FigmaSquircle** 之间的差异似乎可以忽略不计。从视觉上看,使用相同的平滑度因子时,结果 appears to be identical。**SmoothCorner** 也支持 **FigmaSquircle** 具有的平滑度因子。

smooth_corner

**ShapeBorder** SmoothCorner 和 FigmaSquircle 在视觉上是相同的

**SmoothCorner** 在边框半径超过形状 **圆角半径** 时不会失效。

smooth_corner_no_break

**ShapeBorder** SmoothCorner 在半径大于圆角半径时不像 FigmaSquircle 那样失效

结论 – SmoothCorner

如果 **FigmaSquircle** 在平滑度为 **0.6** 时是 **iOS Swift-UI** 圆角矩形的正确表示,那么 **SmoothCorner** 也 **是正确** 的。因为它们是相同的,所以 **SmoothCorner** 具有与 **FigmaSquircle** 相同的可疑的椭圆形形状。

**SmoothCorner** 相较于 **FigmaSquircle** 有一个优势,那就是当半径超过形状的圆角半径时,形状不会失效。这是一个令人愉悦且期望的功能,并且与 **RoundedRectangleBorder** 相对于其 **StadiumBorder** 形状的行为相匹配,当圆角半径超过圆角半径时,它会保持 **椭圆形** 形状。

**SmoothCorner** 的性能没有已知的反馈,因为它不像 **FigmaSquircle** 那样被广泛使用。其性能影响也应予以研究。

CupertinoSquircleBorder 即 CupertinoCorners

一个用于制作 iOS 圆角,也称为圆角矩形的小部件和边框,使用贝塞尔曲线,并在角落有两个点。

研究发现

**CupertinoCorners** 在视觉上看起来与 **ContinuousRectangleBorder** 几乎完全相同。

cupertino_border

**ShapeBorder** CupertinoCorners 和 ContinuousRectangleBorder 在视觉上是相同的

它也有同样的 TIE-fighter 问题。

cupertino_border_tie_fighter

**ShapeBorder** CupertinoCorners 与 ContinuousRectangleBorder 具有相同的 TIE-fighter 形状失效。

当边框半径大于最短边时,TIE-fighter 形状确实会发生分歧。**ContinuousRectangleBorder** 停止呈现增加的 TIE-fighter 形状,但对于 **CupertinoCorners**,它变得更加明显和加倍。

cupertino_border_tie_fighter_dual

**ShapeBorder** CupertinoCorners 的 TIE-fighter 形状比 ContinuousRectangleBorder 更具侵略性。

结论 – CupertinoCorners

没有理由选择 **CupertinoCorners** 而不是 Flutter SDK 中存在的 **ContinuousRectangleBorder**。它与 **FigmaSquircle** 的匹配程度也与 **ContinuousRectangleBorder** 一样差。

SuperEllipse

**SuperEllipse** 是一个用于在 flutter 中创建超椭圆形状的包。超椭圆形是介于矩形和圆形之间的过渡形状。

研究发现

**SuperEllipse** 在视觉上看起来与 **ContinuousRectangleBorder** 几乎完全相同。

super_ellipse

**ShapeBorder** SuperEllipse 和 ContinuousRectangleBorder 在视觉上是相同的

它也有同样的 TIE-fighter 问题,但与 **CupertinoCorners** 不同的是,它在任何给定的边框半径下都与 **ContinuousRectangleBorder** 完全相同,即使是 **非常高** 的半径。

super_ellipse_tie_fighter

**ShapeBorder** CupertinoSuperEllipse 与 ContinuousRectangleBorder 具有相同的 TIE-fighter 形状失效。

结论 – SuperEllipse

没有理由选择 **SuperEllipse** 而不是 Flutter SDK 中存在的 **ContinuousRectangleBorder**。它与 **FigmaSquircle** 的匹配程度也与 **ContinuousRectangleBorder** 一样差。

StadiumBorder

Flutter 标准的圆形椭圆形边框。它在应用于它的 widget 的矩形内适合一个椭圆形形状的边框,即两端带有半圆的盒子。

  • 名称:**StadiumBorder**
  • 来自 Flutter SDK
  • 形状是否会失效:**否**

研究发现

当使用等于形状圆角半径的边框半径时,**FigmaSquircle** 和标准的 Flutter SDK 圆形 **StadiumBorder** 之间 **没有** 可见差异。

stadium

**ShapeBorder** StadiumBorder 和 FigmaSquircle 在视觉上是相同的

当边框半径等于圆角半径时,平滑度因子对 **FigmaSquircle** 也没有影响。

stadium_figma

**ShapeBorder** StadiumBorder 和 FigmaSquircle 在视觉上是相同的,即使放大查看也是如此。

结论 – StadiumBorder

使用 **FigmaSquircle** 来制作 **椭圆形** 形状没有意义,它在任何等于形状圆角半径的半径下都与 **StadiumBorder** 相同。在圆角半径约为圆角半径的 0.6 倍时,我们已经开始注意到 **FigmaSquircle** 和圆形 **RoundedRectangleBorder** 之间的视觉差异可以忽略不计。

SquircleStadiumBorder PR

一个被 Flutter SDK 拒绝的 Stadium Squircle PR。它在这里被讨论过 flutter/flutter#27523。这是 RydMike 对 PR 的代码复兴,并进行了一些修改。

当宽度接近高度时,形状会收缩其高度,反之亦然。这是 **不期望** 的行为。

  • 其他问题

实现不是 `OutlinedBorder`,所以它没有轮廓功能。这需要添加。

研究发现

**SquircleStadiumBorder PR** 比 **FigmaSquircle** 看起来更好。

squircle_stadium_border

**ShapeBorder** SquircleStadiumBorder 比 FigmaSquircle 是一个漂亮的椭圆形圆角矩形。

当放大时,**SquircleStadiumBorder PR** 和 **FigmaSquircle** 之间存在明显差异。

squircle_stadium_border_zoom

**ShapeBorder** SquircleStadiumBorder 和 FigmaSquircle 放大查看。

在椭圆形边框处,**FigmaSquircle** 仅等于 **StadiumBorder**。因此,将 **SquircleStadiumBorder PR** 与 **StadiumBorder** 进行比较,与将其与 **FigmaSquircle** 进行比较是相同的。

squircle_stadium_border_stadium

**ShapeBorder** SquircleStadiumBorder 与 StadiumBorder 的比较与与 FigmaSquircle 的比较相同。

**SquircleStadiumBorder** 在长边接近短边宽度时,其行为与 **StadiumBorder** 不同。宽度缩小,在最短边和最长边改变之前,因此在边缘上绘制椭圆形曲线的位置会发生交换。这种 **行为是不期望的**,它应该表现得像 **StadiumBorder** 一样。

squircle_stadium

**ShapeBorder** SquircleStadiumBorder 与 StadiumBorder 在边长改变时的比较。

结论 – SquircleStadiumBorder PR

**SquircleStadiumBorder PR** 似乎是 **唯一** 具有漂亮圆角矩形形状的 **椭圆形** 形状。它 **可能比** **FigmaSquircle** **更适合** iOS 椭圆形圆角矩形形状。这是基于 **FigmaSquircle** 仅等于普通圆形椭圆形边框,而 **iOS 圆角矩形** 椭圆形形状可能并非如此。

SimonSquircle

Simon Lightfoot 在 Gist 中提供的一个圆角矩形实现。

  • 名称:**SimonSquircleBorder**
  • 来自 Slightfoot gist
  • 形状是否会失效:**是**

该形状无法处理 0 到小于 1.0 的边框半径,它会产生非常奇怪的形状。

研究发现

对于 0 到小于 1 的边框半径,会绘制出奇怪的形状。该形状 **不是** iOS 圆角矩形,它完全是其他东西。下面显示了在不同边框半径下产生的形状示例。

simon0

**ShapeBorder** SimonSquircle 在边框半径为 0.57 处

simon1

**ShapeBorder** SimonSquircle 在边框半径为 1.36 处

simon2

**ShapeBorder** SimonSquircle 在边框半径为 2.62 处

simon3

**ShapeBorder** SimonSquircle 在边框半径为 26 处

simon4

**ShapeBorder** SimonSquircle 在边框半径为 80 处

simon5

**ShapeBorder** SimonSquircle 在边框半径为 133 处

结论 – SimonSquircle

不要将此形状用于 iOS 圆角矩形形状,它 **不是** 匹配的。

Beveled

具有倒角或“斜面”角的矩形边框。

  • 名称:**BeveledRectangleBorder**
  • 来自 Flutter SDK

出于明显的原因,此形状不进行比较。它仅包含在研究应用中,以展示 Flutter SDK 中存在的另一种边角形状,但很少使用。

附录 A – 关于性能问题的推文

一些关于使用 Figma Squircle 和 ContinuousRectangleBorder 性能问题的推文链接。

Figma Squircle

Continuous Rectangle Border

GitHub

查看 Github