如何在 Flutter 中正确显示 Snackbar?

How to properly display a Snackbar in Flutter?

我试图在点击 floatingActionbutton 时显示 Snackbar。但是当我点击 floatingactionbutton 时,它没有显示任何内容。这是我的代码。我正在使用 StatefulWidget。我调试并检查了 onPressed 函数也正在执行,但不知何故 Snackbar 不可见。问题的根本原因是什么?我觉得我传递的 BuildContext 有问题。

class MyApp extends StatefulWidget{
  @override
  MyAppState createState() {
    // TODO: implement createState
    return new MyAppState();
  }

}

class MyAppState extends State<MyApp>{
  File _image;
  String _text;

  Future getImage() async {
    var image = await ImagePicker.pickImage(source: ImageSource.camera);
    _image = image;
    final FirebaseVisionImage visionImage = FirebaseVisionImage.fromFile(_image);
    final TextRecognizer textRecognizer = FirebaseVision.instance.textRecognizer();
    final VisionText visionText = await textRecognizer.processImage(visionImage);

    String detectedText = visionText.text;

    setState(() {
      _image = image;
      _text = detectedText;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: new AppBar(
          title: new Text('Image Picker Example'),
        ),
        body: new Center(
          child: _image == null
              ? new Text('No image selected.')
              : new Image.file(_image),
        ),
        floatingActionButton: new FloatingActionButton(
          onPressed: (){
            showSnackBar(context);
//            getImage();
          },
          tooltip: 'Pick Image',
          child: new Icon(Icons.add_a_photo),
        ),
      ),
    );
  }

  void showSnackBar(BuildContext context) {
    final scaffold = Scaffold.of(context);
    final snackBarContent = SnackBar(
      content: Text("sagar"),
      action: SnackBarAction(
          label: 'UNDO', onPressed: scaffold.hideCurrentSnackBar),
    );
    scaffold.showSnackBar(snackBarContent);
  }

}
class MyApp extends StatefulWidget{
  @override
  MyAppState createState() {
    // TODO: implement createState
    return new MyAppState();
  }

}

class MyAppState extends State<MyApp>{

  final GlobalKey<ScaffoldState> _scaffoldkey = new GlobalKey<ScaffoldState>();
  File _image;
  String _text;

  Future getImage() async {
    var image = await ImagePicker.pickImage(source: ImageSource.camera);
    _image = image;
    final FirebaseVisionImage visionImage = FirebaseVisionImage.fromFile(_image);
    final TextRecognizer textRecognizer = FirebaseVision.instance.textRecognizer();
    final VisionText visionText = await textRecognizer.processImage(visionImage);

    String detectedText = visionText.text;

    setState(() {
      _image = image;
      _text = detectedText;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        key: _scaffoldkey,
        appBar: new AppBar(
          title: new Text('Image Picker Example'),
        ),
        body: new Center(
          child: _image == null
              ? new Text('No image selected.')
              : new Image.file(_image),
        ),
        floatingActionButton: new FloatingActionButton(
          onPressed: (){
            showSnackBar();
//            getImage();
          },
          tooltip: 'Pick Image',
          child: new Icon(Icons.add_a_photo),
        ),
      ),
    );
  }

  void showSnackBar() {
    final snackBarContent = SnackBar(
      content: Text("sagar"),
      action: SnackBarAction(
          label: 'UNDO', onPressed: _scaffoldkey.currentState.hideCurrentSnackBar),
    );
    _scaffoldkey.currentState.showSnackBar(snackBarContent);
  }



}

发生这种情况是因为使用的 BuildContext 没有 Scaffold 祖先,因此将无法找到它来呈现 SnackBar 因为它取决于 Scaffold 来显示它。

根据 of method 文档:

When the Scaffold is actually created in the same build function, the context argument to the build function can't be used to find the Scaffold (since it's "above" the widget being returned). In such cases, the following technique with a Builder can be used to provide a new scope with a BuildContext that is "under" the Scaffold:

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: Text('Demo')
    ),
    body: Builder(
      // Create an inner BuildContext so that the onPressed methods
      // can refer to the Scaffold with Scaffold.of().
      builder: (BuildContext context) {
        return Center(
          child: RaisedButton(
            child: Text('SHOW A SNACKBAR'),
            onPressed: () {
              ScaffoldMessenger.of(context).showSnackBar(SnackBar(
                content: Text('Hello!'),
              ));
            },
          ),
        );
      },
    ),
  );
}

解决方案

将您的 FloatingActionButton 包装在 Builder 小部件中比使用 GlobalKey 更优雅,这在 @Epizon 的回答中已经提到。

显示SnackBar你可以这样使用:

 ScaffoldMessenger.of(context).showSnackBar(SnackBar(
          content: Text('User Logged In'),
        ));

之前的SnackBar是这样使用的:

Scaffold.of(context).showSnackBar(SnackBar(
                content: Text('Rahul Kushwaha!'),
              ));

现在,SnackBar 由ScaffoldMessenger管理。有关详细信息,请研究此 link

我在尝试从父级 Parent() => childLevel1 => childOfLevel1(){showing the snackbar here}

的二级子级 StatfulWidgetshowSnackbar() 时遇到了这个问题

我实现了@Epizon 的回答,但是 .currentState.showSnackBar(snackBarContent); 被弃用了,据 flutter 说:

The SnackBar API within the Scaffold is now handled by the ScaffoldMessenger read full document

所以在@Epizon 的回答中替换这一行: _scaffoldKey.currentState!.showSnackBar(_snackBarContent);

对此: ScaffoldMessenger.of(_scaffoldKey.currentState!.context) .showSnackBar(_snackBarContent);

将解决弃用问题

这是代码

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

    void _showSnack(String msg) {
      final _snackBarContent = SnackBar(content: Text(msg));
      ScaffoldMessenger.of(_scaffoldKey.currentState!.context)
          .showSnackBar(_snackBarContent);     
    }

return MaterialApp(
      home: Scaffold(
        //resizeToAvoidBottomInset: true,
        key: _scaffoldKey,
      body: Center(
child: ElevatedButton(
child: Text("show snackbar"),
onPressed(){_showSnack("im located in the btn's onPressed");},
);
);