1. 简介
Material Components (MDC) 有助于开发者实现 Material Design。MDC 是由一组 Google 工程师和用户体验设计人员倾心打造的,提供数十种精美实用的界面组件,可用于 Android、iOS、Web 和 Flutter.material.io/develop |
现在,您可以比以往更多地使用 MDC 自定义应用的独特样式。Material Design 的近期扩展可让设计师和开发者更灵活地表达其产品的品牌。
在 Codelab MDC-101 和 MDC-102 中,您使用 Material Components (MDC) 为名为 Shrine 的应用构建了基础内容。Shrine 是一款销售服装和家居用品的电子商务应用。该应用包含的用户流从登录屏幕开始,然后将用户转到显示产品的主屏幕。
构建内容
在此 Codelab 中,您将利用以下各项自定义 Shrine 应用:
- 颜色
- 字体排版
- 高度
- 形状
- 布局
Android | iOS |
此 Codelab 中的 MDC-Flutter 组件和子系统
- 主题
- 字体排版
- 高度
- 图片列表
您在 Flutter 开发方面的经验处于什么水平?
2. 设置您的 Flutter 开发环境
您需要使用两款软件才能完成此 Codelab:Flutter SDK 和一款编辑器。
您可以使用以下任一设备运行此 Codelab:
- 一台连接到计算机并设置为开发者模式的实体 Android 或 iOS 设备。
- iOS 模拟器(需要安装 Xcode 工具)。
- Android 模拟器(需要在 Android Studio 中设置)。
- 浏览器(调试需要 Chrome)。
- 作为 Windows、Linux 或 macOS 桌面应用使用。您必须在打算实施部署的平台上进行开发。因此,如果您要开发 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
打开项目并运行应用
- 在您选择的编辑器中打开项目。
- 按照所选编辑器的《使用入门:试驾》中的说明“运行应用”。
大功告成!您应该会在设备上看到之前的 Codelab 中的 Shrine 登录页面。
Android | iOS |
点击“Next”可查看上一个 Codelab 中的首页。
Android | iOS |
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 的颜色。
微件的每个带颜色参数都会映射到这些方案中的一种颜色。例如,文本字段在主动接收输入内容时,它的装饰的颜色应为主题的主色。如果该颜色不易辨认(在所处背景下难以辨别),请改用 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 |
主屏幕应如下所示:
Android | iOS |
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 |
登录屏幕和主屏幕上的文本看起来不同:有些文本使用的是 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 |
看起来效果好多了。
设置文本字段的主题
您还可以使用 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 |
在文本字段中输入文字 - 边框和浮动标签会以主要颜色呈现。不过,我们不太容易看出来。人在色彩对比度不够高的情况下难以区分像素,因此这些元素不易辨认。(如需了解详情,请参阅 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 |
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 |
您已移除卡片下的阴影。
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
中的 elevatedButtonTheme
或 textButtonTheme
。且把这个挑战留给学习者吧!
热重启。
Android | iOS |
8. 更改布局
接下来,我们要更改布局,以不同的宽高比和大小显示卡片,使每张卡片看起来都各不相同。
将 GridView 替换为 AsymmetricView
我们已经编写了一个非对称布局的文件。
在 home.dart
中,添加以下导入操作:
import 'supplemental/asymmetric_view.dart';
删除 _buildGridCards
并替换 body
:
body: AsymmetricView(
products: ProductsRepository.loadProducts(Category.all),
),
保存项目。
Android | iOS |
现在,产品水平滚动,形成一种类似编织物的图案。
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 |
Android | iOS |
结果大不相同!请将 app.dart's
_buildShrineTheme
还原为此步骤之前的状态。或者,下载 104 起始代码。
10. 恭喜!
现在,您已创建了一个与设计人员提出的设计规范相似的应用。
后续步骤
现在,您已使用过以下 MDC 组件:主题、字体排版、高度和形状。您可以继续探索 MDC-Flutter 库中的更多组件和子系统。
请深入研究 supplemental
目录中的文件,了解如何创建水平滚动的非对称布局网格。
如果您规划的应用设计中包含的元素在 MDC 库中没有相应的组件,该怎么办?在 MDC-104:Material Design 高级组件中,我们将介绍如何使用 MDC 库创建自定义组件来实现特定外观。