Flutter:导航离开时如何关闭 SnackBar?

Posted

技术标签:

【中文标题】Flutter:导航离开时如何关闭 SnackBar?【英文标题】:Flutter: How to close SnackBar when navigating away? 【发布时间】:2021-12-03 15:14:15 【问题描述】:

我有一个问题。我有一个“详细信息”页面,可以通过ScaffoldMessenger 显示SnackBar。 此小吃栏不会隐藏,它的持续时间设置为很长时间,因为它应该保持可见很长时间,或者直到它被用户或导航离开视图。

最后一部分是我遇到的问题。在我的dispose 方法中,我尝试调用ScaffoldMessenger.of(_scaffoldKey.currentContext!).hideCurrentSnackBar(),但这不起作用并抛出无法访问上下文的错误。 我怀疑这是因为与键关联的上下文也是从小部件树中删除的相同上下文,因为我正在离开它(通过使用后退按钮)。

我不想处理在其他视图中删除快餐栏的问题。我知道每次导航到它们时我都可以致电ScaffoldMessenger.of(context).clearAllSnackBars(),但出于架构原因我不喜欢它:

    “详细”视图拥有快餐栏,因为它负责创建它。它还应负责处理它。 将来我可能会重新组织我的观点,然后我必须记住 清理各处的小吃店。我给你的例子是受限的 示例,但想象有可访问的侧边栏导致许多 不同的看法。这意味着将这段代码添加到所有这些视图中。

所以我真的很想在处理 DetailPage 视图时以某种方式删除该快餐栏。我怎样才能做到这一点?

链接到dartpad.dev

import 'package:flutter/material.dart';

void main() 
  runApp(App());


class App extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return MaterialApp(home: const HomePage(), routes: 
      '/detail': (_) => const DetailPage(),
    );
  


class HomePage extends StatelessWidget 
  const HomePage(
    Key? key,
  ) : super(key: key);

  @override
  Widget build(BuildContext context) 
    return Scaffold(
      appBar: AppBar(title: const Text('Home')),
      body: SizedBox.expand(
        child: TextButton(
          child: const Text('See detail'),
          onPressed: () 
            Navigator.pushNamed(context, '/detail');
          ,
        ),
      ),
    );
  


class DetailPage extends StatefulWidget 
  const DetailPage(
    Key? key,
  ) : super(key: key);

  @override
  _DetailPageState createState() => _DetailPageState();


class _DetailPageState extends State<DetailPage> 
  final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();

  @override
  void initState() 
    super.initState();
    WidgetsBinding.instance!.addPostFrameCallback((_) 
      ScaffoldMessenger.of(_scaffoldKey.currentContext!).showSnackBar(SnackBar(
        content: const Text('Entered detail page'),
        duration: const Duration(days: 1),
        action: SnackBarAction(
            label: 'Close',
            onPressed: () 
              ScaffoldMessenger.of(_scaffoldKey.currentContext!)
                  .hideCurrentSnackBar();
            ),
      ));
    );
  

  @override
  void dispose() 
    ScaffoldMessenger.of(_scaffoldKey.currentContext!).hideCurrentSnackBar();
    super.dispose();
  

  @override
  Widget build(BuildContext context) 
    return Scaffold(
      key: _scaffoldKey,
      appBar: AppBar(title: const Text('Detail')),
      body: const SizedBox.expand(
        child: Center(child: Text('Detail page')),
      ),
    );
  

【问题讨论】:

【参考方案1】:

使用WillPopScope 小部件删除snackbar。

此小部件允许异步代码在视图弹出导航堆栈之前运行,并且此时上下文仍然存在于小部件树中。您可以通过这种方式摆脱覆盖的 dispose 方法。

您可以在 this dartpad 中看到它的工作原理,或者只需注意以下代码:

import 'package:flutter/material.dart';

void main() 
  runApp(App());


class App extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return MaterialApp(home: const HomePage(), routes: 
      '/detail': (_) => const DetailPage(),
    );
  


class HomePage extends StatelessWidget 
  const HomePage(
    Key? key,
  ) : super(key: key);

  @override
  Widget build(BuildContext context) 
    return Scaffold(
      appBar: AppBar(title: const Text('Home')),
      body: SizedBox.expand(
        child: TextButton(
          child: const Text('See detail'),
          onPressed: () 
            Navigator.pushNamed(context, '/detail');
          ,
        ),
      ),
    );
  


class DetailPage extends StatefulWidget 
  const DetailPage(
    Key? key,
  ) : super(key: key);

  @override
  _DetailPageState createState() => _DetailPageState();


class _DetailPageState extends State<DetailPage> 
  final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();

  @override
  void initState() 
    super.initState();
    WidgetsBinding.instance!.addPostFrameCallback((_) 
      ScaffoldMessenger.of(_scaffoldKey.currentContext!).showSnackBar(SnackBar(
        content: const Text('Entered detail page'),
        duration: const Duration(days: 1),
        action: SnackBarAction(
            label: 'Close',
            onPressed: () 
              ScaffoldMessenger.of(_scaffoldKey.currentContext!)
                  .hideCurrentSnackBar();
            ),
      ));
    );
  

  @override
  Widget build(BuildContext context) 
    return WillPopScope(
        child: Scaffold(
          key: _scaffoldKey,
          appBar: AppBar(title: const Text('Detail')),
          body: const SizedBox.expand(
            child: Center(child: Text('Detail page')),
          ),
        ),
        onWillPop: () async 
          ScaffoldMessenger.of(_scaffoldKey.currentContext!).hideCurrentSnackBar();
          return Future.value(true);
        );
  

我认为唯一的缺点是hideCurrentSnackBar 没有与Future 一起完成,所以动画没有完成。也许有一种方法可以使用某种Completer

【讨论】:

以上是关于Flutter:导航离开时如何关闭 SnackBar?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Flutter Web view App 中在没有互联网的情况下打开 Web View 应用程序时显示“您处于离线状态”?

linux终端里,退出后再次登录,如何找回上次离开时的软件状态(还原上次会话)?

单击离开时取消选择表格单元格。迅速

如何处理后退按钮android以退出应用程序并在首页中打开时关闭本机导航侧菜单

如何使用导航抽屉打开和关闭更改按钮图像

Flutter Firebase 消息传递 - 应用打开时未显示推送通知