在Flutter中显示SnackBar

82

我想在Flutter的Stateful widget中显示一个简单的SnackBar。我的应用程序使用一个名为MyHomePage的有状态(widget)创建了新的MaterialApp实例。

我试图在showSnackBar()方法中显示SnackBar。但是它失败并显示 The method showSnackBar was called on null

这段代码有什么问题吗?

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key}) : super(key: key);

  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {

  final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();

  @override
  void initState() {
    super.initState();
    showInSnackBar("Some text");
  }

  @override
  Widget build(BuildContext context) {
    return new Padding(
            key: _scaffoldKey,
            padding: const EdgeInsets.all(16.0),
            child: new Text("Simple Text")
    );
  }

  void showInSnackBar(String value) {
    _scaffoldKey.currentState.showSnackBar(new SnackBar(
        content: new Text(value)
    ));
  }
}

解决方案:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
        title: 'Flutter',
        theme: new ThemeData(
            primarySwatch: Colors.blue,
        ),
        home: new Scaffold(body: new MyHomePage()),
      );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key}) : super(key: key);

  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {

  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    showInSnackBar("Some text");
    return new Padding(
        padding: const EdgeInsets.all(16.0),
        child: new Scaffold(
          body: new Text("Simple Text")
        )
    );
  }

  void showInSnackBar(String value) {
    Scaffold.of(context).showSnackBar(new SnackBar(
        content: new Text(value)
    ));
  }
}

1
我更倾向于建议所有Flutter用户使用Flushbar插件。您可以查看此答案,了解为什么应该使用Flushbar插件而不是直接使用Snackbar。请查看此答案:https://dev59.com/9FUK5IYBdhLWcg3wyihl#51261864 - Aman Malhotra
只需使用异步函数调用您的 snackbar,如果将函数放在 build 中而不是 initstate 中,则每次有 setstate 时都会弹出 snackbar。 - MathieuAuclair
这是一个关于如何使SnackBar小部件在任何页面上可用的教程视频,如果你感到很困惑:https://youtu.be/u9KoFtu0HEY - doppelgunner
1
Scaffold.of()已经被弃用,如果要显示SnackBars,请使用ScaffoldMessenger - LS-
你可以尝试这个:https://dev59.com/F1MI5IYBdhLWcg3wCWwo#69344954 - Gopal Kohli
20个回答

76

在我的情况下,我有这样的代码(在state类中)


final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();

void showInSnackBar(String value) {
    _scaffoldKey.currentState.showSnackBar(new SnackBar(content: new Text(value)));
}

但是我没有为scaffold设置键。

因此,当我添加key: _scaffoldKey时:

 @override
 Widget build(BuildContext context) {
  return new Scaffold(
    key: _scaffoldKey,
    body: new SafeArea(

SnackBar开始工作啦 :)


Flutter文档将其描述为“一个不太优雅但更快捷的解决方案...”。请小心处理 :) - Rohan Taneja
这是使用ScaffoldKey的完美案例! - Ashwin Balani
1
当前状态中未找到showSnackBar方法。 - yousef Abbdolzadeh

54

这里有三个问题。第一个是您在任何地方都没有Scaffold,而Scaffold小部件知道如何显示SnackBar。第二个是您有一个用于获取Scaffold的关键字,但您将其放在了Padding上(而Paddings不知道SnackBar)。第三个是您在与其关联的小部件有机会初始化之前使用了它的关键字,因为initState在build之前被调用。

最简单的解决方案是将MyApp小部件中的home行更改为:

home: new Scaffold(body: new MyHomePage()),

...然后删除所有涉及_scaffoldKey的提及,并在当前使用_scaffoldKey.currentState的地方改用Scaffold.of(context)


1
将我的StatefulWidget用新的Scaffold包装起来,并使用Scaffold.of(context)代替_scaffoldKey引用会有所帮助。谢谢! - kosiara - Bartosz Kosarzycki
1
强烈不建议嵌套Scaffold,所以在这里要小心! - Westy92
@Westy92,这是有趣的评论,请问您能否告诉我们原因?我曾看到有人鼓励在每个小部件中使用脚手架封装,但我并不相信。 - Tokenyet
@Tokenyet https://github.com/flutter/flutter/pull/26259/files#diff-ad7e5ee7be5375e4242652ddc6448da7R729 - Westy92

38

ScaffoldState已被弃用,请使用ScaffoldMessengerState

使用ScaffoldMessenger通常有两种显示SnackBar的方式。


  1. 直接方式:

    @override
    Widget build(BuildContext context) {
      return Scaffold(
        body: ElevatedButton(
          onPressed: () {
            var snackBar = SnackBar(content: Text('Hello World'));
            ScaffoldMessenger.of(context).showSnackBar(snackBar);
          },
          child: Text('Show SnackBar'),
        ),
      );
    }
    

  1. 使用 GlobalKey

final _globalKey = GlobalKey<ScaffoldMessengerState>();

@override
Widget build(BuildContext context) {
  return ScaffoldMessenger(
    key: _globalKey,
    child: Scaffold(
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            var snackBar = SnackBar(content: Text('Hello World'));
            _globalKey.currentState.showSnackBar(snackBar);
          },
          child: Text('Show SnackBar'),
        ),
      ),
    ),
  );
}

33

在Flutter中,有一种更好、更干净的方式来显示Snackbar。我通过艰难的探索发现了它,现在分享给大家,希望能对其他人有所帮助。

不需要更改你的主要应用部分。

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
  return new MaterialApp(
    title: 'MyApp',
    theme: new ThemeData(
    primarySwatch: Colors.orange),
    home: new MainPage());
  }
}

页面状态码是事情将发生变化的地方。

我们知道Flutter提供了Scaffold.of(context).showSnackBar。然而,context应该是Scaffold的后代的上下文,而不是包括Scaffold的上下文。为了避免错误,我们需要在Scaffold的正文中使用BuildContext,并将其存储在一个变量中,如下所示。

class MainPageState extends State<MainPage> {                                                                         
BuildContext scaffoldContext;                                                                                                                                                                                                

@override                                                                                                           
Widget build(BuildContext context) {                                                                                

return new Scaffold(                                                                                              
    backgroundColor: Colors.grey,                                                                                 
    appBar: new AppBar(                                                                                           
      title: const Text(APP_TITLE),                                                                               
    ),                                                                                                             
    body: new Builder(builder: (BuildContext context) {                                                           
      scaffoldContext = context;                                                                                  
      return new Center(
           child: new Text('Hello World', style: new TextStyle(fontSize: 32.0)),
         );                                                                                
    }));                                                                                                           
}                                                                                                                   


void createSnackBar(String message) {                                                                               
  final snackBar = new SnackBar(content: new Text(message),                                                         
  backgroundColor: Colors.red);                                                                                      

  // Find the Scaffold in the Widget tree and use it to show a SnackBar!                                            
  Scaffold.of(scaffoldContext).showSnackBar(snackBar);                                                              
  }                                                                                                                   
}     

现在,您可以从任何地方调用此函数,并且它将显示Snackbar。例如,我正在使用它来显示互联网连接性消息。

现在,您可以从任何地方调用此函数,并且它将显示 Snackbar。例如,我正在使用它来显示互联网连接性消息。


这种方法比@Jack the Ripper提供的GlobalKey方法更好吗? - Kirill Karmazin
因为GlobalKey很昂贵。请参考此答案:https://dev59.com/9VUK5IYBdhLWcg3wwiSW - live-love

13

您可以使用这个:

final _scaffoldKey = GlobalKey<ScaffoldState>();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      key: _scaffoldKey,
      appBar: AppBar(


    ),
  }
}

然后在onPressed()中添加以下代码

_scaffoldKey.currentState.showSnackBar(
       new SnackBar(
          content: new Text('Hello this is snackbar!')
       )
);

11
Scaffold.of(context).showSnackBar(
     SnackBar(content: Text("Thanks for using snackbar",
     textAlign: TextAlign.center, style: TextStyle(fontSize: 16.0, fontWeight: 
     FontWeight.bold),), duration: Duration(seconds: 2), backgroundColor: Colors.red,)
);

10

你可以在initState()中初始化Snackbar,等布局构建完成后执行一个函数。

void initState() {
super.initState();
WidgetsBinding.instance
    .addPostFrameCallback((_) => _scaffoldKey.currentState.showSnackBar(SnackBar(content: Text("Your message here..")));}

7

我猜测_scaffoldKey.currentState在调用时还未被初始化,因此initState已经被调用,在build之前。

我不确定你是否可以从initState中获取ScaffoldState。如果你更改代码,则可以使用以下方法从build方法中显示snackbar:

Scaffold.of(context).showSnackBar(SnackBar(Text(value)));

仅仅将 method showInSnackBar() 移动到 build() 方法中是不起作用的。一方面,您是正确的 - initState() 在 build() 方法之前调用,但即使在 initState() 方法之后:Scaffold.of(context)_scaffoldKey.currentState 字段仍然为空。 - kosiara - Bartosz Kosarzycki

6

Scaffold.Of(context)已经不建议用于显示snackbar。

请使用新的方式:

final snackBar = SnackBar(content: Text('Yay! A SnackBar!'));

ScaffoldMessenger.of(context).showSnackBar(snackBar);

snackbars


4

如果有人想要在initState()中初始化Snackbar(换句话说,在页面加载时),这是我的解决方案。另外,我假设您已经准备好了_scaffoldKey

void initState() {
  super.initState();
  Future.delayed(Duration(seconds: 1)).then(
    (_) => _displaySnackbar
  );
}

// Display Snackbar
void get _displaySnackbar {
  _scaffoldKey.currentState.showSnackBar(SnackBar(
    duration: Duration(minutes: 1),
    content: Text('Your snackbar message')
  ));
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接