概述

#

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

迁移前的代码

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 表示一个抬升的小部件。有些小部件使用不同的颜色。

要将应用的用户界面恢复到之前的行为(我们不推荐这样做)

  • 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.bodyLargeText 小部件包含长字符串时,文本会换行成两行。然而,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.tonalElevatedButton 非常相似,但没有抬升变化和阴影。
  • FilterChip.elevatedChoiceChip.elevatedActionChip.elevated 是相同芯片的抬升变体,带有阴影和填充色。
  • Dialog.fullscreen 填充整个屏幕,通常在顶部包含一个标题、一个操作按钮和一个关闭按钮。

时间线

#

稳定版本:3.16

参考资料

#

文档

API 文档

相关问题

相关 PR