跳至主要內容

遷移至 Material 3

摘要

#

Material 函式庫已更新,以符合 Material 3 設計規範。變更包括新的元件和元件主題、更新的元件視覺效果等等。許多更新都是無縫的。當您針對 3.16 (或更新版本) 發行版重新編譯應用程式時,您會看到受影響的小工具的新版本。但完成遷移也需要一些手動操作。

遷移指南

#

在 3.16 發行版之前,您可以將 useMaterial3 旗標設定為 true 來選擇加入 Material 3 的變更。從 Flutter 3.16 發行版 (2023 年 11 月) 開始,useMaterial3 預設為 true。

順帶一提,您可以透過將 useMaterial3 設定為 false 來還原應用程式中的 Material 2 行為。不過,這只是一個暫時的解決方案。useMaterial3 旗標 Material 2 實作最終將作為 Flutter 棄用政策的一部分而移除。

色彩

#

ThemeData.colorScheme 的預設值已更新,以符合 Material 3 設計規範。

ColorScheme.fromSeed 建構函式會產生一個衍生自給定 seedColorColorScheme。此建構函式產生的色彩旨在協調搭配使用,並滿足 Material 3 設計系統中可存取性的對比要求。

更新至 3.16 發行版時,如果沒有正確的 ColorScheme,您的 UI 看起來可能會有點奇怪。若要修正此問題,請遷移至從 ColorScheme.fromSeed 建構函式產生的 ColorScheme

遷移前的程式碼

dart
theme: ThemeData(
  colorScheme: ColorScheme.light(primary: Colors.blue),
),

遷移後的程式碼

dart
theme: ThemeData(
  colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
),

若要產生基於內容的動態色彩配置,請使用 ColorScheme.fromImageProvider 靜態方法。如需產生色彩配置的範例,請查看從網路圖片產生的 ColorScheme範例。

Flutter Material 3 的變更包括新的背景色彩。ColorScheme.surfaceTint 表示提升的小工具。某些小工具會使用不同的色彩。

若要將您的應用程式 UI 還原為先前的行為 (我們不建議這麼做)

  • Colors.grey[50]! 設定為 ColorScheme.background (當主題為 Brightness.light 時)。
  • Colors.grey[850]! 設定為 ColorScheme.background (當主題為 Brightness.dark 時)。

遷移前的程式碼

dart
theme: ThemeData(
  colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),

遷移後的程式碼

dart
theme: ThemeData(
  colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple).copyWith(
    background: Colors.grey[50]!,
  ),
),
dart
darkTheme: ThemeData(
  colorScheme: ColorScheme.fromSeed(
    seedColor: Colors.deepPurple,
    brightness: Brightness.dark,
  ).copyWith(background: Colors.grey[850]!),
),

ColorScheme.surfaceTint 值表示 Material 3 中元件的提升。某些小工具可能會同時使用 surfaceTintshadowColor 來表示提升 (例如,CardElevatedButton),而其他小工具可能只會使用 surfaceTint 來表示提升 (例如 AppBar)。

若要還原小工具的先前行為,請將主題中的 Colors.transparent 設定為 ColorScheme.surfaceTint。若要區分小工具的陰影與內容 (當它沒有陰影時),請將 ColorScheme.shadow 色彩設定為小工具主題中沒有預設陰影色彩的 shadowColor 屬性。

遷移前的程式碼

dart
theme: ThemeData(
  colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),

遷移後的程式碼

dart
theme: ThemeData(
  colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple).copyWith(
    surfaceTint: Colors.transparent,
  ),
  appBarTheme: AppBarTheme(
   elevation: 4.0,
   shadowColor: Theme.of(context).colorScheme.shadow,
 ),
),

ElevatedButton 現在會以新的色彩組合設定自己的樣式。先前,當 useMaterial3 旗標設定為 false 時,ElevatedButton 會以 ColorScheme.primary 設定背景樣式,並以 ColorScheme.onPrimary 設定前景樣式。若要達到相同的視覺效果,請切換至新的 FilledButton 小工具,而無需提升變更或陰影。

遷移前的程式碼

dart
ElevatedButton(
  onPressed: () {},
  child: const Text('Button'),
),

遷移後的程式碼

dart
ElevatedButton(
  style: ElevatedButton.styleFrom(
    backgroundColor: Theme.of(context).colorScheme.primary,
    foregroundColor: Theme.of(context).colorScheme.onPrimary,
  ),
  onPressed: () {},
  child: const Text('Button'),
),

排版

#

ThemeData.textTheme 的預設值已更新,以符合 Material 3 的預設值。變更包括更新的字型大小、字型粗細、字母間距和行高。如需更多詳細資訊,請查看 TextTheme 文件。

如下列範例所示,在 3.16 發行版之前,當受限版面配置中使用 TextTheme.bodyLarge 的長字串 Text 小工具時,文字會換行成兩行。但是,3.16 發行版會將文字換行成三行。如果您必須達到先前的行為,請調整文字樣式,並在必要時調整字母間距。

遷移前的程式碼

dart
ConstrainedBox(
  constraints: const BoxConstraints(maxWidth: 200),
    child: Text(
      'This is a very long text that should wrap to multiple lines.',
      style: Theme.of(context).textTheme.bodyLarge,
  ),
),

遷移後的程式碼

dart
ConstrainedBox(
  constraints: const BoxConstraints(maxWidth: 200),
    child: Text(
      'This is a very long text that should wrap to multiple lines.',
      style: Theme.of(context).textTheme.bodyMedium!.copyWith(
        letterSpacing: 0.0,
      ),
  ),
),

元件

#

某些元件無法僅更新以符合 Material 3 設計規範,而是需要全新的實作。由於 Flutter SDK 不確切知道您想要什麼,因此這類元件需要手動遷移。

將 Material 2 樣式的 BottomNavigationBar 小工具替換為新的 NavigationBar 小工具。它稍微高一點,包含藥丸狀導覽指示器,並使用新的色彩對應。

遷移前的程式碼

dart
BottomNavigationBar(
  items: const <BottomNavigationBarItem>[
    BottomNavigationBarItem(
      icon: Icon(Icons.home),
      label: 'Home',
    ),
    BottomNavigationBarItem(
      icon: Icon(Icons.business),
      label: 'Business',
    ),
    BottomNavigationBarItem(
      icon: Icon(Icons.school),
      label: 'School',
    ),
  ],
),

遷移後的程式碼

dart
NavigationBar(
  destinations: const <Widget>[
    NavigationDestination(
      icon: Icon(Icons.home),
      label: 'Home',
    ),
    NavigationDestination(
      icon: Icon(Icons.business),
      label: 'Business',
    ),
    NavigationDestination(
      icon: Icon(Icons.school),
      label: 'School',
    ),
  ],
),

請查看BottomNavigationBar 遷移至 NavigationBar 的完整範例。

Drawer 小工具替換為 NavigationDrawer,它提供藥丸狀導覽指示器、圓角和新的色彩對應。

遷移前的程式碼

dart
Drawer(
  child: ListView(
    children: <Widget>[
      DrawerHeader(
        child: Text(
          'Drawer Header',
          style: Theme.of(context).textTheme.titleLarge,
        ),
      ),
      ListTile(
        leading: const Icon(Icons.message),
        title: const Text('Messages'),
        onTap: () { },
      ),
      ListTile(
        leading: const Icon(Icons.account_circle),
        title: const Text('Profile'),
        onTap: () {},
      ),
      ListTile(
        leading: const Icon(Icons.settings),
        title: const Text('Settings'),
        onTap: () { },
      ),
    ],
  ),
),

遷移後的程式碼

dart
NavigationDrawer(
  children: <Widget>[
    DrawerHeader(
      child: Text(
        'Drawer Header',
        style: Theme.of(context).textTheme.titleLarge,
      ),
    ),
    const NavigationDrawerDestination(
      icon: Icon(Icons.message),
      label: Text('Messages'),
    ),
    const NavigationDrawerDestination(
      icon: Icon(Icons.account_circle),
      label: Text('Profile'),
    ),
    const NavigationDrawerDestination(
      icon: Icon(Icons.settings),
      label: Text('Settings'),
    ),
  ],
),

請查看Drawer 遷移至 NavigationDrawer 的完整範例。

Material 3 引入了中型和大型應用程式列,可在捲動前顯示較大的標題。捲動時,會使用 ColorScheme.surfaceTint 色彩來建立與內容的分隔,而不是陰影。

下列程式碼示範如何實作中型應用程式列

dart
CustomScrollView(
  slivers: <Widget>[
    const SliverAppBar.medium(
      title: Text('Title'),
    ),
    SliverToBoxAdapter(
      child: Card(
        child: SizedBox(
          height: 1200,
          child: Padding(
            padding: const EdgeInsets.fromLTRB(8, 100, 8, 100),
            child: Text(
              'Here be scrolling content...',
              style: Theme.of(context).textTheme.headlineSmall,
            ),
          ),
        ),
      ),
    ),
  ],
),

現在有兩種 TabBar 小工具:主要和次要。次要索引標籤用於內容區域內,以進一步區隔相關內容並建立階層。請查看 TabBar.secondary 範例。

新的 TabBar.tabAlignment 屬性會指定索引標籤的水平對齊方式。

下列範例顯示如何在可捲動的 TabBar 中修改索引標籤對齊方式

dart
AppBar(
  title: const Text('Title'),
  bottom: const TabBar(
    tabAlignment: TabAlignment.start,
    isScrollable: true,
    tabs: <Widget>[
      Tab(
        icon: Icon(Icons.cloud_outlined),
      ),
      Tab(
        icon: Icon(Icons.beach_access_sharp),
      ),
      Tab(
        icon: Icon(Icons.brightness_5_sharp),
      ),
    ],
  ),
),

SegmentedButton (是 ToggleButtons 的更新版本) 使用完全圓角、版面配置高度和大小不同,並使用 Dart Set 來判斷選取的項目。

遷移前的程式碼

dart
enum Weather { cloudy, rainy, sunny }

ToggleButtons(
  isSelected: const [false, true, false],
  onPressed: (int newSelection) { },
  children: const <Widget>[
    Icon(Icons.cloud_outlined),
    Icon(Icons.beach_access_sharp),
    Icon(Icons.brightness_5_sharp),
  ],
),

遷移後的程式碼

dart
enum Weather { cloudy, rainy, sunny }

SegmentedButton<Weather>(
  selected: const <Weather>{Weather.rainy},
  onSelectionChanged: (Set<Weather> newSelection) { },
  segments: const <ButtonSegment<Weather>>[
    ButtonSegment(
      icon: Icon(Icons.cloud_outlined),
      value: Weather.cloudy,
    ),
    ButtonSegment(
      icon: Icon(Icons.beach_access_sharp),
      value: Weather.rainy,
    ),
    ButtonSegment(
      icon: Icon(Icons.brightness_5_sharp),
      value: Weather.sunny,
    ),
  ],
),

請查看ToggleButtons 遷移至 SegmentedButton 的完整範例。

新元件

#
  • 「功能表列和階層式功能表」提供可使用滑鼠或鍵盤完全瀏覽的桌面樣式功能表系統。功能表由 MenuBarMenuAnchor 錨定。新的功能表系統不是現有應用程式必須遷移的內容,但部署在網頁或桌面平台上的應用程式應考慮使用它來取代 PopupMenuButton (和相關) 類別。
  • DropdownMenu 結合了文字欄位和功能表,以產生有時稱為組合方塊的內容。使用者可以透過輸入相符的字串,或透過觸控、滑鼠或鍵盤與功能表互動,從潛在的大型清單中選取功能表項目。這可以是 DropdownButton 小工具的理想替代方案,儘管這不是必要的。
  • SearchBarSearchAnchor 適用於使用者輸入搜尋查詢的互動,應用程式會計算相符回應的清單,然後使用者選取其中一個或調整查詢。
  • Badge 使用只有幾個字元的小標籤來裝飾其子系。例如「+1」。徽章通常用於裝飾 NavigationDestinationNavigationRailDestinationNavigationDrawerDestination 或按鈕圖示 (如 TextButton.icon) 內的圖示。
  • FilledButtonFilledButton.tonalElevatedButton 非常相似,但沒有提升變更和陰影。
  • FilterChip.elevatedChoiceChip.elevatedActionChip.elevated 是具有陰影和填滿色彩的相同晶片之提升變體。
  • Dialog.fullscreen 會填滿整個螢幕,且通常包含標題、動作按鈕和頂端的關閉按鈕。

時間軸

#

穩定版本:3.16

參考

#

文件

API 文件

相關問題

相關 PR