SnackBar 是 Flutter 的底部消息提示,在使用 SnackBar 的时候,需要用到 Builder ,Builder 也是 Widget,Builder 可以用闭包的方式创建子 Widget,使得子 Widget 可以使用父 Widget 的上下文,这里你可能还不太好理解,下面看具体的案例。

SnackBar

Flutter 学习(十四)基础 Widget - SnackBar 和 Builder 的使用 - 图1

SnackBar 是具有可选操作的轻量级消息提示,在屏幕的底部显示。

SnackBar 的快速上手

SnackBar 需要用 Scaffold.of(context).showSnackBar() 来显示,使用方式如下:

  1. Scaffold.of(context).showSnackBar(SnackBar(
  2. content: Text('SnackBar'), duration: Duration(seconds: 5)));

完整代码如下,点击按钮,然后弹出一个提示:

  1. import 'package:flutter/cupertino.dart';
  2. import 'package:flutter/material.dart';
  3. void main() => runApp(SnackBarBuilderWidget());
  4. class SnackBarBuilderWidget extends StatelessWidget {
  5. @override
  6. Widget build(BuildContext context) {
  7. return MaterialApp(
  8. title: "Flutter Demo",
  9. theme: ThemeData(
  10. primaryColor: Colors.blue,
  11. ),
  12. home: Scaffold(
  13. appBar: AppBar(title: Text("Flutter UI Widget -- SnackBar 及 Builder")),
  14. body: RaisedButton(
  15. child: Text('Show SnackBar'),
  16. onPressed: () {
  17. Scaffold.of(context).showSnackBar(SnackBar(
  18. content: Text('SnackBar'), duration: Duration(seconds: 5)));
  19. },
  20. ),
  21. ),
  22. );
  23. }
  24. }

但是如果这么写,运行后,就会报如下的错误:

  1. I/flutter ( 8385): ══╡ EXCEPTION CAUGHT BY GESTURE ╞═══════════════════════════════════════════════════════════════════
  2. I/flutter ( 8385): The following assertion was thrown while handling a gesture:
  3. I/flutter ( 8385): Scaffold.of() called with a context that does not contain a Scaffold.
  4. I/flutter ( 8385): No Scaffold ancestor could be found starting from the context that was passed to Scaffold.of(). This
  5. I/flutter ( 8385): usually happens when the context provided is from the same StatefulWidget as that whose build
  6. I/flutter ( 8385): function actually creates the Scaffold widget being sought.
  7. I/flutter ( 8385): There are several ways to avoid this problem. The simplest is to use a Builder to get a context that
  8. I/flutter ( 8385): is "under" the Scaffold. For an example of this, please see the documentation for Scaffold.of():
  9. I/flutter ( 8385): https://docs.flutter.io/flutter/material/Scaffold/of.html
  10. I/flutter ( 8385): A more efficient solution is to split your build function into several widgets. This introduces a
  11. I/flutter ( 8385): new context from which you can obtain the Scaffold. In this solution, you would have an outer widget
  12. I/flutter ( 8385): that creates the Scaffold populated by instances of your new inner widgets, and then in these inner
  13. I/flutter ( 8385): widgets you would use Scaffold.of().
  14. I/flutter ( 8385): A less elegant but more expedient solution is assign a GlobalKey to the Scaffold, then use the
  15. I/flutter ( 8385): key.currentState property to obtain the ScaffoldState rather than using the Scaffold.of() function.

提示 Scaffold.of(context) 里的 context 没有 Scaffold,找不到 Scaffold,所以报错了,为什么会出现这个问题?
首先,看一下 context 是从哪里来的,context 是从 build 函数里传过来的:

  1. Widget build(BuildContext context) {
  2. ...
  3. }

build 函数里传来的 context 是其父 Widget,也就是 MyApp 的 context,而 MyApp 的 context 里当然没有 Scaffold,所以会报 context 里没有 Scaffold 的错误。 那么怎么解决这个问题呢?有两个方法:

  1. 使用 Builder Widget
  2. 将使用 SnackBar 的 Widget 拆分出来

使用 Builder

代码所在位置

flutter_widget_demo/lib/snackbar/SnackBarBuilderWidget.dart

Builder 的构造函数及参数说明

Builder 的构造函数为:

  1. class Builder extends StatelessWidget {
  2. const Builder({
  3. Key key,
  4. @required this.builder
  5. }) : assert(builder != null),
  6. super(key: key);
  7. ...
  8. }
参数名字 参数类型 意义 必选 or 可选
key Key Widget 的标识 可选
builder WidgetBuilder 创建 子Widget 必选

Builder 的必选参数是 WidgetBuilder,接下里介绍一下 WidgetBuilder 的使用。

WidgetBuilder

WidgetBuilder 是一个函数,定义为:

  1. typedef WidgetBuilder = Widget Function(BuildContext context);

应该说 WidgetBuilder 函数实现了 Builder Widget 的核心功能,Builder 只是 WidgetBuilder 的封装,在有的地方,其实是直接使用 WidgetBuilder 的。

Builer 的使用

Builder 的使用方法如下:

  1. home: Scaffold(
  2. appBar: AppBar(title: Text("Flutter UI Widget -- SnackBar 及 Builder")),
  3. body: Builder(
  4. builder: (context) => RaisedButton(
  5. child: Text('Show SnackBar'),
  6. onPressed: () {
  7. Scaffold.of(context).showSnackBar(SnackBar(
  8. content: Text('SnackBar'),
  9. duration: Duration(seconds: 5)));
  10. },
  11. ),
  12. ),
  13. ),

Builder 使用 WidgetBuilder 来创建子 Widget。例如上面的例子,使用 Builder,Builder 是一个闭包,将 Scaffold 的 context 传递给 子Widget,这样 SnackBar 使用的 context 就是 Scaffold 的 context。

完整代码如下:

  1. import 'package:flutter/cupertino.dart';
  2. import 'package:flutter/material.dart';
  3. void main() => runApp(SnackBarBuilderWidget());
  4. class SnackBarBuilderWidget extends StatelessWidget {
  5. @override
  6. Widget build(BuildContext context) {
  7. return MaterialApp(
  8. title: "Flutter Demo",
  9. theme: ThemeData(
  10. primaryColor: Colors.blue,
  11. ),
  12. home: Scaffold(
  13. appBar: AppBar(title: Text("Flutter UI Widget -- SnackBar 及 Builder")),
  14. body: Builder(
  15. builder: (context) => RaisedButton(
  16. child: Text('Show SnackBar'),
  17. onPressed: () {
  18. Scaffold.of(context).showSnackBar(SnackBar(
  19. content: Text('SnackBar'),
  20. duration: Duration(seconds: 5)));
  21. },
  22. ),
  23. ),
  24. ),
  25. );
  26. }
  27. }

运行效果如下:

Flutter 学习(十四)基础 Widget - SnackBar 和 Builder 的使用 - 图2

将使用 SnackBar 的 Widget 拆分出来

代码所在位置

flutter_widget_demo/lib/snackbar/SnackBarNoBuilerWidget.dart

拆分方法

将使用 SnackBar 的 Widget 拆分出来后,SnackBar 的 Widget 使用的 context 就是 Scaffold 的 context。

  1. import 'package:flutter/cupertino.dart';
  2. import 'package:flutter/material.dart';
  3. void main() => runApp(SnackBarNoBuilerWidget());
  4. class SnackBarNoBuilerWidget extends StatelessWidget {
  5. @override
  6. Widget build(BuildContext context) {
  7. return MaterialApp(
  8. title: "Flutter Demo",
  9. theme: ThemeData(
  10. primaryColor: Colors.blue,
  11. ),
  12. home: Scaffold(
  13. appBar: AppBar(title: Text("Flutter UI基础Widget -- SnackBar")),
  14. body: SnackBarWidget()),
  15. );
  16. }
  17. }
  18. class SnackBarWidget extends StatelessWidget {
  19. @override
  20. Widget build(BuildContext context) {
  21. // TODO: implement build
  22. return RaisedButton(
  23. child: Text('Show SnackBar'),
  24. onPressed: () {
  25. Scaffold.of(context).showSnackBar(SnackBar(
  26. content: Text('SnackBar'), duration: Duration(seconds: 5)));
  27. },
  28. );
  29. }
  30. }

总结

两种方式,使用 Builder Widget,或者将 SnackBar 拆分出来,都可以实现底部消息提示,但是建议使用 Builder。

SnackBar 的构造函数及参数说明

SnackBar 的构造函数为:

  1. class SnackBar extends StatelessWidget {
  2. const SnackBar({
  3. Key key,
  4. @required this.content,
  5. this.backgroundColor,
  6. this.action,
  7. this.duration = _kSnackBarDisplayDuration,
  8. this.animation,
  9. }) : assert(content != null),
  10. assert(duration != null),
  11. super(key: key);
  12. ...
  13. }
参数名字 参数类型 意义 必选 or 可选
key Key Widget 的标识 可选
content Widget SnackBar 显示的主要内容 必选
backgroundColor Color SnackBar 的背景色 可选
action SnackBarAction SnackBar 的按钮 可选
duration Duration SnackBar 显示的时间
默认是4.0s
可选
animation Animation SnackBar 显示和消失的动画 可选

参考

【1】Flutter 实战
【2】Flutter 中文文档
【3】Flutter 完全手册