在 AlertDialog 小部件顶部显示 SnackBar

问题描述 投票:0回答:5

我有一个

AlertDialog
小部件,当您点击其文本时,它会显示 SnackBar。 SnackBar 当前显示在
AlertDialog
屏障后面的背景中。我希望 Snackbar 显示在透明
AlertDialog
屏障的顶部。我正在寻求的行为可以在 Flutter 中实现吗?我创建了一个全新的 Flutter 应用程序,仅包含用于说明下面用例的相关代码以及屏幕截图。

Main.dart 要点

@override
Widget build(BuildContext context) {
  WidgetsBinding.instance!.addPostFrameCallback((_) async {
    showDialog(
      context: context,
      builder: (BuildContext dialogContext) => AlertDialog(
        content: GestureDetector(
          onTap: () {
            ScaffoldMessenger.of(dialogContext).showSnackBar(SnackBar(
              content: const Text('snack'),
              duration: const Duration(seconds: 1),
              action: SnackBarAction(
                label: 'ACTION',
                onPressed: () {},
              ),
            ));
          },
          child: Center(
            child: Text('Show SnackBar!'),
          ),
        ),
      ),
    );
  });
  // This method is rerun every time setState is called, for instance as done
  // by the _incrementCounter method above.
  //
  // The Flutter framework has been optimized to make rerunning build methods
  // fast, so that you can just rebuild anything that needs updating rather
  // than having to individually change instances of widgets.
  return Scaffold(
    appBar: AppBar(
      // Here we take the value from the MyHomePage object that was created by
      // the App.build method, and use it to set our appbar title.
      title: Text(widget.title),
    ),
    body: Center(
      // Center is a layout widget. It takes a single child and positions it
      // in the middle of the parent.
      child: Column(
        // Column is also a layout widget. It takes a list of children and
        // arranges them vertically. By default, it sizes itself to fit its
        // children horizontally, and tries to be as tall as its parent.
        //
        // Invoke "debug painting" (press "p" in the console, choose the
        // "Toggle Debug Paint" action from the Flutter Inspector in Android
        // Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
        // to see the wireframe for each widget.
        //
        // Column has various properties to control how it sizes itself and
        // how it positions its children. Here we use mainAxisAlignment to
        // center the children vertically; the main axis here is the vertical
        // axis because Columns are vertical (the cross axis would be
        // horizontal).
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Text(
            'You have pushed the button this many times:',
          ),
          Text(
            '$_counter',
            style: Theme.of(context).textTheme.headline4,
          ),
        ],
      ),
    ),
    floatingActionButton: FloatingActionButton(
      onPressed: _incrementCounter,
      tooltip: 'Increment',
      child: Icon(Icons.add),
    ), // This trailing comma makes auto-formatting nicer for build methods.
  );
}

flutter dart flutter-layout
5个回答
30
投票

更新

感谢艾米,我意识到点击障碍物并不会关闭对话框。此外,由于使用嵌套支架,代码导致显示多个 SnackBar。

查看以下解决所有问题的模型:

showDialog
 |
 |
ScaffoldMessenger => "Set a scope to show SnackBars only in the inner Scaffold"
   |
   --- Builder => "Add a Builder widget to access the Scaffold Messenger"
        |
        --- Scaffold => "The inner Scaffold that is needed to show SnackBars"
             |
             --- GestureDetector => "Dismiss the dialog when tapped outside"
                  |
                  --- GestureDetector => "Don't dismiss it when tapped inside"
                       |
                       --- AlertDialog => "Your dialog"

这是实现:

showDialog(
  context: context,
  builder: (context) => ScaffoldMessenger(
    child: Builder(
      builder: (context) => Scaffold(
        backgroundColor: Colors.transparent,
        body: GestureDetector(
          behavior: HitTestBehavior.opaque,
          onTap: () => Navigator.of(context).pop(),
          child: GestureDetector(
            onTap: () {},
            child: AlertDialog(
              content: GestureDetector(
                onTap: () {
                  ScaffoldMessenger.of(context).showSnackBar(
                    SnackBar(
                      content: const Text('snack'),
                      duration: const Duration(seconds: 1),
                      action: SnackBarAction(
                        label: 'ACTION',
                        onPressed: () {},
                      ),
                    ),
                  );
                },
                child: Center(
                  child: Text('Show SnackBar!'),
                ),
              ),
            ),
          ),
        ),
      ),
    ),
  ),
);

旧答案

ScaffoldMessenger
显示最近的后代
SnackBar
中的
Scaffold
。如果您在
Scaffold
之前添加另一个
AlertDialog
,它将使用它而不是留在对话框后面的根。

showDialog(
  context: context,
  builder: (BuildContext dialogContext) => Scaffold(
    backgroundColor: Colors.transparent, // Make Scaffold's background transparent
    body: AlertDialog(
      content: GestureDetector(
        onTap: () {
          ScaffoldMessenger.of(dialogContext).showSnackBar(SnackBar(
            content: const Text('snack'),
            duration: const Duration(seconds: 1),
            action: SnackBarAction(
              label: 'ACTION',
              onPressed: () {},
            ),
          ));
        },
        child: Center(
          child: Text('Show SnackBar!'),
        ),
      ),
    ),
  ),
);

8
投票

这里的问题是

showDialog
使用
MaterialApp
提供的根导航器。因此,当您显示对话框时,它会完全推到您的脚手架上。为了解决这个问题,您需要一个导航器,该导航器曾经是显示小吃栏的脚手架的“子项”。因此,以下代码添加了此导航器,将 useRootNavigator 设置为
false
以使用此导航器,并且重要的是在新创建的导航器下使用
BuildContext
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Navigator( //New navigator added here initialRoute: '/', onGenerateRoute: (setting) { return MaterialPageRoute( builder: (context) => Center( child: Builder(builder: (context) { WidgetsBinding.instance! .addPostFrameCallback((_) async { showDialog( context: context, useRootNavigator: false,//Dialog must not use root navigator builder: (BuildContext dialogContext) => AlertDialog( content: GestureDetector( onTap: () { ScaffoldMessenger.of(dialogContext) .showSnackBar(SnackBar( content: const Text('snack'), duration: const Duration(seconds: 1), action: SnackBarAction( label: 'ACTION', onPressed: () {}, ), )); }, child: Center( child: Text('Show SnackBar!'), ), ), ), ); }); return Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text( 'You have pushed the button this many times:', ), Text( '$_counter', style: Theme.of(context).textTheme.headline4, ), ]); }), )); }), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: Icon(Icons.add), ), // This trailing comma makes auto-formatting nicer for build methods. ); }

结果:

请注意,此解决方案确实会稍微限制对话框大小,并且应用栏和浮动操作按钮位于内容上方,这可能是不可取的。只需在新创建的导航器下方添加另一个支架并根据需要向下移动这些 appbar/FAB 属性即可解决此问题。模态下方带有

AppBar
的示例:

@override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Navigator(
          initialRoute: '/',
          onGenerateRoute: (setting) {
            return MaterialPageRoute(
                builder: (context) => Scaffold(
                    appBar: AppBar(
                      title: Text(widget.title),
                    ),
                    body: Center(
                      child: Builder(builder: (context) {
                        WidgetsBinding.instance!
                            .addPostFrameCallback((_) async {
                          showDialog(
                            context: context,
                            useRootNavigator: false,
                            builder: (BuildContext dialogContext) =>
                                AlertDialog(
                              content: GestureDetector(
                                onTap: () {
                                  ScaffoldMessenger.of(dialogContext)
                                      .showSnackBar(SnackBar(
                                    content: const Text('snack'),
                                    duration: const Duration(seconds: 1),
                                    action: SnackBarAction(
                                      label: 'ACTION',
                                      onPressed: () {},
                                    ),
                                  ));
                                },
                                child: Center(
                                  child: Text('Show SnackBar!'),
                                ),
                              ),
                            ),
                          );
                        });
                        return Column(
                            mainAxisAlignment: MainAxisAlignment.center,
                            children: <Widget>[
                              Text(
                                'You have pushed the button this many times:',
                              ),
                              Text(
                                '$_counter',
                                style: Theme.of(context).textTheme.headline4,
                              ),
                            ]);
                      }),
                    )));
          }),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }

结果:

使用

4
投票


0
投票
您可以将对话框包装在具有透明背景的脚手架中。

但是,您仍然需要手势检测器来弹出示波器。

class Example extends StatelessWidget { const Example({super.key}); @override Widget build(BuildContext context) { return Center( child: TextButton( child: Text('open dialog'), onPressed: () => showDialog( context: context, builder: (context) => DialogWidget())), ); } } class DialogWidget extends StatelessWidget { const DialogWidget({super.key}); @override Widget build(BuildContext context) { return GestureDetector( onTap: () => Navigator.pop(context), child: Scaffold( backgroundColor: Colors.transparent, body: GestureDetector( onTap: (){}, child: AlertDialog( actions: [ TextButton( onPressed: () { ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Text('hallo world'), action: SnackBarAction( label: 'click me!', onPressed: () {}, ), )); }, child: Text("Allert")) ], ), ), ), ); } } ``


© www.soinside.com 2019 - 2024. All rights reserved.