网上已经有不少文章展示如何使用 CircularNotchedRectangle
或 AutomaticNotchedShape
实现带凹陷效果的 BottomAppBar,但是都没有提到如何自定义 NotchedShape 实现任意形状的 BottomAppBar,本文以闲鱼底部导航为例展示如何自定义 NotchedShape。
首先假设你已经知道如何使用 CircularNotchedRectangle
创建凹陷效果,我们主要做的就是写一个新的 class 继承 NotchedShape
来实现我们想要的效果。
在开始写 NotchedShape
之前我们先准备好一个正常的 BottomAppBar,因为不是重点,具体的实现就不展开说了,看起来像是这样的:
唯一值得一提的是,中间的按钮是一个正常的 FloatingActionButton
,为了让它比原来大并能超出 BottomAppBar 而使用了 Transform.scale
。
接下来,我们创建新的 class CustomNotchedShape
继承 NotchedShape
并 override getOuterPath
:
class CustomNotchedShape extends NotchedShape {
final BuildContext context;
const CustomNotchedShape(this.context);
@override
Path getOuterPath(Rect host, Rect? guest) {
return Path();
}
}
NotchedShape
和 CustomClipper
很像,都是通过创建一个 path 来定义我们想要的形状,如果你有 CustomClipper
的经验,那 NotchedShape
也就差不多了。唯一不一样的是 Path getOuterPath(Rect host, Rect? guest)
有两个参数,host
在这里就是 BottomAppBar 本身的矩形边界,guest
则是嵌入 BottomAppBar 的矩形边界。由于我们并不需要嵌入 guest,第二个参数可以忽略掉。
我们可以先简单写一个梯形 path 来验证。
Path getOuterPath(Rect host, Rect? guest) {
return Path()
..moveTo(host.left + 20, host.top)
..lineTo(host.right - 20, host.top)
..lineTo(host.right, host.bottom)
..lineTo(host.left, host.bottom);
}
效果:
知道怎么使用 path 创建自定义形状之后就可以思考如何实现闲鱼底部导航的效果了,经过观察,这样的效果可以由圆弧或贝塞尔曲线组合而成,使用贝塞尔曲线可能是最简单的,而且只需要一个控制点的贝塞尔曲线。
我们可以先画轮廓直线,然后再换成单个控制点的贝塞尔曲线即可。
Path getOuterPath(Rect host, Rect? guest) {
const radius = 40.0;
const lx = 20.0;
const ly = 8;
var x = (MediaQuery.of(context).size.width - radius) / 2 - lx;
return Path()
..moveTo(host.left, host.top)
..lineTo(x, host.top)
..lineTo(x += lx, host.top - ly)
..lineTo(x += radius, host.top - ly)
..lineTo(x += lx, host.top)
..lineTo(host.right, host.top)
..lineTo(host.right, host.bottom)
..lineTo(host.left, host.bottom);
}
lx 和 ly 是旁边两条斜线的 x、y,radius 是顶部横线的长度。
然后我们替换成贝塞尔曲线以及选择合适的控制点,再引入两个变量 bx、by 作为控制点的 x、y 偏移量,所有参数调整到合适到值之后即可实现最终效果:
Path getOuterPath(Rect host, Rect? guest) {
const radius = 40.0;
const lx = 20.0;
const ly = 8;
const bx = 10.0;
const by = 20.0;
var x = (MediaQuery.of(context).size.width - radius) / 2 - lx;
return Path()
..moveTo(host.left, host.top)
..lineTo(x, host.top)
..quadraticBezierTo(x + bx, host.top, x += lx, host.top - ly)
..quadraticBezierTo(
x + radius / 2, host.top - by, x += radius, host.top - ly)
..quadraticBezierTo((x += lx) - bx, host.top, x, host.top)
..lineTo(host.right, host.top)
..lineTo(host.right, host.bottom)
..lineTo(host.left, host.bottom);
}
源码:https://github.com/qiuxiang/custom_notched_shape
DartPad 在线演示:https://dartpad.dev/?null_safety=true&id=a976bbb3961a8c5f5998a54c8d2ed7aa