Flutter 中 FloatingActionButton与 BottomAppBar 的使用详解 | Flutter Widgets

3,363 阅读4分钟

这是我参与更文挑战的第18天,活动详情查看: 更文挑战

前言

上一篇我们聊了底部导航栏 BottomNavigationBar 的使用详解,这篇我们聊聊 FloatingActionButton 与 BottomAppBar的使用,为啥要放到一起呢?只有这两个 Widget 组合在一起才可以出效果,单独聊没有太大的价值,因为使用都比较简单。

看效果

比如这些奇怪的导航,我们要怎么实现?

凹凸菜单圆角导航波浪导航
image.pngimage.pngimage.png

FloatingActionButton

实现上面的效果前,我们先聊聊 FloatingActionButton (中间蓝色加号按钮),然后再与下面的 BottomAppBar 结合即可

先看整体结构

Scaffold(
  body:[bodyWidget],
  // 这里设置悬浮按钮
  floatingActionButton: FloatingActionButton(
    // 设置一个➕按钮
    child: Icon(Icons.add),
    // 添加点击事件
    onPressed: () {
    },
  ),
)

一般我们使用它就是在 Scaffold 中设置即可,当然他是一个 Widget 我们在任何地方都可以使用他。

效果

image.png

属性调配

backgroundColor: Colors.orangeforegroundColor: Colors.orangesplashColor: Colors.orange
image.pngimage.png01.gif
elevation: 0elevation: 6(默认)highlightElevation: 12
image.pngimage.pngimage.png
mini: trueshape: RoundedRectangleBordershape: ContinuousRectangleBorder
image.pngimage.pngimage.png

上代码

FloatingActionButton(
  child: Icon(Icons.add),
  // backgroundColor: Colors.orange,
  // foregroundColor: Colors.orange,
  // splashColor: Colors.orange,
  // elevation: 6,
  // highlightElevation: 12,
  // mini: true,
  // shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
  // shape: ContinuousRectangleBorder(
  //   side: BorderSide(
  //     width: 4,
  //     color: Colors.orange,
  //   ),
  //   borderRadius: BorderRadius.circular(12),
  // ),
  // shape: BeveledRectangleBorder(
  //   side: BorderSide(
  //     width: 4,
  //     color: Colors.orange,
  //   ),
  //   borderRadius: BorderRadius.circular(12),
  // ),
  onPressed: () {
  },
)

上面是写基本的设置的属性,有时我们还有一些特殊的需求,比如

改变大小

你会发现除了刚才设置的 mini 和默认大小外,没有属性可以设置大小,那么我们就在外层包一层 SizedBox 即可。

SizedBox(
  width: 80,
  height: 80,
  child: FloatingActionButton(...),
)

image.png

排列多个

比如我们做上下排列的效果,那么我们只需要添加一个 Column 即可,里面放置多个 FloatingActionButton 即可

Column(
  mainAxisSize: MainAxisSize.min,
  children: [
    FloatingActionButton(
      child: Icon(Icons.ac_unit_rounded),
      backgroundColor: Colors.orange,
      onPressed: () {},
    ),
    SizedBox(height: 10),
    FloatingActionButton(
      child: Icon(Icons.adb_sharp),
      backgroundColor: Colors.green,
      onPressed: () {},
    ),
    SizedBox(height: 10),
    FloatingActionButton(
      child: Icon(Icons.more_horiz_outlined),
      onPressed: () {},
    ),
  ],
)

看效果

image.png

处理异常

FloatingActionButton 还有个 heroTag 属性我们没有聊,这个是干嘛用的呢?这个就是 Hero widget 的标签,如果此时我们设置点击跳转到一个新的页面,那么会报错,因为在 material 的设计规范里,每个屏幕只能有一个悬浮按钮,如果多个的话,在路由导航是就会出现标签冲突(不设置都是默认标签对象),解决版本就是设置不同的 heroTag 即可。

Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          FloatingActionButton(
            child: Icon(Icons.ac_unit_rounded),
            backgroundColor: Colors.orange,
            // 设置 tag1
            heroTag: 'tag1',
            onPressed: () {},
          ),
          SizedBox(height: 10),
          FloatingActionButton(
            child: Icon(Icons.adb_sharp),
            backgroundColor: Colors.green,
            // 设置 tag2
            heroTag: 'tag2',
            onPressed: () {},
          ),
          SizedBox(height: 10),
          FloatingActionButton(
            child: Icon(Icons.more_horiz_outlined),
            // 设置 tag3
            heroTag: 'tag3',
            onPressed: () {},
          ),
        ],
      )

位置设置

02.gif
如上图我们可以通过 ScaffoldfloatingActionButtonLocation 属性来设置 FloatingActionButton 的位置,为了更好的展示,我们增加一个 BottomAppBar 来帮助我们理解各个属性的调配

BottomAppBar

上一篇我们聊过 BottomNavigationBar ,这里我们将 bottomNavigationBar 属性改为 BottomAppBar ,因为 Flutter 中所有的控件都是 Widget

Scaffold(
  // 设置位置,中心停靠
  floatingActionButtonLocation:FloatingActionButtonLocation.centerDocked,
  // 设置 BottomAppBar
  bottomNavigationBar: BottomAppBar(
    child: Row(
      mainAxisAlignment: MainAxisAlignment.spaceAround,
      children: [
        TextButton.icon(
          icon: Icon(Icons.home),
          label: Text('首页'),
          onPressed: () {},
        ),
        SizedBox(),
        TextButton.icon(
          icon: Icon(Icons.people),
          label: Text('我的'),
          onPressed: () {},
        ),
      ],
    ),
  )
)

看效果

image.png

位置调配

  • 非 mini

小表展示了各种位置效果,觉得有用记得点赞支持我哦

startFloatcenterFloatendFloat
image.pngimage.pngimage.png
startDockedcenterDockedendDocked
image.pngimage.pngimage.png
startTopcenterTopendTop
image.pngimage.pngimage.png
  • mini 类型

这个需要配合 FloatingActionButton 中的 mini 属性来使用,并且位置属性加了前缀 mini,如 FloatingActionButtonLocation.miniCenterDocked ,位置与上面是一致的。

区别在于 BottomAppBar 设置缺口 shape 的时候,缺口的半径不同

centerDockedminiCenterDocked
image.pngimage.png

多形状 Shape

在 Flutter 很多 Widget 都是可以设置 Shape 来自定义形状的,从输入框的边框线条到背景都是如此。

  • 圆形缺口矩形
BottomAppBar(
  /// 圆形缺口矩形
  shape: CircularNotchedRectangle(),
  child: [childWidget],
)

image.png

  • 斜角矩形
BottomAppBar(
  /// 自动缺口形状
  shape: AutomaticNotchedShape(
    // 斜角矩形
    BeveledRectangleBorder(
      borderRadius: BorderRadius.circular(20),
    ),
  ),
  child: [childWidget],
)

image.png

  • 圆角矩形
BottomAppBar(
  /// 自动缺口形状
  shape: AutomaticNotchedShape(
    // 圆角矩形
    RoundedRectangleBorder(
      borderRadius: BorderRadius.circular(40),
    ),
  ),
  child: [childWidget],
)

image.png

  • 自定义形状

如果上面的一些效果也不能满足我们的需求,我们可以选择自定义 Shape,具体怎么自定义可以看下面的代码,当然如果你阅读 Flutter 的源码会更好。

BottomAppBar(
  /// 自定义形状
  shape: MyShape(),
  child: [childWidget],
)
  • 锯齿状
/// 自定义Shape
class MyShape extends NotchedShape {
  @override
  Path getOuterPath(Rect host, Rect? guest) {
    var path = Path();
    int wallCount = 10;
    double step = host.width / wallCount;
    double wall = host.height / 4;
    for (var i = 0; i < wallCount; i++) {
      // 上下起伏的锯齿状
      path.relativeLineTo(step, i.isEven ? -wall : wall);
    }
    // 分别连接到右下角、左下角、闭合左上角
    path
      ..lineTo(host.right, host.bottom)
      ..lineTo(host.left, host.bottom)
      ..close();
    return path;
  }
}

image.png

  • 波浪状

/// 自定义Shape
class MyShape extends NotchedShape {
  @override
  Path getOuterPath(Rect host, Rect? guest) {
    var path = Path();
    int wallCount = 10;
    double step = host.width / wallCount;
    double wall = host.height / 4;
    for (var i = 0; i < wallCount; i++) {
      // 圆角波浪
      path.relativeArcToPoint(
        Offset(step, i.isEven ? -wall : wall),
        radius: Radius.circular(20),
      );
    }
    // 分别连接到右下角、左下角、闭合左上角
    path
      ..lineTo(host.right, host.bottom)
      ..lineTo(host.left, host.bottom)
      ..close();
    return path;
  }
}

image.png

源码仓库

基于 Flutter 🔥 最新版本

参考链接

关注专栏

  • 此文章已收录到下面👇 的专栏,可以直接关注
  • 更多文章继续阅读|系列文章持续更新

👏 欢迎点赞➕收藏➕关注,有任何问题随时在下面👇评论,我会第一时间回复哦