概述

#

Scaffold 中的 SnackBar API 现在由 ScaffoldMessenger 处理,而 ScaffoldMessenger 默认在 MaterialApp 的上下文中提供一个。

背景

#

在此更改之前,SnackBar 会通过调用当前 BuildContext 内的 Scaffold 来显示。通过调用 Scaffold.of(context).showSnackBar,当前 Scaffold 会将 SnackBar 动画化显示。这只适用于当前的 Scaffold,并且在 SnackBar 显示过程中路由发生变化时不会在路由之间持久化。如果 showSnackBar 在执行异步事件的过程中被调用,并且 BuildContext 由于路由更改和 Scaffold 被销毁而失效,也会导致错误。

ScaffoldMessenger 现在负责处理 SnackBar,以便在路由之间持久化并在当前 Scaffold 上始终显示。默认情况下,MaterialApp 中包含一个根 ScaffoldMessenger,但您可以创建自己的 ScaffoldMessenger 受控范围,以进一步控制哪些 Scaffold 会接收您的 SnackBar

变更说明

#

以前的方法是调用 Scaffold 来显示 SnackBar

dart
Scaffold(
  key: scaffoldKey,
  body: Builder(
    builder: (BuildContext context) {
      return GestureDetector(
        onTap: () {
          Scaffold.of(context).showSnackBar(SnackBar(
            content: const Text('snack'),
            duration: const Duration(seconds: 1),
            action: SnackBarAction(
              label: 'ACTION',
              onPressed: () { },
            ),
          ));
        },
        child: const Text('SHOW SNACK'),
      );
    },
  )
);

新的方法是调用 ScaffoldMessenger 来显示 SnackBar。在这种情况下,不再需要 Builder 来提供一个具有“位于”Scaffold“之下”的 BuildContext 的新作用域。

dart
Scaffold(
  key: scaffoldKey,
  body: GestureDetector(
    onTap: () {
      ScaffoldMessenger.of(context).showSnackBar(SnackBar(
        content: const Text('snack'),
        duration: const Duration(seconds: 1),
        action: SnackBarAction(
          label: 'ACTION',
          onPressed: () { },
        ),
      ));
    },
    child: const Text('SHOW SNACK'),
  ),
);

在过渡期间显示 SnackBar 时,SnackBar 会完成 Hero 动画,平滑地移动到下一页。

ScaffoldMessenger 创建一个作用域,其中所有后代 Scaffold 都会注册以接收 SnackBar,这就是它们在这些过渡中持久化的方式。当使用 MaterialApp 提供的根 ScaffoldMessenger 时,所有后代 Scaffold 都会接收 SnackBar,除非在树的更下方创建了新的 ScaffoldMessenger 作用域。通过实例化您自己的 ScaffoldMessenger,您可以根据应用程序的上下文控制哪些 Scaffold 接收 SnackBar,哪些不接收。

方法 debugCheckHasScaffoldMessenger 可用于断言给定的上下文具有 ScaffoldMessenger 祖先。尝试在没有 ScaffoldMessenger 祖先的情况下显示 SnackBar 会导致断言,如下所示:

No ScaffoldMessenger widget found.
Scaffold widgets require a ScaffoldMessenger widget ancestor.
Typically, the ScaffoldMessenger widget is introduced by the MaterialApp
at the top of your application widget tree.

迁移指南

#

迁移前的代码

dart
// The ScaffoldState of the current context was used for managing SnackBars.
Scaffold.of(context).showSnackBar(mySnackBar);
Scaffold.of(context).hideCurrentSnackBar(mySnackBar);
Scaffold.of(context).removeCurrentSnackBar(mySnackBar);

// If a Scaffold.key is specified, the ScaffoldState can be directly
// accessed without first obtaining it from a BuildContext via
// Scaffold.of. From the key, use the GlobalKey.currentState
// getter. This was previously used to manage SnackBars.
final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
Scaffold(
  key: scaffoldKey,
  body: ...,
);

scaffoldKey.currentState.showSnackBar(mySnackBar);
scaffoldKey.currentState.hideCurrentSnackBar(mySnackBar);
scaffoldKey.currentState.removeCurrentSnackBar(mySnackBar);

迁移后的代码

dart
// The ScaffoldMessengerState of the current context is used for managing SnackBars.
ScaffoldMessenger.of(context).showSnackBar(mySnackBar);
ScaffoldMessenger.of(context).hideCurrentSnackBar(mySnackBar);
ScaffoldMessenger.of(context).removeCurrentSnackBar(mySnackBar);

// If a ScaffoldMessenger.key is specified, the ScaffoldMessengerState can be directly
// accessed without first obtaining it from a BuildContext via
// ScaffoldMessenger.of. From the key, use the GlobalKey.currentState
// getter. This is used to manage SnackBars.
final GlobalKey<ScaffoldMessengerState> scaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();
ScaffoldMessenger(
  key: scaffoldMessengerKey,
  child: ...
)

scaffoldMessengerKey.currentState.showSnackBar(mySnackBar);
scaffoldMessengerKey.currentState.hideCurrentSnackBar(mySnackBar);
scaffoldMessengerKey.currentState.removeCurrentSnackBar(mySnackBar);

// The root ScaffoldMessenger can also be accessed by providing a key to 
// MaterialApp.scaffoldMessengerKey. This way, the ScaffoldMessengerState can be directly accessed
// without first obtaining it from a BuildContext via ScaffoldMessenger.of. From the key, use
// the GlobalKey.currentState getter.
final GlobalKey<ScaffoldMessengerState> rootScaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();
MaterialApp(
  scaffoldMessengerKey: rootScaffoldMessengerKey,
  home: ...
)

rootScaffoldMessengerKey.currentState.showSnackBar(mySnackBar);
rootScaffoldMessengerKey.currentState.hideCurrentSnackBar(mySnackBar);
rootScaffoldMessengerKey.currentState.removeCurrentSnackBar(mySnackBar);

时间线

#

发布于版本:1.23.0-13.0.pre
稳定版本:2.0.0

参考资料

#

API 文档

相关问题

相关 PR