MDC-103 Flutter:通过颜色、形状、高度和类型设置 Material 主题

1. 简介

logo_components_color_2x_web_96dp.png

Material Components (MDC) 有助于开发者实现 Material Design。MDC 是由一组 Google 工程师和用户体验设计人员倾心打造的,提供数十种精美实用的界面组件,可用于 Android、iOS、Web 和 Flutter.material.io/develop

现在,您可以比以往更多地使用 MDC 自定义应用的独特样式。Material Design 的近期扩展可让设计师和开发者更灵活地表达其产品的品牌。

在 Codelab MDC-101MDC-102 中,您使用 Material Components (MDC) 为名为 Shrine 的应用构建了基础内容。Shrine 是一款销售服装和家居用品的电子商务应用。该应用包含的用户流从登录屏幕开始,然后将用户转到显示产品的主屏幕。

构建内容

在此 Codelab 中,您将利用以下各项自定义 Shrine 应用:

  • 颜色
  • 字体排版
  • 高度
  • 形状
  • 布局

Android

iOS

Shrine 登录页面,主题为棕色和粉色

Shrine 登录页面,主题为棕色和粉色

Shrine 产品页面,顶部是一个应用栏,不对称的水平可滚动网格中满是商品,主题为粉色和棕色

Shrine 产品页面,顶部是一个应用栏,不对称的水平可滚动网格中满是商品,主题为粉色和棕色

此 Codelab 中的 MDC-Flutter 组件和子系统

  • 主题
  • 字体排版
  • 高度
  • 图片列表

您在 Flutter 开发方面的经验处于什么水平?

新手水平 中等水平 熟练水平

2. 设置您的 Flutter 开发环境

您需要使用两款软件才能完成此 Codelab:Flutter SDK一款编辑器

您可以使用以下任一设备运行此 Codelab:

  • 一台连接到计算机并设置为开发者模式的实体 AndroidiOS 设备。
  • iOS 模拟器(需要安装 Xcode 工具)。
  • Android 模拟器(需要在 Android Studio 中设置)。
  • 浏览器(调试需要 Chrome)。
  • 作为 WindowsLinuxmacOS 桌面应用使用。您必须在打算实施部署的平台上进行开发。因此,如果您要开发 Windows 桌面应用,则必须在 Windows 上进行开发,才能访问相应的构建链。如需详细了解针对各种操作系统的具体要求,请访问 docs.flutter.dev/desktop

3. 下载 Codelab 起始应用

接着 MDC-102 继续操作?

如果您已完成 MDC-102,您的代码应该可以直接用于此 Codelab。请跳到以下步骤:更改颜色。

从头开始?

下载起始 Codelab 应用

起始应用位于 material-components-flutter-codelabs-103-starter_and_102-complete/mdc_100_series 目录中。

…或从 GitHub 克隆

如需从 GitHub 克隆此 Codelab,请运行以下命令:

git clone https://github.com/material-components/material-components-flutter-codelabs.git
cd material-components-flutter-codelabs/mdc_100_series
git checkout 103-starter_and_102-complete

打开项目并运行应用

  1. 在您选择的编辑器中打开项目。
  2. 按照所选编辑器的《使用入门:试驾》中的说明“运行应用”。

大功告成!您应该会在设备上看到之前的 Codelab 中的 Shrine 登录页面。

Android

iOS

没有主题的 Shrine 登录页面

没有主题的 Shrine 登录页面

点击“Next”可查看上一个 Codelab 中的首页。

Android

iOS

没有主题的 Shrine 产品网格页面

没有主题的 Shrine 产品网格页面

4. 更改颜色

设计师已创建代表 Shrine 品牌的配色方案,并希望您在 Shrine 应用中实现该配色方案

首先,我们要将这些颜色导入到项目中。

创建 colors.dart

lib 中,新建一个名为 colors.dart 的 dart 文件。导入 Material 组件并添加 const 颜色值:

import 'package:flutter/material.dart';

const kShrinePink50 = Color(0xFFFEEAE6);
const kShrinePink100 = Color(0xFFFEDBD0);
const kShrinePink300 = Color(0xFFFBB8AC);
const kShrinePink400 = Color(0xFFEAA4A4);

const kShrineBrown900 = Color(0xFF442B2D);

const kShrineErrorRed = Color(0xFFC5032B);

const kShrineSurfaceWhite = Color(0xFFFFFBFA);
const kShrineBackgroundWhite = Colors.white;

自定义调色板

此颜色主题已由设计师使用自定义颜色创建好(如下图所示)。它包含从 Shrine 品牌中选择的颜色,并且这些颜色已应用到 Material Theme Editor,而该编辑器对它们进行了扩展,形成了颜色更丰富的调色板。(这些颜色并非来自 2014 Material 调色板。)

Material Theme Editor 已经按照不同深浅整理这些颜色并以数字标签对其进行标记,每种颜色都包括以下标签:50、100、200…一直到 900。Shrine 只使用粉色色样中深浅为 50、100 和 300 的颜色和棕色色样中深浅为 900 的颜色。

b9170eb94fd3b106.jpeg f8b4b97f898f154e.png

微件的每个带颜色参数都会映射到这些方案中的一种颜色。例如,文本字段在主动接收输入内容时,它的装饰的颜色应为主题的主色。如果该颜色不易辨认(在所处背景下难以辨别),请改用 PrimaryVariant。

这些变体是针对 2014 年推出的《Material 准则》创建的,并且仍在当前准则(请参阅“颜色系统”一文)和 MDC-Flutter 中提供。如需在代码中使用这些变体,只需调用基本颜色,然后调用颜色深浅(其值通常是 100 的倍数)。例如,Pink 400 可通过以下命令检索:Colors.pink[400]

这些调色板完全可以用于您的设计和代码。如果您已经有了品牌专用色,那么您可以使用调色板生成工具Material Theme Editor 自行生成颜色和谐的调色板。

现在,我们已经有了想要使用的颜色,可以将其应用于界面了。为此,我们将在微件层次结构的顶部,设置应用到 MaterialApp 实例的 ThemeData 微件的值。

自定义 ThemeData.light()

Flutter 包含几个内置主题。浅色主题就是其中之一。接下来,我们就要复制浅色主题,并更改相应的值以针对我们的应用进行自定义,而不是从头开始构建 ThemeData 微件。

colors.dart 导入到 app.dart.

import 'colors.dart';

然后,将以下代码添加到 app.dart 中 ShrineApp 类的范围之外:

// TODO: Build a Shrine Theme (103)
final ThemeData _kShrineTheme = _buildShrineTheme();

ThemeData _buildShrineTheme() {
  final ThemeData base = ThemeData.light();
  return base.copyWith(
    colorScheme: base.colorScheme.copyWith(
      primary: kShrinePink100,
      onPrimary: kShrineBrown900,
      secondary: kShrineBrown900,
      error: kShrineErrorRed,
    ),
    // TODO: Add the text themes (103)
    // TODO: Add the icon themes (103)
    // TODO: Decorate the inputs (103)
  );
}

现在,将 ShrineApp 的 build() 函数(MaterialApp 微件中)末尾的 theme: 设为新主题:

  // TODO: Add a theme (103)
  theme: _kShrineTheme, // New code

保存您的项目。现在您的登录屏幕应如下所示:

Android

iOS

以粉色和棕色为主题的 Shrine 登录页面

以粉色和棕色为主题的 Shrine 登录页面

主屏幕应如下所示:

Android

iOS

粉色和棕色主题的 Shrine 产品网格页面

粉色和棕色主题的 Shrine 产品网格页面

5. 修改字体排版和标签的样式

除颜色更改外,设计师还向我们提供了要使用的特定字体排版。Flutter 的 ThemeData 包括 3 个文本主题。每个文本主题都是文本样式的集合,例如“大标题”和“标题”。我们将在应用中使用几种样式,并更改部分值。

自定义文本主题

为了将字体导入项目,必须将其添加到 pubspec.yaml 文件中。

在 pubspec.yaml 中,紧跟在 flutter: 标记之后添加以下代码:

  # TODO: Insert Fonts (103)
  fonts:
    - family: Rubik
      fonts:
        - asset: fonts/Rubik-Regular.ttf
        - asset: fonts/Rubik-Medium.ttf
          weight: 500

现在,您可以访问和使用 Rubik 字体了。

pubspec 文件问题排查

如果直接剪切并粘贴上述声明,运行 pub get 时可能会遇到错误。如果遇到错误,请先移除前导空白,然后使用 2 个空格的缩进以空格替换这些空白。(

fonts:

之前两个空格,

family: Rubik

之前四个空格,依此类推。)

如果看到“Mapping values are not allowed here”,请检查问题行的缩进及其上一行的缩进。

login.dart 中,更改 Column() 中的以下代码:

Column(
  children: <Widget>[
    Image.asset('assets/diamond.png'),
    const SizedBox(height: 16.0),
    Text(
      'SHRINE',
      style: Theme.of(context).textTheme.headline5,
    ),
  ],
)

app.dart 中的 _buildShrineTheme() 之后,添加以下代码:

// TODO: Build a Shrine Text Theme (103)
TextTheme _buildShrineTextTheme(TextTheme base) {
  return base.copyWith(
    headline5: base.headline5!.copyWith(
      fontWeight: FontWeight.w500,
    ),
    headline6: base.headline6!.copyWith(
      fontSize: 18.0,
    ),
    caption: base.caption!.copyWith(
      fontWeight: FontWeight.w400,
      fontSize: 14.0,
    ),
    bodyText1: base.bodyText1!.copyWith(
      fontWeight: FontWeight.w500,
      fontSize: 16.0,
    ),
  ).apply(
    fontFamily: 'Rubik',
    displayColor: kShrineBrown900,
    bodyColor: kShrineBrown900,
  );
}

这段代码获取 TextTheme 并更改了大标题、标题和图片说明的外观。

以这种方式应用 fontFamily 只会将更改应用到 copyWith() 中指定的字体排版缩放值(大标题、标题、图片说明)。

对于某些字体,我们设置一个自定义 fontWeight,以 100 为增量单位:w500(500 磅)对应于中等字体,w400 对应于常规字体。

使用新的文本主题

将以下主题添加到 _buildShrineTheme 中的 error 后:

// TODO: Add the text themes (103)
textTheme: _buildShrineTextTheme(base.textTheme),
textSelectionTheme: const TextSelectionThemeData(
  selectionColor: kShrinePink100,
),

保存您的项目。这一次,我们还会重启应用(称为热重启),因为我们修改了字体。

Android

iOS

应用了文本主题的 Shrine 产品网格页面

应用了文本主题的 Shrine 产品网格页面

登录屏幕和主屏幕上的文本看起来不同:有些文本使用的是 Rubik 字体,还有些文本显示为棕色而不是黑色或白色。 图标还会以棕色显示。

缩小文本

现在的标签有点大。

home.dart 中,更改最里面一列的 children:

// TODO: Change innermost Column (103)
children: <Widget>[
// TODO: Handle overflowing labels (103)
  Text(
    product.name,
    style: theme.textTheme.button,
    softWrap: false,
    overflow: TextOverflow.ellipsis,
    maxLines: 1,
  ),
  const SizedBox(height: 4.0),
  Text(
    formatter.format(product.price),
    style: theme.textTheme.caption,
  ),
  // End new code
],

居中放置文本

我们需要将标签居中,并将文本与每张卡片的底部对齐,而不是与每张图片的底部对齐。

将标签移到主轴末端(底部)并将其更改为居中:

  // TODO: Align labels to the bottom and center (103)
  mainAxisAlignment: MainAxisAlignment.end,
  crossAxisAlignment: CrossAxisAlignment.center,

保存您的项目。

Android

iOS

采用不同文本对齐方式的 Shrine 产品网格页面

采用不同文本对齐方式的 Shrine 产品网格页面

看起来效果好多了。

设置文本字段的主题

您还可以使用 InputDecorationTheme 对文本字段设置装饰的主题。

app.dart_buildShrineTheme() 方法中,指定 inputDecorationTheme: 值:

// TODO: Decorate the inputs (103)
inputDecorationTheme: const InputDecorationTheme(
  border: OutlineInputBorder(),
),

目前,文本字段有一个 filled 装饰。我们要将其移除。移除 filled 并指定 inputDecorationTheme 将为文本字段提供轮廓样式。

login.dart 中,移除 filled: true 值:

// Remove filled: true values (103)
TextField(
  controller: _usernameController,
  decoration: const InputDecoration(
    // Removed filled: true
    labelText: 'Username',
  ),
),
const SizedBox(height: 12.0),
TextField(
  controller: _passwordController,
  decoration: const InputDecoration(
    // Removed filled: true
    labelText: 'Password',
  ),
  obscureText: true,
),

热重启。当“Username”字段处于活动状态(您正在其中进行键入)时,登录屏幕应如下所示:

Android

iOS

Shrine 登录页面,其中的用户名字段已获得焦点

Shrine 登录页面,其中的用户名字段已获得焦点

在文本字段中输入文字 - 边框和浮动标签会以主要颜色呈现。不过,我们不太容易看出来。人在色彩对比度不够高的情况下难以区分像素,因此这些元素不易辨认。(如需了解详情,请参阅 Material 准则“颜色”一文中的“易于辨认的颜色”部分)。

app.dart 中的 inputDecorationTheme: 下,指定一个 focusedBorder:

// TODO: Decorate the inputs (103)
inputDecorationTheme: const InputDecorationTheme(
  border: OutlineInputBorder(),
  focusedBorder: OutlineInputBorder(
    borderSide: BorderSide(
      width: 2.0,
      color: kShrineBrown900,
    ),
  ),
),

接下来,在 inputDecorationTheme: 下指定 floatingLabelStyle:

// TODO: Decorate the inputs (103)
inputDecorationTheme: const InputDecorationTheme(
  border: OutlineInputBorder(),
  focusedBorder: OutlineInputBorder(
    borderSide: BorderSide(
      width: 2.0,
      color: kShrineBrown900,
    ),
  ),
  floatingLabelStyle: TextStyle(
    color: kShrineBrown900,
  ),
),

最后,我们让“取消”按钮使用次要颜色,而不是主要颜色,以提高对比度。

TextButton(
  child: const Text('CANCEL'),
  onPressed: () {
    _usernameController.clear();
    _passwordController.clear();
  },
  style: TextButton.styleFrom(
    primary: Theme.of(context).colorScheme.secondary,
  ),
),

保存您的项目。

Android

iOS

Shrine 登录页面,包含可访问的“取消”按钮

Shrine 登录页面,包含可访问的“取消”按钮

6. 调整高度

现在,您已使用与 Shrine 匹配的特定颜色和排版设置页面样式,接下来我们调整高度。

更改“NEXT”按钮的高度

ElevatedButton 的默认高度为 2。我们将增加高度。

login.dart 中,将 style: 值添加到 NEXT ElevatedButton:

ElevatedButton(
  child: const Text('NEXT'),
  onPressed: () {
    Navigator.pop(context);
  },
  style: ElevatedButton.styleFrom(
    elevation: 8.0,
  ),
),

保存您的项目。

调整卡片高度

目前,这些卡片位于网站导航旁的白色表面上。

home.dart 中,将 elevation: 值添加到卡片:

// TODO: Adjust card heights (103)
elevation: 0.0,

保存项目。

Android

iOS

Shrine 产品网格页面(没有每张卡片的高度)

Shrine 产品网格页面(没有每张卡片的高度)

您已移除卡片下的阴影。

7. 添加形状

Shrine 采用很酷的几何样式,通过八角形或矩形定义各种元素。接下来,我们要在主屏幕的卡片中以及登录屏幕的文本字段和按钮中实现这种形状样式设置。

更改登录屏幕上的文本字段形状

app.dart 中,导入以下文件:

import 'supplemental/cut_corners_border.dart';

还是在 app.dart 中,修改文本字段的装饰主题,以使用切角边框:

// TODO: Decorate the inputs (103)
inputDecorationTheme: const InputDecorationTheme(
  border: CutCornersBorder(),
  focusedBorder: CutCornersBorder(
    borderSide: BorderSide(
      width: 2.0,
      color: kShrineBrown900,
    ),
  ),
  floatingLabelStyle: TextStyle(
    color: kShrineBrown900,
  ),
),

更改登录屏幕上的按钮形状

login.dart 中,向 CANCEL 按钮添加斜角矩形边框:

TextButton(
  child: const Text('CANCEL'),
  onPressed: () {
    _usernameController.clear();
    _passwordController.clear();
  },
  style: TextButton.styleFrom(
    primary: Theme.of(context).colorScheme.secondary,
    shape: const BeveledRectangleBorder(
      borderRadius: BorderRadius.all(Radius.circular(7.0)),
    ),
  ),
),

TextButton 没有可见的形状,为什么要添加边框形状呢?因为在该按钮被触摸时,涟漪动画会绑定到同一个形状。

现在,向“NEXT”按钮添加相同的形状:

ElevatedButton(
  child: const Text('NEXT'),
  onPressed: () {
    Navigator.pop(context);
  },
  style: ElevatedButton.styleFrom(
    elevation: 8.0,
    shape: const BeveledRectangleBorder(
      borderRadius: BorderRadius.all(Radius.circular(7.0)),
    ),
  ),

如需更改所有按钮的形状,我们还可以使用 app.dart 中的 elevatedButtonThemetextButtonTheme。且把这个挑战留给学习者吧!

热重启。

Android

iOS

应用了形状主题的 Shrine 登录页面

应用了形状主题的 Shrine 登录页面

8. 更改布局

接下来,我们要更改布局,以不同的宽高比和大小显示卡片,使每张卡片看起来都各不相同。

将 GridView 替换为 AsymmetricView

我们已经编写了一个非对称布局的文件。

home.dart 中,添加以下导入操作:

import 'supplemental/asymmetric_view.dart';

删除 _buildGridCards 并替换 body

body: AsymmetricView(
  products: ProductsRepository.loadProducts(Category.all),
),

保存项目。

Android

iOS

Shrine 产品页面,其中包含不对称的可水平滚动布局

Shrine 产品页面,其中包含不对称的可水平滚动布局

现在,产品水平滚动,形成一种类似编织物的图案。

9. 尝试其他主题(可选)

颜色是一种有力的品牌表达方式,颜色的细微变化都会对用户体验产生巨大影响。为了检验这一点,我们来看看在品牌配色方案略有不同的情况下 Shrine 的外观如何。

修改颜色

colors.dart 中,添加以下颜色:

const kShrinePurple = Color(0xFF5D1049);

app.dart 中,将 _buildShrineTheme() 函数更改为以下内容:

ThemeData _buildShrineTheme() {
  final ThemeData base = ThemeData.light();
  return base.copyWith(
    colorScheme: base.colorScheme.copyWith(
      primary: kShrinePurple,
      secondary: kShrinePurple,
      error: kShrineErrorRed,
    ),
    scaffoldBackgroundColor: kShrineSurfaceWhite,
    textSelectionTheme: const TextSelectionThemeData(
      selectionColor: kShrinePurple,
    ),
    inputDecorationTheme: const InputDecorationTheme(
      border: CutCornersBorder(),
      focusedBorder: CutCornersBorder(
        borderSide: BorderSide(
          width: 2.0,
          color: kShrinePurple,
        ),
      ),
      floatingLabelStyle: TextStyle(
        color: kShrinePurple,
      ),
    ),
  );
}

热重启。此时应该会显示新的主题。

Android

iOS

以紫色和白色为主题的 Shrine 登录页面

以紫色和白色为主题的 Shrine 登录页面

Android

iOS

紫色和白色主题的 Shrine 商品页面

紫色和白色主题的 Shrine 商品页面

结果大不相同!请将 app.dart's _buildShrineTheme 还原为此步骤之前的状态。或者,下载 104 起始代码。

10. 恭喜!

现在,您已创建了一个与设计人员提出的设计规范相似的应用。

后续步骤

现在,您已使用过以下 MDC 组件:主题、字体排版、高度和形状。您可以继续探索 MDC-Flutter 库中的更多组件和子系统。

请深入研究 supplemental 目录中的文件,了解如何创建水平滚动的非对称布局网格。

如果您规划的应用设计中包含的元素在 MDC 库中没有相应的组件,该怎么办?在 MDC-104:Material Design 高级组件中,我们将介绍如何使用 MDC 库创建自定义组件来实现特定外观。

我能够用合理的时间和精力完成此 Codelab

非常同意 同意 一般 不同意 非常不同意

我希望日后继续使用 Material Components

非常同意 同意 一般 不同意 非常不同意