1. 简介
Material Components (MDC) 有助于开发者实现 Material Design。MDC 由 Google 的工程师和用户体验设计人员倾力打造,提供数十种精美实用的界面组件,可用于 Android、iOS、网页和 Flutter。如需了解详情,请访问 material.io/develop |
在 Codelab MDC-101 中,您已经使用以下两种 Material 组件构建了一个登录页面:文本字段和带有水墨涟漪效果的按钮。现在,我们在此基础上通过添加导航、结构和数据进行扩展。
您将构建的内容
在此 Codelab 中,您将为名为 Shrine 的应用构建一个主屏幕。Shrine 是一款销售服装和家居用品的电子商务应用。其中包含:
- 顶部应用栏
- 商品网格列表
Android | iOS |
此 Codelab 中的 Material 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-101 继续操作?
如果您完成了 MDC-101,您的代码应该就能用于此 Codelab。跳到步骤:添加顶部应用栏。
从头开始?
下载 Codelab 入门版应用
起始应用位于 material-components-flutter-codelabs-102-starter_and_101-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 102-starter_and_101-complete
打开项目并运行应用
- 在您选择的编辑器中打开项目。
- 按照所选编辑器的《使用入门:试驾》中的说明“运行应用”。
大功告成!您应该会在设备上看到 MDC-101 Codelab 中的 Shrine 登录页面。
Android | iOS |
现在,登录屏幕看起来很好,让我们在应用中增加一些商品。
4. 添加顶部应用栏
现在,如果您点击“Next”按钮,就可以看到显示“You did it!”的主屏幕。太棒了!但现在用户没有任何可执行的操作,或者对在应用中的位置知情。为提供帮助,是时候添加导航了。
Material Design 可提供确保高度易用性的导航模式。顶部应用栏是最明显的组件之一。
为了提供导航功能并让用户快速执行其他操作,我们来添加一个顶部应用栏。
添加 AppBar widget
在 home.dart
中,向 Scaffold 中添加 AppBar,并移除突出显示的 const
:
return const Scaffold(
// TODO: Add app bar (102)
appBar: AppBar(
// TODO: Add buttons and title (102)
),
将 AppBar 添加到 Scaffold 的 appBar:
字段中,即可免费获得完美的布局,将 AppBar 保持在页面顶部和正文下方。
保存您的项目。当 Shrine 应用更新时,点击 Next 查看主屏幕。
Android | iOS |
AppBar 看起来很棒,但还需要一个标题。
添加 Text 微件
在 home.dart
中,为 AppBar 添加一个标题:
// TODO: Add app bar (102)
appBar: AppBar(
// TODO: Add buttons and title (102)
title: const Text('SHRINE'),
// TODO: Add trailing buttons (102)
保存您的项目。
Android | iOS |
许多应用栏的标题旁边都有一个按钮。让我们在应用中添加一个菜单图标。
添加位于首部的 IconButton
同样,仍是在 home.dart
中,为 AppBar 的 leading:
字段设置一个 IconButton。(将其放在 title:
字段前,以模拟从首到尾的顺序):
// TODO: Add buttons and title (102)
leading: IconButton(
icon: const Icon(
Icons.menu,
semanticLabel: 'menu',
),
onPressed: () {
print('Menu button');
},
),
保存您的项目。
Android | iOS |
菜单图标(也称为“汉堡图标”)会显示在您预期的位置。
您也可以在标题尾部添加按钮。在 Flutter 中,它们被称为“操作”。
添加操作
剩余空间还可以添加两个 IconButton。
将它们添加到标题后的 AppBar 实例中:
// TODO: Add trailing buttons (102)
actions: <Widget>[
IconButton(
icon: const Icon(
Icons.search,
semanticLabel: 'search',
),
onPressed: () {
print('Search button');
},
),
IconButton(
icon: const Icon(
Icons.tune,
semanticLabel: 'filter',
),
onPressed: () {
print('Filter button');
},
),
],
保存您的项目。主屏幕看起来应该像下面这样:
Android | iOS |
现在,应用的右侧有一个首部按钮、一个标题和两个操作。应用栏还使用细微的阴影来显示高度,表示其与内容位于不同的层级。
5. 在网格中添加卡
应用现在已初步成型,让我们接着放置一些卡片来组织内容。
添加 GridView
首先,在顶部应用栏下方添加一张卡片。单独使用卡片微件的信息不足,无法使其出现在正确的位置,因此我们需要将其封装在 GridView 微件中。
将 Scaffold 正文的中心替换为 GridView:
// TODO: Add a grid view (102)
body: GridView.count(
crossAxisCount: 2,
padding: const EdgeInsets.all(16.0),
childAspectRatio: 8.0 / 9.0,
// TODO: Build a grid of cards (102)
children: <Widget>[Card()],
),
我们来分析该代码。GridView 会调用 count()
构造函数,因为其显示的项数是可数的而不是无限的。不过,它需要更多信息才能定义其布局。
crossAxisCount:
指定横向显示项数。我们设置为 2 列。
padding:
字段为 GridView 的 4 条边设置内边距。当然,您看不到尾部或底边的内边距,因为这两边旁边还没有 GridView 子项。
childAspectRatio:
字段会根据宽高比(宽度与高度的比)确定项目的大小。
默认情况下,GridView 将创建大小相同的图块。
我们有一个空卡片,让我们为卡片添加一些子级微件。
布局内容
卡片应包含一张图片、一个标题和一个辅助文本。
更新 GridView 的子项:
// TODO: Build a grid of cards (102)
children: <Widget>[
Card(
clipBehavior: Clip.antiAlias,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
AspectRatio(
aspectRatio: 18.0 / 11.0,
child: Image.asset('assets/diamond.png'),
),
Padding(
padding: const EdgeInsets.fromLTRB(16.0, 12.0, 16.0, 8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text('Title'),
const SizedBox(height: 8.0),
Text('Secondary Text'),
],
),
),
],
),
)
],
此代码会添加一个列微件,用于垂直布局子微件。
crossAxisAlignment: field
指定 CrossAxisAlignment.start
,这意味着“将文本与前缘对齐”。
无论提供何种类型的图片,AspectRatio 微件都会决定图片所采用的形状。
Padding 使得文本与边框保持一定距离。
两个 Text 微件垂直堆叠,它们之间保持 8 个单位的间隔 (SizedBox)。我们将使用另一个 Column 来把它们放到 Padding 中。
保存您的项目。
Android | iOS |
在此预览中,您可以看到卡片从边缘插入,带有圆角和阴影(这表示卡片的高度)。整个形状在 Material 中被称为“容器”。(不要与名为 Container 的实际微件类混淆。)
卡片通常以集合的形式和其他卡片一起出现,让我们在网格中给它们布局。
6. 进行卡片收集
当屏幕上出现多张卡片时,它们就会组成一个或多个集合。集合中的卡片是共面的,这意味着卡片共享相同的静止高度(除非卡片被拾起或拖动,但在这里我们不会这么做)。
将卡片添加到集合中
现在,我们在 GridView 的 children:
字段中构造了卡片。这有一大段难以阅读的嵌套代码。让我们将它提取到一个可以生成任意数量的空卡片的函数,然后返回卡片列表。
在 build()
函数上方创建新的专用函数(请注意,以下划线开头的函数是私有 API):
// TODO: Make a collection of cards (102)
List<Card> _buildGridCards(int count) {
List<Card> cards = List.generate(
count,
(int index) {
return Card(
clipBehavior: Clip.antiAlias,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
AspectRatio(
aspectRatio: 18.0 / 11.0,
child: Image.asset('assets/diamond.png'),
),
Padding(
padding: const EdgeInsets.fromLTRB(16.0, 12.0, 16.0, 8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: const <Widget>[
Text('Title'),
SizedBox(height: 8.0),
Text('Secondary Text'),
],
),
),
],
),
);
},
);
return cards;
}
将生成的卡片分配给 GridView 的 children
字段。记得用这一新代码替换 GridView 中包含的所有内容:
// TODO: Add a grid view (102)
body: GridView.count(
crossAxisCount: 2,
padding: const EdgeInsets.all(16.0),
childAspectRatio: 8.0 / 9.0,
children: _buildGridCards(10) // Replace
),
保存您的项目。
Android | iOS |
卡片已经显示,但尚未显示任何内容。现在是时候添加商品数据了。
添加商品数据
此应用中的一些商品包含图片、名称和价格信息。让我们将这些信息添加到已有的卡片微件中
然后,在 home.dart
中,导入数据模型需要的新软件包和文件:
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'model/product.dart';
import 'model/products_repository.dart';
最后,更改 _buildGridCards()
以获取商品信息,并将这些数据应用到卡片中:
// TODO: Make a collection of cards (102)
// Replace this entire method
List<Card> _buildGridCards(BuildContext context) {
List<Product> products = ProductsRepository.loadProducts(Category.all);
if (products.isEmpty) {
return const <Card>[];
}
final ThemeData theme = Theme.of(context);
final NumberFormat formatter = NumberFormat.simpleCurrency(
locale: Localizations.localeOf(context).toString());
return products.map((product) {
return Card(
clipBehavior: Clip.antiAlias,
// TODO: Adjust card heights (103)
child: Column(
// TODO: Center items on the card (103)
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
AspectRatio(
aspectRatio: 18 / 11,
child: Image.asset(
product.assetName,
package: product.assetPackage,
// TODO: Adjust the box size (102)
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.fromLTRB(16.0, 12.0, 16.0, 8.0),
child: Column(
// TODO: Align labels to the bottom and center (103)
crossAxisAlignment: CrossAxisAlignment.start,
// TODO: Change innermost Column (103)
children: <Widget>[
// TODO: Handle overflowing labels (103)
Text(
product.name,
style: theme.textTheme.titleLarge,
maxLines: 1,
),
const SizedBox(height: 8.0),
Text(
formatter.format(product.price),
style: theme.textTheme.titleSmall,
),
],
),
),
),
],
),
);
}).toList();
}
注意:应用现在还无法编译和运行。我们还需要进行一项更改。
此外,在尝试编译之前,更改 build()
函数以将 BuildContext 传递到 _buildGridCards()
:
// TODO: Add a grid view (102)
body: GridView.count(
crossAxisCount: 2,
padding: const EdgeInsets.all(16.0),
childAspectRatio: 8.0 / 9.0,
children: _buildGridCards(context) // Changed code
),
热重启应用。
Android | iOS |
您可能已经注意到,我们未在卡片之间添加任何垂直的间隔。这是因为默认情况下顶部和底部外边距为 4 个点。
保存您的项目。
商品数据显示出来了,但图像四周有额外的空间。图像默认依据 .scaleDown
的 BoxFit 绘制(在这个情况下)。让我们将其更改为 .fitWidth
,以便将图像略微放大一点,删除多余的空白。
向图像添加 fit:
字段,值为 BoxFit.fitWidth
:
// TODO: Adjust the box size (102)
fit: BoxFit.fitWidth,
Android | iOS |
现在,我们的商品完美地展现在应用中了!
7. 恭喜!
我们的应用已经有了基本的流程,可将用户从登录页面转到主屏幕,然后用户可在主屏幕中查看商品。通过几行代码,我们添加了一个顶部应用栏(包含标题和三个按钮)以及卡片(用于展示应用的内容)。我们的主屏幕简洁实用,具有基本的结构和可操作的内容。
后续步骤
在顶部应用栏、卡片、文本字段和按钮中,我们现在使用 Material Flutter 库中的四个核心组件!您可以访问 Material 组件 widget 目录探索更多内容。
虽然我们的应用完全可以正常运行,但它尚未表达任何特殊的品牌或观点。在 MDC-103:通过颜色、形状、高度和类型设置 Material Design 主题中,我们将自定义这些组件的样式,来诠释一个充满活力的、现代的品牌。