遷移到 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 行為。但這只是臨時方案。作為 Flutter 棄用政策的一部分,useMaterial3 標誌和 Material 2 實現最終都將被移除。
顏色
#ThemeData.colorScheme 的預設值已更新,以匹配 Material 3 設計規範。
ColorScheme.fromSeed 建構函式會根據給定的 seedColor(種子顏色)生成 ColorScheme。該建構函式生成的顏色旨在實現良好的協同效果,並滿足 Material 3 設計系統中無障礙訪問的對比度要求。
升級到 3.16 版本時,如果未配置正確的 ColorScheme,您的 UI 看起來可能會有些奇怪。若要解決此問題,請遷移至透過 ColorScheme.fromSeed 建構函式生成的 ColorScheme。
遷移前的程式碼
theme: ThemeData(
colorScheme: ColorScheme.light(primary: Colors.blue),
),
遷移後的程式碼
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時)。
遷移前的程式碼
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
遷移後的程式碼
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple).copyWith(
background: Colors.grey[50]!,
),
),
darkTheme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.deepPurple,
brightness: Brightness.dark,
).copyWith(background: Colors.grey[850]!),
),
ColorScheme.surfaceTint 值指示 Material 3 中元件的層級(elevation)。一些微件可能同時使用 surfaceTint 和 shadowColor 來指示層級(例如 Card 和 ElevatedButton),而另一些微件可能僅使用 surfaceTint(例如 AppBar)。
若要恢復微件之前的行為,請在主題中將 Colors.transparent 設定為 ColorScheme.surfaceTint。若要區分微件的陰影與內容(當沒有陰影時),請將 ColorScheme.shadow 顏色設定為不帶預設陰影顏色的微件主題的 shadowColor 屬性。
遷移前的程式碼
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
遷移後的程式碼
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 微件,且無需處理層級變化或投影。
遷移前的程式碼
ElevatedButton(
onPressed: () {},
child: const Text('Button'),
),
遷移後的程式碼
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 版本之前,當 Text 微件使用 TextTheme.bodyLarge 且包含長字串時,在受約束的佈局中會自動換行至兩行。然而,3.16 版本會將其換行為三行。如果您必須實現之前的行為,請調整文字樣式,如有必要,請調整字間距。
遷移前的程式碼
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,
),
),
遷移後的程式碼
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 微件。它略高一些,包含藥丸形狀的導航指示器,並使用新的顏色對映。
遷移前的程式碼
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',
),
],
),
遷移後的程式碼
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,它提供藥丸狀導航指示器、圓角和新的顏色對映。
遷移前的程式碼
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: () { },
),
],
),
),
遷移後的程式碼
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 顏色代替投影來建立與內容的分割。
以下程式碼演示瞭如何實現中型應用欄
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 中修改選項卡對齊方式
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 來確定選中項。
遷移前的程式碼
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),
],
),
遷移後的程式碼
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 的完整示例。
新元件
#- “選單欄和級聯選單”提供了桌面風格的菜單系統,可透過滑鼠或鍵盤完全導航。選單由
MenuBar或MenuAnchor錨定。現有應用程式不必遷移到新的菜單系統,但在 Web 或桌面平臺上部署的應用程式應考慮使用它,而不是PopupMenuButton(及相關)類。 -
DropdownMenu結合了文字欄位和選單,生成有時被稱為組合框(combo box)的元件。使用者可以透過輸入匹配字串或透過觸控、滑鼠或鍵盤與選單互動,從可能較大的列表中選擇選單項。這可以很好地替代DropdownButton微件,儘管不是強制要求。 -
SearchBar和SearchAnchor用於使用者輸入搜尋查詢的互動場景,應用計算匹配結果列表,然後使用者進行選擇或調整查詢。 -
Badge為其子元素裝飾一個簡短的小標籤(例如“+1”)。徽章通常用於裝飾NavigationDestination、NavigationRailDestination、NavigationDrawerDestination中的圖示,或按鈕的圖示(如TextButton.icon)。 -
FilledButton和FilledButton.tonal與ElevatedButton非常相似,但沒有層級變化和投影。 -
FilterChip.elevated、ChoiceChip.elevated和ActionChip.elevated是相同晶片(Chips)的懸浮變體,帶有投影和填充顏色。 -
Dialog.fullscreen填滿整個螢幕,通常包含標題、操作按鈕和頂部的關閉按鈕。
時間線
#穩定版本:3.16
參考資料
#文件
API 文件
相關問題
相關 PR