跳到主內容

遷移到 Material 3

瞭解如何將 Flutter 應用程式的 UI 從 Material 2 遷移到 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 建構函式會根據給定的 seedColor 生成一個 ColorScheme。此建構函式生成的顏色旨在協同工作並滿足 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),
      ),
    ],
  ),
),

SegmentedButtonToggleButtons 的更新版本,它使用完全圓角,在佈局高度和大小方面有所不同,並使用 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 錨定。新的菜單系統不是現有應用程式必須遷移的內容,但是部署在 Web 或桌面平臺上的應用程式應考慮使用它,而不是 PopupMenuButton(和相關)類。
  • DropdownMenu 將文字欄位和選單組合在一起,以生成有時稱為組合框的內容。使用者可以透過輸入匹配的字串或透過觸控、滑鼠或鍵盤與選單互動來從潛在的大列表中選擇選單項。這可以很好地替代 DropdownButton 小部件,儘管這並非必要。
  • SearchBarSearchAnchor 用於使用者輸入搜尋查詢、應用程式計算匹配響應列表,然後使用者選擇一個或調整查詢的互動。
  • Badge 使用只有幾個字元的小標籤裝飾其子項。例如 '+1'。徽章通常用於裝飾 NavigationDestinationNavigationRailDestinationNavigationDrawerDestination 或按鈕的圖示內的圖示,如 TextButton.icon 中。
  • FilledButtonFilledButton.tonal 與沒有抬升更改和投影的 ElevatedButton 非常相似。
  • FilterChip.elevatedChoiceChip.elevatedActionChip.elevated 是具有投影和填充顏色的相同晶片的抬高變體。
  • Dialog.fullscreen 填充整個螢幕,通常包含標題、一個操作按鈕和頂部的一個關閉按鈕。

時間線

#

穩定版本:3.16

參考資料

#

文件

API 文件

相關問題

相關 PR