Flutter BottomAppBar 自定義路徑 + 貝塞爾曲線實現閒魚底部導航

7c00發表於2021-07-28

網上已經有不少文章展示如何使用 CircularNotchedRectangleAutomaticNotchedShape 實現帶凹陷效果的 BottomAppBar,但是都沒有提到如何自定義 NotchedShape 實現任意形狀的 BottomAppBar,本文以閒魚底部導航為例展示如何自定義 NotchedShape。

首先假設你已經知道如何使用 CircularNotchedRectangle 建立凹陷效果,我們主要做的就是寫一個新的 class 繼承 NotchedShape 來實現我們想要的效果。

在開始寫 NotchedShape 之前我們先準備好一個正常的 BottomAppBar,因為不是重點,具體的實現就不展開說了,看起來像是這樣的:

Screenshot_20210728-172230

唯一值得一提的是,中間的按鈕是一個正常的 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();
  }
}
複製程式碼

NotchedShapeCustomClipper<Path> 很像,都是通過建立一個 path 來定義我們想要的形狀,如果你有 CustomClipper<Path> 的經驗,那 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);
}
複製程式碼

效果:

Screenshot_20210728-180335

知道怎麼使用 path 建立自定義形狀之後就可以思考如何實現閒魚底部導航的效果了,經過觀察,這樣的效果可以由圓弧或貝塞爾曲線組合而成,使用貝塞爾曲線可能是最簡單的,而且只需要一個控制點的貝塞爾曲線。

我們可以先畫輪廓直線,然後再換成單個控制點的貝塞爾曲線即可。

Screenshot_20210728-174420

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 偏移量,所有引數調整到合適到值之後即可實現最終效果:

Screenshot_20210728-164125

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);
}
複製程式碼

原始碼:github.com/qiuxiang/cu…

DartPad 線上演示:dartpad.dev/?null_safet…

相關文章