返回

Flutter BottomAppBar 自定义路径 + 贝塞尔曲线实现闲鱼底部导航

前端

1. 实现一个自定义的 ShapeBorder

要实现一个自定义的 ShapeBorder,我们需要重写它的 paint 方法。在 paint 方法中,我们可以使用 Path 类来创建想要的形状。

import 'package:flutter/material.dart';

class CustomShapeBorder extends ShapeBorder {
  @override
  EdgeInsetsGeometry get dimensions => EdgeInsets.all(0.0);

  @override
  Path getInnerPath(Rect rect, {TextDirection textDirection}) {
    return Path()
      ..lineTo(0.0, rect.height)
      ..lineTo(rect.width, rect.height)
      ..lineTo(rect.width, 0.0)
      ..close();
  }

  @override
  Path getOuterPath(Rect rect, {TextDirection textDirection}) {
    return Path()
      ..lineTo(0.0, rect.height)
      ..lineTo(rect.width, rect.height)
      ..lineTo(rect.width, 0.0)
      ..close();
  }

  @override
  void paint(Canvas canvas, Rect rect, {TextDirection textDirection}) {
    Path path = Path()
      ..moveTo(rect.width / 2 - 20, 0)
      ..lineTo(rect.width / 2, rect.height)
      ..lineTo(rect.width / 2 + 20, 0)
      ..close();

    canvas.drawPath(path, Paint()..color = Colors.red);
  }

  @override
  ShapeBorder scale(double t) => this;
}

2. 使用 ShapeBorder 来设置 BottomAppBar 的形状

BottomAppBar(
  shape: CustomShapeBorder(),
  child: Row(
    children: <Widget>[
      // ...
    ],
  ),
);

3. 使用贝塞尔曲线来创建圆角

Path path = Path()
  ..moveTo(rect.width / 2 - 20, 0)
  ..quadraticBezierTo(rect.width / 2, rect.height / 2, rect.width / 2 + 20, 0)
  ..close();

在上面的代码中,我们使用 quadraticBezierTo 方法来创建了一个贝塞尔曲线。quadraticBezierTo 方法的第一个参数是控制点的坐标,第二个参数是终点的坐标。通过调整控制点的坐标,我们可以控制曲线的形状。

4. 将 ShapeBorder 和 Path 应用到 BottomAppBar

BottomAppBar(
  shape: ShapeBorder.lerp(
    NotchedShape(),
    CustomShapeBorder(),
    curve: Curves.easeIn,
    textDirection: TextDirection.ltr,
  ),
  child: Row(
    children: <Widget>[
      // ...
    ],
  ),
);

在上面的代码中,我们使用 ShapeBorder.lerp 方法来将 NotchedShapeCustomShapeBorder 混合在一起。ShapeBorder.lerp 方法的第一个参数是起始的 ShapeBorder,第二个参数是结束的 ShapeBorder,第三个参数是控制混合程度的曲线,第四个参数是文本方向。通过调整 curve 参数,我们可以控制混合的形状。

5. 完整的代码

import 'package:flutter/material.dart';

class CustomShapeBorder extends ShapeBorder {
  @override
  EdgeInsetsGeometry get dimensions => EdgeInsets.all(0.0);

  @override
  Path getInnerPath(Rect rect, {TextDirection textDirection}) {
    return Path()
      ..lineTo(0.0, rect.height)
      ..lineTo(rect.width, rect.height)
      ..lineTo(rect.width, 0.0)
      ..close();
  }

  @override
  Path getOuterPath(Rect rect, {TextDirection textDirection}) {
    return Path()
      ..lineTo(0.0, rect.height)
      ..lineTo(rect.width, rect.height)
      ..lineTo(rect.width, 0.0)
      ..close();
  }

  @override
  void paint(Canvas canvas, Rect rect, {TextDirection textDirection}) {
    Path path = Path()
      ..moveTo(rect.width / 2 - 20, 0)
      ..lineTo(rect.width / 2, rect.height)
      ..lineTo(rect.width / 2 + 20, 0)
      ..close();

    canvas.drawPath(path, Paint()..color = Colors.red);
  }

  @override
  ShapeBorder scale(double t) => this;
}

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        bottomNavigationBar: BottomAppBar(
          shape: ShapeBorder.lerp(
            NotchedShape(),
            CustomShapeBorder(),
            curve: Curves.easeIn,
            textDirection: TextDirection.ltr,
          ),
          child: Row(
            children: <Widget>[
              // ...
            ],
          ),
        ),
      ),
    );
  }
}