跳至主要內容

由 ScaffoldMessenger 管理的 SnackBar

摘要

#

Scaffold 內的 SnackBar API 現在由 ScaffoldMessenger 處理,在 MaterialApp 的 context 中預設提供一個。

背景

#

在此變更之前,SnackBar 會透過呼叫目前 BuildContext 內的 Scaffold 來顯示。呼叫 Scaffold.of(context).showSnackBar 時,目前的 Scaffold 會將 SnackBar 動畫顯示出來。這只會應用於目前的 Scaffold,如果在 SnackBar 呈現過程中變更了路由,則不會跨路由持續顯示。如果在執行非同步事件過程中呼叫 showSnackBar,且由於路由變更和 Scaffold 被處置而導致 BuildContext 失效,也會導致錯誤。

ScaffoldMessenger 現在處理 SnackBar,以便跨路由持續顯示,並且永遠顯示在目前的 Scaffold 上。預設情況下,根 ScaffoldMessenger 包含在 MaterialApp 中,但您可以建立自己的 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,您可以根據應用程式的 context 控制哪些 Scaffold 接收 SnackBar,哪些不接收。

方法 debugCheckHasScaffoldMessenger 可用於斷言給定的 context 具有 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