type
status
date
slug
summary
tags
category
icon
password
以下是将你提供的 Medium 文章翻译为中文的版本:
掌握 Flutter 中的形状和裁剪
在处理形状时,如果你不知道如何实现设计的 UI,可能会感到有些不知所措。在本文中,我们将从基础开始,然后逐步介绍一些高级示例。
<ins/>
1. 什么是形状和盒子?
形状可以是任何由路径定义的形式。
盒子是一个矩形形状,由 4 个点组成。它可以具有额外的属性,如边框半径。
在 Flutter 中,盒子可能出现在不同的上下文中,例如:
- RenderBox:非 sliver 上下文中的 widget 渲染对象
- BoxDecoration
- BoxBorder
另外,还有一些类,如 ShapeDecoration 和 ShapeBorder。
我们经常使用
BoxDecoration
来为 Container
、DecoratedSlivers
或 DecoratedBoxes
添加样式:就这么简单,我们得到了如下效果:
另外,我们还可以使用
ShapeDecoration
,它提供了类似的颜色、阴影和渐变的定制,主要的区别在于它的 shape
参数接受 ShapeBorder
,而不是 BoxShape
。那么,什么是
ShapeBorder
呢?如果我们查看其实现,就会更清楚了:它有一些超类,比如
OutlinedBorder
、StarBorder
、BeveledRectangleBorder
,这些都是用来装饰 widget 的不同形状。另外,还有一个 WidgetStateOutlinedBorder
。如果你不熟悉 WidgetStates
,可以阅读 这篇文章。边框的本质意味着应该有两条路径,内路径和外路径:
如果你不熟悉贝塞尔曲线,建议你查阅 这个交互式指南。Flutter 的
Path
支持线性、二次、三次和圆锥形段。了解了术语后,接下来我们来做些有趣的事情:创建一些自定义形状!
2. 自定义 ShapeBorder
实现
为了实现这一点,我们需要创建
ShapeBorder
或 OutlinedBorder
的一个超类,并实现以下方法:getInnerPath
和getOuterPath
:返回相应路径的方法
paint
:绘制形状的方法
scale
、copyWith
让我们使用圆锥形段来创建一个消息气泡形状,并使权重(w)参数可变。
接下来,实现
getOuterPath
方法。这里,路径由从左下角开始并顺时针方向的线段和圆锥段组成。为了更清楚,下面是每一行所做的操作的可视化:
现在,我们来创建一个稍微不同的 内 路径:
现在,我们可以像这样在任何
DecoratedBox
中使用这个形状:为了改变,我们可以使用
AnimatedBuilder
动画化圆锥形段的权重。如果你对 Flutter 中的动画不太熟悉,可以参考 官方教程。3. 裁剪器的应用
在 Flutter 中,有几个内置的 裁剪器,例如:
- ClipRect:矩形裁剪
- ClipRRect:圆角矩形裁剪
- ClipOval:圆形和椭圆形裁剪
- ClipPath:自定义路径裁剪
前三个非常简单,只需将你的 widget 包装在它们之一中即可进行裁剪。如果你想了解更多关于这些类的信息,可以参考 官方文档。
ClipPath
接受一个 CustomClipper
作为参数。在大多数情况下,只需将一个 ShapeBorder
传递给 ShapeBorderClipper
,它是 CustomClipper
的实现,通过 ShapeBorder
的外路径裁剪子项:一个重要的要点是,每次在布局中使用裁剪器时,都会创建一个新的图层,这是一个相对昂贵的操作,因此在可能的情况下,使用装饰而非裁剪。
4. 自定义裁剪器
在某些情况下,我们需要更多的裁剪控制,例如,当裁剪应该依赖于内容或某些兄弟 widget 时。让我们创建一个票据形状的 widget,它的缺口将依赖于内容的大小:
这里的难点在于,顶部和底部的子项大小可能不同,并且缺口应该依赖于此。在 Flutter 中,可以使用
RenderBox
获取有关 widget 大小的信息,它是 RenderObject
的子类。如果你不熟悉 RenderObject
,可以查阅 官方文档。首先,创建我们的布局,并为将顶部和底部子项分开的
SizedBox
添加一个 GlobalKey
。GlobalKey
允许我们获取一个 widget 的 BuildContext
,而我们需要上下文来获取 RenderObject
。我们需要在票据 widget 的上下文中获取 SizedBox
的坐标。为此,我们需要相应的 RenderBox
。由于我们这里没有使用 Slivers,因此可以安全地将 RenderObject
转换为 RenderBox
:而裁剪器本身将是这样:
<ins/>